diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 30af24bd2..a2c56fad9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,7 +1,7 @@ # trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue # trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions -FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12 +FROM mcr.microsoft.com/devcontainers/cpp:2-debian-12 USER root diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bf1c50982..bc660170c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,7 @@ "features": { "ghcr.io/devcontainers/features/python:1": { "installTools": true, - "version": "latest" + "version": "3.13" } }, "customizations": { diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml index 2ef67405a..b62729332 100644 --- a/.github/workflows/build_firmware.yml +++ b/.github/workflows/build_firmware.yml @@ -19,6 +19,8 @@ jobs: pio-build: name: build-${{ inputs.platform }} runs-on: ubuntu-24.04 + outputs: + artifact-id: ${{ steps.upload.outputs.artifact-id }} steps: - uses: actions/checkout@v5 with: @@ -55,6 +57,7 @@ jobs: - name: Store binaries as an artifact uses: actions/upload-artifact@v4 + id: upload with: name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip overwrite: true diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml index f5352b3c4..9c57f8b7d 100644 --- a/.github/workflows/build_one_arch.yml +++ b/.github/workflows/build_one_arch.yml @@ -3,6 +3,7 @@ name: Build One Arch on: workflow_dispatch: inputs: + # trunk-ignore(checkov/CKV_GHA_7) arch: type: choice options: @@ -16,10 +17,13 @@ on: - stm32 - native +permissions: read-all + +env: + INPUT_ARCH: ${{ github.event.inputs.arch }} + jobs: setup: - strategy: - fail-fast: false runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 @@ -31,23 +35,11 @@ jobs: - name: Generate matrix id: jsonStep run: | - if [[ "$GITHUB_HEAD_REF" == "" ]]; then - TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra) - else - TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr) - fi - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" - echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra) + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" + echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - rp2350: ${{ steps.jsonStep.outputs.rp2350 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} - check: ${{ steps.jsonStep.outputs.check }} + selected_arch: ${{ steps.jsonStep.outputs.selected_arch }} version: runs-on: ubuntu-latest @@ -64,101 +56,18 @@ jobs: long: ${{ steps.version.outputs.long }} deb: ${{ steps.version.outputs.deb }} - build-esp32: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}} + build: + if: ${{ github.event_name != 'workflow_dispatch' }} needs: [setup, version] strategy: fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + matrix: + build: ${{ fromJson(needs.setup.outputs.selected_arch) }} uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32 - - build-esp32s3: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32s3 - - build-esp32c3: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c3 - - build-esp32c6: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c6 - - build-nrf52840: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: nrf52840 - - build-rp2040: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2040 - - build-rp2350: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2350 - - build-stm32: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: stm32 + pio_env: ${{ matrix.build.board }} + platform: ${{ matrix.build.arch }} build-debian-src: if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} @@ -179,62 +88,6 @@ jobs: if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }} uses: ./.github/workflows/test_native.yml - docker-deb-amd64: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - - docker-deb-amd64-tft: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - pio_env: native-tft - - docker-alp-amd64: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: alpine - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - - docker-alp-amd64-tft: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: alpine - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - pio_env: native-tft - - docker-deb-arm64: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/arm64 - runs-on: ubuntu-24.04-arm - push: false - - docker-deb-armv7: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/arm/v7 - runs-on: ubuntu-24.04-arm - push: false - gather-artifacts: permissions: contents: write @@ -252,18 +105,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - needs: - [ - version, - build-esp32, - build-esp32s3, - build-esp32c3, - build-esp32c6, - build-nrf52840, - build-rp2040, - build-rp2350, - build-stm32, - ] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 @@ -332,169 +174,3 @@ jobs: name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" github-token: ${{ secrets.GITHUB_TOKEN }} - - release-artifacts: - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} - needs: - - version - - gather-artifacts - - build-debian-src - - package-pio-deps-native-tft - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - name: Create release - uses: softprops/action-gh-release@v2 - id: create_release - with: - draft: true - prerelease: true - name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha - tag_name: v${{ needs.version.outputs.long }} - body: | - Autogenerated by github action, developer should edit as required before publishing... - - - name: Download source deb - uses: actions/download-artifact@v5 - with: - pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src - merge-multiple: true - path: ./output/debian-src - - - name: Download `native-tft` pio deps - uses: actions/download-artifact@v5 - with: - pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output/pio-deps-native-tft - - - name: Zip Linux sources - working-directory: output - run: | - zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src - zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add Linux sources to GtiHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip - gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - release-firmware: - strategy: - fail-fast: false - matrix: - arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [release-artifacts, version] - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output - - - name: Display structure of downloaded files - run: ls -lR - - - name: Device scripts permissions - run: | - chmod +x ./output/device-install.sh - chmod +x ./output/device-update.sh - - - name: Zip firmware - run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output - - - uses: actions/download-artifact@v5 - with: - name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - merge-multiple: true - path: ./elfs - - - name: Zip debug elfs - run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add bins and debug elfs to GitHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish-firmware: - runs-on: ubuntu-24.04 - if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [release-firmware, version] - env: - targets: |- - esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./publish - - - name: Publish firmware to meshtastic.github.io - uses: peaceiris/actions-gh-pages@v4 - env: - # On event/* branches, use the event name as the destination prefix - DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }} - with: - deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }} - external_repository: meshtastic/meshtastic.github.io - publish_branch: master - publish_dir: ./publish - destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }} - keep_files: true - user_name: github-actions[bot] - user_email: github-actions[bot]@users.noreply.github.com - commit_message: ${{ needs.version.outputs.long }} - enable_jekyll: true diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml index 3c83ce960..15b3fdba9 100644 --- a/.github/workflows/build_one_target.yml +++ b/.github/workflows/build_one_target.yml @@ -3,6 +3,7 @@ name: Build One Target on: workflow_dispatch: inputs: + # trunk-ignore(checkov/CKV_GHA_7) arch: type: choice options: @@ -19,11 +20,13 @@ on: type: string required: false description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets. - # find-target: # type: boolean # default: true # description: 'Find the available targets' + +permissions: read-all + jobs: find-targets: if: ${{ inputs.target == '' }} @@ -51,13 +54,13 @@ jobs: - name: Generate matrix id: jsonStep run: | - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra) echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY echo "Targets:" >> $GITHUB_STEP_SUMMARY - echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY + echo $TARGETS >> $GITHUB_STEP_SUMMARY version: if: ${{ inputs.target != '' }} @@ -75,11 +78,9 @@ jobs: long: ${{ steps.version.outputs.long }} deb: ${{ steps.version.outputs.deb }} - build-arch: + build: if: ${{ inputs.target != '' && inputs.arch != 'native' }} needs: [version] - strategy: - fail-fast: false uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} @@ -105,70 +106,12 @@ jobs: if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }} uses: ./.github/workflows/test_native.yml - docker-deb-amd64: - if: ${{ inputs.target != '' && inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - - docker-deb-amd64-tft: - if: ${{ inputs.target != '' && inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - pio_env: native-tft - - docker-alp-amd64: - if: ${{ inputs.target != '' && inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: alpine - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - - docker-alp-amd64-tft: - if: ${{ inputs.target != '' && inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: alpine - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - pio_env: native-tft - - docker-deb-arm64: - if: ${{ inputs.target != '' && inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/arm64 - runs-on: ubuntu-24.04-arm - push: false - - docker-deb-armv7: - if: ${{ inputs.target != '' && inputs.arch == 'native' }} - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/arm/v7 - runs-on: ubuntu-24.04-arm - push: false - gather-artifacts: permissions: contents: write pull-requests: write - strategy: - fail-fast: false runs-on: ubuntu-latest - needs: [version, build-arch] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 @@ -237,159 +180,3 @@ jobs: name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }} description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" github-token: ${{ secrets.GITHUB_TOKEN }} - - release-artifacts: - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}} - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} - needs: - - version - - gather-artifacts - - build-debian-src - - package-pio-deps-native-tft - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - name: Create release - uses: softprops/action-gh-release@v2 - id: create_release - with: - draft: true - prerelease: true - name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha - tag_name: v${{ needs.version.outputs.long }} - body: | - Autogenerated by github action, developer should edit as required before publishing... - - - name: Download source deb - uses: actions/download-artifact@v5 - with: - pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src - merge-multiple: true - path: ./output/debian-src - - - name: Download `native-tft` pio deps - uses: actions/download-artifact@v5 - with: - pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output/pio-deps-native-tft - - - name: Zip Linux sources - working-directory: output - run: | - zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src - zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add Linux sources to GtiHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip - gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - release-firmware: - strategy: - fail-fast: false - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}} - needs: [release-artifacts, version] - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-*-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output - - - name: Display structure of downloaded files - run: ls -lR - - - name: Device scripts permissions - run: | - chmod +x ./output/device-install.sh - chmod +x ./output/device-update.sh - - - name: Zip firmware - run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output - - - uses: actions/download-artifact@v5 - with: - pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip - merge-multiple: true - path: ./elfs - - - name: Zip debug elfs - run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add bins and debug elfs to GitHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip - gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish-firmware: - runs-on: ubuntu-24.04 - if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }} - needs: [release-firmware, version] - env: - targets: |- - esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./publish - - - name: Publish firmware to meshtastic.github.io - uses: peaceiris/actions-gh-pages@v4 - env: - # On event/* branches, use the event name as the destination prefix - DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }} - with: - deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }} - external_repository: meshtastic/meshtastic.github.io - publish_branch: master - publish_dir: ./publish - destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }} - keep_files: true - user_name: github-actions[bot] - user_email: github-actions[bot]@users.noreply.github.com - commit_message: ${{ needs.version.outputs.long }} - enable_jekyll: true diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index f61e314a7..02a4c23b8 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -27,19 +27,11 @@ on: jobs: setup: - if: github.repository == 'meshtastic/firmware' strategy: - fail-fast: false + fail-fast: true matrix: arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 + - all - check runs-on: ubuntu-24.04 steps: @@ -49,33 +41,22 @@ jobs: python-version: 3.x cache: pip - run: pip install -U platformio - - name: Uncomment build epoch - shell: bash - run: | - sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini - name: Generate matrix id: jsonStep run: | if [[ "$GITHUB_HEAD_REF" == "" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr) fi - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" - echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" + echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT + echo "$TARGETS" >> $GITHUB_STEP_SUMMARY outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - rp2350: ${{ steps.jsonStep.outputs.rp2350 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} + all: ${{ steps.jsonStep.outputs.all }} check: ${{ steps.jsonStep.outputs.check }} version: - if: github.repository == 'meshtastic/firmware' runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -94,7 +75,8 @@ jobs: needs: setup strategy: fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.check) }} + matrix: + check: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }} @@ -103,96 +85,20 @@ jobs: - name: Build base id: base uses: ./.github/actions/setup-base - - name: Check ${{ matrix.board }} - run: bin/check-all.sh ${{ matrix.board }} + - name: Check ${{ matrix.check.board }} + run: bin/check-all.sh ${{ matrix.check.board }} - build-esp32: + build: needs: [setup, version] strategy: fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + matrix: + build: ${{ fromJson(needs.setup.outputs.all) }} uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32 - - build-esp32s3: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32s3 - - build-esp32c3: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c3 - - build-esp32c6: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c6 - - build-nrf52840: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: nrf52840 - - build-rp2040: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2040 - - build-rp2350: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2350 - - build-stm32: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: stm32 + pio_env: ${{ matrix.build.board }} + platform: ${{ matrix.build.platform }} build-debian-src: if: github.repository == 'meshtastic/firmware' @@ -203,7 +109,7 @@ jobs: secrets: inherit package-pio-deps-native-tft: - if: ${{ github.event_name == 'workflow_dispatch' }} + if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }} uses: ./.github/workflows/package_pio_deps.yml with: pio_env: native-tft @@ -213,60 +119,26 @@ jobs: if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }} uses: ./.github/workflows/test_native.yml - docker-deb-amd64: - if: github.repository == 'meshtastic/firmware' + docker: + strategy: + fail-fast: false + matrix: + distro: [debian, alpine] + platform: [linux/amd64, linux/arm64, linux/arm/v7] + pio_env: [native, native-tft] + exclude: + - distro: alpine + platform: linux/arm/v7 + - pio_env: native-tft + platform: linux/arm64 + - pio_env: native-tft + platform: linux/arm/v7 uses: ./.github/workflows/docker_build.yml with: - distro: debian - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - - docker-deb-amd64-tft: - if: github.repository == 'meshtastic/firmware' - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - pio_env: native-tft - - docker-alp-amd64: - if: github.repository == 'meshtastic/firmware' - uses: ./.github/workflows/docker_build.yml - with: - distro: alpine - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - - docker-alp-amd64-tft: - if: github.repository == 'meshtastic/firmware' - uses: ./.github/workflows/docker_build.yml - with: - distro: alpine - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - pio_env: native-tft - - docker-deb-arm64: - if: github.repository == 'meshtastic/firmware' - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/arm64 - runs-on: ubuntu-24.04-arm - push: false - - docker-deb-armv7: - if: github.repository == 'meshtastic/firmware' - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/arm/v7 - runs-on: ubuntu-24.04-arm + distro: ${{ matrix.distro }} + platform: ${{ matrix.platform }} + runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} + pio_env: ${{ matrix.pio_env }} push: false gather-artifacts: @@ -288,18 +160,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - needs: - [ - version, - build-esp32, - build-esp32s3, - build-esp32c3, - build-esp32c6, - build-nrf52840, - build-rp2040, - build-rp2350, - build-stm32, - ] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index 7f397ce18..e8c3d3450 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -7,23 +7,13 @@ on: # Merge group is a special trigger that is used to trigger the workflow when a merge group is created. merge_group: -env: - FAIL_FAST_PER_ARCH: true - jobs: setup: strategy: fail-fast: true matrix: arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 + - all - check runs-on: ubuntu-24.04 steps: @@ -39,19 +29,12 @@ jobs: if [[ "$GITHUB_HEAD_REF" == "" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr) fi - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" - echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" + echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - rp2350: ${{ steps.jsonStep.outputs.rp2350 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} + all: ${{ steps.jsonStep.outputs.all }} check: ${{ steps.jsonStep.outputs.check }} version: @@ -73,7 +56,8 @@ jobs: needs: setup strategy: fail-fast: true - matrix: ${{ fromJson(needs.setup.outputs.check) }} + matrix: + check: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest if: ${{ github.event_name != 'workflow_dispatch' }} @@ -82,96 +66,19 @@ jobs: - name: Build base id: base uses: ./.github/actions/setup-base - - name: Check ${{ matrix.board }} - run: bin/check-all.sh ${{ matrix.board }} + - name: Check ${{ matrix.check.board }} + run: bin/check-all.sh ${{ matrix.check.board }} - build-esp32: + build: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + matrix: + build: ${{ fromJson(needs.setup.outputs.all) }} uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32 - - build-esp32s3: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32s3 - - build-esp32c3: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c3 - - build-esp32c6: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c6 - - build-nrf52840: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: nrf52840 - - build-rp2040: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2040 - - build-rp2350: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2350 - - build-stm32: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: stm32 + pio_env: ${{ matrix.build.board }} + platform: ${{ matrix.build.platform }} build-debian-src: if: github.repository == 'meshtastic/firmware' @@ -192,54 +99,26 @@ jobs: if: ${{ !contains(github.ref_name, 'event/') }} uses: ./.github/workflows/test_native.yml - docker-deb-amd64: + docker: + strategy: + fail-fast: false + matrix: + distro: [debian, alpine] + platform: [linux/amd64, linux/arm64, linux/arm/v7] + pio_env: [native, native-tft] + exclude: + - distro: alpine + platform: linux/arm/v7 + - pio_env: native-tft + platform: linux/arm64 + - pio_env: native-tft + platform: linux/arm/v7 uses: ./.github/workflows/docker_build.yml with: - distro: debian - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - - docker-deb-amd64-tft: - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - pio_env: native-tft - - docker-alp-amd64: - uses: ./.github/workflows/docker_build.yml - with: - distro: alpine - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - - docker-alp-amd64-tft: - uses: ./.github/workflows/docker_build.yml - with: - distro: alpine - platform: linux/amd64 - runs-on: ubuntu-24.04 - push: false - pio_env: native-tft - - docker-deb-arm64: - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/arm64 - runs-on: ubuntu-24.04-arm - push: false - - docker-deb-armv7: - uses: ./.github/workflows/docker_build.yml - with: - distro: debian - platform: linux/arm/v7 - runs-on: ubuntu-24.04-arm + distro: ${{ matrix.distro }} + platform: ${{ matrix.platform }} + runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} + pio_env: ${{ matrix.pio_env }} push: false gather-artifacts: @@ -260,18 +139,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - needs: - [ - version, - build-esp32, - build-esp32s3, - build-esp32c3, - build-esp32c6, - build-nrf52840, - build-rp2040, - build-rp2350, - build-stm32, - ] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml index 96c993cba..2059fde2c 100644 --- a/.github/workflows/sec_sast_semgrep_cron.yml +++ b/.github/workflows/sec_sast_semgrep_cron.yml @@ -41,7 +41,7 @@ jobs: # step 4 - name: publish code scanning alerts - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: report.sarif category: semgrep diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml index 32e2c2c8b..11ba59386 100644 --- a/.github/workflows/stale_bot.yml +++ b/.github/workflows/stale_bot.yml @@ -17,8 +17,10 @@ jobs: steps: - name: Stale PR+Issues - uses: actions/stale@v10.0.0 + uses: actions/stale@v10.1.0 with: days-before-stale: 45 - exempt-issue-labels: pinned,3.0 - exempt-pr-labels: pinned,3.0 + stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days. + close-issue-message: This issue has not had any comment since the last notice. It has been closed automatically. If this is incorrect, or the issue becomes relevant again, please request that it is reopened. + exempt-issue-labels: pinned,3.0,triaged,backlog + exempt-pr-labels: pinned,3.0,triaged,backlog diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml index 6b788f4c7..9cf1c9e53 100644 --- a/.github/workflows/test_native.yml +++ b/.github/workflows/test_native.yml @@ -40,7 +40,7 @@ jobs: - name: Integration test run: | - .pio/build/coverage/program & + .pio/build/coverage/program -s & PID=$! timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done" echo "Simulator started, launching python test..." diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 942659348..7852fc31f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,7 @@ jobs: pio upgrade - name: Setup Node - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: 22 diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index ec1563931..0a43c3079 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,24 +4,24 @@ cli: plugins: sources: - id: trunk - ref: v1.7.2 + ref: v1.7.3 uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.473 - - renovate@41.132.5 + - checkov@3.2.486 + - renovate@41.157.0 - prettier@3.6.2 - - trufflehog@3.90.8 + - trufflehog@3.90.11 - yamllint@1.37.1 - bandit@1.8.6 - - trivy@0.67.0 + - trivy@0.67.2 - taplo@0.10.0 - - ruff@0.13.2 - - isort@6.0.1 + - ruff@0.14.1 + - isort@7.0.0 - markdownlint@0.45.0 - oxipng@9.1.5 - svgo@4.0.0 - - actionlint@1.7.7 + - actionlint@1.7.8 - flake8@7.3.0 - hadolint@2.14.0 - shfmt@3.6.0 diff --git a/Dockerfile b/Dockerfile index b1e151ac7..111dd69fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions -FROM python:3.13-slim-trixie AS builder +FROM python:3.14-slim-trixie AS builder ARG PIO_ENV=native ENV DEBIAN_FRONTEND=noninteractive ENV TZ=Etc/UTC diff --git a/alpine.Dockerfile b/alpine.Dockerfile index 670736241..0469874e4 100644 --- a/alpine.Dockerfile +++ b/alpine.Dockerfile @@ -3,7 +3,7 @@ # trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions -FROM python:3.13-alpine3.22 AS builder +FROM python:3.14-alpine3.22 AS builder ARG PIO_ENV=native ENV PIP_ROOT_USER_ACTION=ignore diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index d2c933461..810c9780e 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -31,6 +31,7 @@ build_flags = -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL -DAXP_DEBUG_PORT=Serial -DCONFIG_BT_NIMBLE_ENABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3 -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 -DCONFIG_BT_NIMBLE_MAX_CCCDS=20 -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192 @@ -56,7 +57,7 @@ lib_deps = # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib - https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip + https://github.com/lewisxhe/XPowersLib/archive/v0.3.1.zip # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini index 1afb9b547..7b06f4cd8 100644 --- a/arch/esp32/esp32c6.ini +++ b/arch/esp32/esp32c6.ini @@ -28,7 +28,7 @@ lib_deps = ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib - lewisxhe/XPowersLib@0.3.0 + lewisxhe/XPowersLib@0.3.1 # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py index aaa76aa45..b4c18c05b 100755 --- a/bin/generate_ci_matrix.py +++ b/bin/generate_ci_matrix.py @@ -1,28 +1,32 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Generate the CI matrix.""" +import argparse import json -import sys -import random import re from platformio.project.config import ProjectConfig -options = sys.argv[1:] +parser = argparse.ArgumentParser(description="Generate the CI matrix") +parser.add_argument("platform", help="Platform to build for") +parser.add_argument( + "--level", + choices=["extra", "pr"], + nargs="*", + default=[], + help="Board level to build for (omit for full release boards)", +) +args = parser.parse_args() outlist = [] -if len(options) < 1: - print(json.dumps(outlist)) - exit(1) - cfg = ProjectConfig.get_instance() pio_envs = cfg.envs() # Gather all PlatformIO environments for filtering later all_envs = [] for pio_env in pio_envs: - env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags') + env_build_flags = cfg.get(f"env:{pio_env}", "build_flags") env_platform = None for flag in env_build_flags: # Extract the platform from the build flags @@ -37,36 +41,35 @@ for pio_env in pio_envs: exit(1) # Store env details as a dictionary, and add to 'all_envs' list env = { - 'name': pio_env, - 'platform': env_platform, - 'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None), - 'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False)) + "ci": {"board": pio_env, "platform": env_platform}, + "board_level": cfg.get(f"env:{pio_env}", "board_level", default=None), + "board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)), } all_envs.append(env) # Filter outputs based on options # Check is mutually exclusive with other options (except 'pr') -if "check" in options: +if "check" in args.platform: for env in all_envs: - if env['board_check']: - if "pr" in options: - if env['board_level'] == 'pr': - outlist.append(env['name']) + if env["board_check"]: + if "pr" in args.level: + if env["board_level"] == "pr": + outlist.append(env["ci"]) else: - outlist.append(env['name']) + outlist.append(env["ci"]) # Filter (non-check) builds by platform else: for env in all_envs: - if options[0] == env['platform']: + if args.platform == env["ci"]["platform"] or args.platform == "all": # Always include board_level = 'pr' - if env['board_level'] == 'pr': - outlist.append(env['name']) + if env["board_level"] == "pr": + outlist.append(env["ci"]) # Include board_level = 'extra' when requested - elif "extra" in options and env['board_level'] == "extra": - outlist.append(env['name']) + elif "extra" in args.level and env["board_level"] == "extra": + outlist.append(env["ci"]) # If no board level is specified, include in release builds (not PR) - elif "pr" not in options and not env['board_level']: - outlist.append(env['name']) + elif "pr" not in args.level and not env["board_level"]: + outlist.append(env["ci"]) # Return as a JSON list print(json.dumps(outlist)) diff --git a/bin/kill-github-actions.sh b/bin/kill-github-actions.sh new file mode 100755 index 000000000..f71047c5e --- /dev/null +++ b/bin/kill-github-actions.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# Script to cancel all running GitHub Actions workflows +# Requires GitHub CLI (gh) to be installed and authenticated + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if gh CLI is installed +if ! command -v gh &> /dev/null; then + print_error "GitHub CLI (gh) is not installed. Please install it first:" + echo " brew install gh" + echo " Or visit: https://cli.github.com/" + exit 1 +fi + +# Check if authenticated +if ! gh auth status &> /dev/null; then + print_error "GitHub CLI is not authenticated. Please run:" + echo " gh auth login" + exit 1 +fi + +# Get repository info +REPO=$(gh repo view --json owner,name -q '.owner.login + "/" + .name') +if [[ -z "$REPO" ]]; then + print_error "Could not determine repository. Make sure you're in a GitHub repository." + exit 1 +fi + +print_status "Working with repository: $REPO" + +# Get all active workflows (both queued and in-progress) +print_status "Fetching active workflows (queued and in-progress)..." +QUEUED_WORKFLOWS=$(gh run list --status queued --json databaseId,displayTitle,headBranch,status --limit 100) +IN_PROGRESS_WORKFLOWS=$(gh run list --status in_progress --json databaseId,displayTitle,headBranch,status --limit 100) + +# Combine both lists +ALL_WORKFLOWS=$(echo "$QUEUED_WORKFLOWS $IN_PROGRESS_WORKFLOWS" | jq -s 'add | unique_by(.databaseId)') + +if [[ "$ALL_WORKFLOWS" == "[]" ]]; then + print_status "No active workflows found." + exit 0 +fi + +# Parse and display active workflows +echo +print_warning "Found active workflows:" +echo "$ALL_WORKFLOWS" | jq -r '.[] | " - \(.displayTitle) (Branch: \(.headBranch), Status: \(.status), ID: \(.databaseId))"' + +echo +read -p "Do you want to cancel ALL these workflows? (y/N): " -n 1 -r +echo + +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_status "Cancelled by user." + exit 0 +fi + +# Cancel each workflow +print_status "Cancelling workflows..." +CANCELLED_COUNT=0 +FAILED_COUNT=0 + +while IFS= read -r WORKFLOW_ID; do + if [[ -n "$WORKFLOW_ID" ]]; then + print_status "Cancelling workflow ID: $WORKFLOW_ID" + if gh run cancel "$WORKFLOW_ID" 2>/dev/null; then + ((CANCELLED_COUNT++)) + else + print_error "Failed to cancel workflow ID: $WORKFLOW_ID" + ((FAILED_COUNT++)) + fi + fi +done < <(echo "$ALL_WORKFLOWS" | jq -r '.[].databaseId') + +echo +print_status "Summary:" +echo " - Cancelled: $CANCELLED_COUNT workflows" +if [[ $FAILED_COUNT -gt 0 ]]; then + echo " - Failed: $FAILED_COUNT workflows" +fi + +print_status "Done!" + +# Optional: Show remaining active workflows +echo +print_status "Checking for any remaining active workflows..." +REMAINING_QUEUED=$(gh run list --status queued --json databaseId --limit 10) +REMAINING_IN_PROGRESS=$(gh run list --status in_progress --json databaseId --limit 10) +REMAINING_ALL=$(echo "$REMAINING_QUEUED $REMAINING_IN_PROGRESS" | jq -s 'add | unique_by(.databaseId)') + +if [[ "$REMAINING_ALL" == "[]" ]]; then + print_status "All workflows successfully cancelled." +else + REMAINING_COUNT=$(echo "$REMAINING_ALL" | jq '. | length') + print_warning "Still $REMAINING_COUNT workflows active (may take a moment to update status)" +fi \ No newline at end of file diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index 3505f1940..6fc5e8597 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,9 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12 diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index e54d1586f..4a1887d9d 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -86,7 +86,7 @@ if platform.name == "espressif32": if platform.name == "nordicnrf52": env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", - env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2", + env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py \"$BUILD_DIR/firmware.hex\" -c -f 0xADA52840 -o \"$BUILD_DIR/firmware.uf2\"", "Generating UF2 file")) Import("projenv") diff --git a/bin/web.version b/bin/web.version index e46a05b19..ba5c9fca6 100644 --- a/bin/web.version +++ b/bin/web.version @@ -1 +1 @@ -2.6.4 \ No newline at end of file +2.6.7 \ No newline at end of file diff --git a/boards/heltec_wireless_tracker_v2.json b/boards/heltec_wireless_tracker_v2.json new file mode 100644 index 000000000..502954e69 --- /dev/null +++ b/boards/heltec_wireless_tracker_v2.json @@ -0,0 +1,37 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "heltec_wireless_tracker_v2" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Wireless Tracker V2", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org", + "vendor": "Heltec" +} diff --git a/debian/changelog b/debian/changelog index 8bd053b25..e124f8929 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.13.0) unstable; urgency=medium + + * Version 2.7.13 + + -- GitHub Actions Sat, 11 Oct 2025 15:27:28 +0000 + meshtasticd (2.7.12.0) unstable; urgency=medium [ Austin Lane ] diff --git a/platformio.ini b/platformio.ini index f6c0f3867..7c63ad7ad 100644 --- a/platformio.ini +++ b/platformio.ini @@ -70,7 +70,7 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip # renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master - https://github.com/meshtastic/ArduinoThread/archive/7c3ee9e1951551b949763b1f5280f8db1fa4068d.zip + https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip # renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb nanopb/Nanopb@0.4.91 # renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32 @@ -120,7 +120,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip + https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.zip ; Common libs for environmental measurements in telemetry module [environmental_base] @@ -164,7 +164,7 @@ lib_deps = # renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass mprograms/QMC5883LCompass@1.2.3 # renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU - dfrobot/DFRobot_RTU@1.0.3 + dfrobot/DFRobot_RTU@1.0.6 # renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip # renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226 diff --git a/protobufs b/protobufs index 60c3e6600..bf149bbdc 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 60c3e6600a2f4e6f49e45aeb47aafd8291a0015c +Subproject commit bf149bbdcce45ba7cd8643db7cb25e5c8815072b diff --git a/src/Power.cpp b/src/Power.cpp index 7de82b8d6..1f4c341f0 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -562,6 +562,7 @@ class AnalogBatteryLevel : public HasBatteryLevel config.power.device_battery_ina_address) { if (!ina226Sensor.isInitialized()) return ina226Sensor.runOnce() > 0; + return ina226Sensor.isRunning(); } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first == config.power.device_battery_ina_address) { if (!ina260Sensor.isInitialized()) @@ -691,6 +692,16 @@ bool Power::setup() #ifdef NRF_APM found = true; #endif +#ifdef EXT_PWR_DETECT + attachInterrupt( + EXT_PWR_DETECT, + []() { + power->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + }, + CHANGE); +#endif enabled = found; low_voltage_counter = 0; diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 51dbcb7be..fad0fb92f 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -86,7 +86,7 @@ int32_t SerialConsole::runOnce() #endif int32_t delay = runOncePart(); -#if defined(SERIAL_HAS_ON_RECEIVE) +#if defined(SERIAL_HAS_ON_RECEIVE) || defined(CONFIG_IDF_TARGET_ESP32S2) return Port.available() ? delay : INT32_MAX; #elif defined(IS_USB_SERIAL) return HWCDC::isPlugged() ? delay : (1000 * 20); diff --git a/src/configuration.h b/src/configuration.h index 91181890b..baf24a636 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -33,6 +33,9 @@ along with this program. If not, see . #include "pcf8563.h" #endif +/* Offer chance for variant-specific defines */ +#include "variant.h" + // ----------------------------------------------------------------------------- // Version // ----------------------------------------------------------------------------- @@ -117,12 +120,17 @@ along with this program. If not, see . #define SX126X_MAX_POWER 22 #endif -#ifdef HELTEC_V4 +#ifdef USE_GC1109_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 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7 #endif +#ifdef RAK13302 +#define NUM_PA_POINTS 22 +#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8 +#endif + // Default system gain to 0 if not defined #ifndef TX_GAIN_LORA #define TX_GAIN_LORA 0 @@ -260,9 +268,6 @@ along with this program. If not, see . // convert 24-bit color to 16-bit (56K) #define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)) -/* Step #1: offer chance for variant-specific defines */ -#include "variant.h" - #if defined(VEXT_ENABLE) && !defined(VEXT_ON_VALUE) // Older variant.h files might not be defining this value, so stay with the old default #define VEXT_ON_VALUE LOW diff --git a/src/detect/ScanI2CConsumer.cpp b/src/detect/ScanI2CConsumer.cpp new file mode 100644 index 000000000..a70fa5398 --- /dev/null +++ b/src/detect/ScanI2CConsumer.cpp @@ -0,0 +1,16 @@ +#include "ScanI2CConsumer.h" +#include + +static std::forward_list ScanI2CConsumers; + +ScanI2CConsumer::ScanI2CConsumer() +{ + ScanI2CConsumers.push_front(this); +} + +void ScanI2CCompleted(ScanI2C *i2cScanner) +{ + for (ScanI2CConsumer *consumer : ScanI2CConsumers) { + consumer->i2cScanFinished(i2cScanner); + } +} \ No newline at end of file diff --git a/src/detect/ScanI2CConsumer.h b/src/detect/ScanI2CConsumer.h new file mode 100644 index 000000000..fd97f7edc --- /dev/null +++ b/src/detect/ScanI2CConsumer.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ScanI2C.h" +#include + +class ScanI2CConsumer +{ + public: + ScanI2CConsumer(); + virtual void i2cScanFinished(ScanI2C *i2cScanner) = 0; +}; + +void ScanI2CCompleted(ScanI2C *i2cScanner); \ No newline at end of file diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 6df3f8be1..da2a57fee 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -378,7 +378,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); - if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c || registerValue == 0xc8d) { + if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c || + registerValue == 0xc8d) { type = SHT4X; logFoundDevice("SHT4X", (uint8_t)addr.address); } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { @@ -580,7 +581,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) scanPort(port, nullptr, 0); } -TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const +TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) { if (address.port == ScanI2C::I2CPort::WIRE) { return &Wire; diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index 6988091ad..c5b791920 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -23,12 +23,12 @@ class ScanI2CTwoWire : public ScanI2C ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override; - TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const; - bool exists(ScanI2C::DeviceType) const override; size_t countDevices() const override; + static TwoWire *fetchI2CBus(ScanI2C::DeviceAddress); + protected: FoundDevice firstOfOrNONE(size_t, DeviceType[]) const override; diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 0487d1fea..297ed3dfa 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -494,22 +494,10 @@ bool GPS::setup() if (!didSerialInit) { int msglen = 0; if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) { -#ifdef TRACKER_T1000_E - // add power up/down strategy, improve ag3335 detection success - digitalWrite(PIN_GPS_EN, LOW); - delay(500); - digitalWrite(GPS_VRTC_EN, LOW); - delay(1000); - digitalWrite(GPS_VRTC_EN, HIGH); - delay(500); - digitalWrite(PIN_GPS_EN, HIGH); - delay(1000); -#endif if (probeTries < GPS_PROBETRIES) { - LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]); gnssModel = probe(serialSpeeds[speedSelect]); if (gnssModel == GNSS_MODEL_UNKNOWN) { - if (++speedSelect == array_count(serialSpeeds)) { + if (currentStep == 0 && ++speedSelect == array_count(serialSpeeds)) { speedSelect = 0; ++probeTries; } @@ -518,10 +506,9 @@ bool GPS::setup() // Rare Serial Speeds #ifndef CONFIG_IDF_TARGET_ESP32C6 if (probeTries == GPS_PROBETRIES) { - LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]); gnssModel = probe(rareSerialSpeeds[speedSelect]); if (gnssModel == GNSS_MODEL_UNKNOWN) { - if (++speedSelect == array_count(rareSerialSpeeds)) { + if (currentStep == 0 && ++speedSelect == array_count(rareSerialSpeeds)) { LOG_WARN("Give up on GPS probe and set to %d", GPS_BAUDRATE); return true; } @@ -1033,7 +1020,7 @@ void GPS::down() LOG_DEBUG("%us until next search", sleepTime / 1000); // If update interval less than 10 seconds, no attempt to sleep - if (updateInterval <= 10 * 1000UL || sleepTime == 0) + if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS || sleepTime == 0) setPowerState(GPS_IDLE); else { @@ -1094,7 +1081,7 @@ int32_t GPS::runOnce() return disable(); } if (!setup()) - return 2000; // Setup failed, re-run in two seconds + return currentDelay; // Setup failed, re-run in two seconds // We have now loaded our saved preferences from flash if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { @@ -1104,6 +1091,29 @@ int32_t GPS::runOnce() publishUpdate(); } + // ======================== GPS_ACTIVE state ======================== + // In GPS_ACTIVE state, GPS is powered on and we're receiving NMEA messages. + // We use the following logic to determine when to update the local position + // or time by running GPS::publishUpdate. + // Note: Local position update is asynchronous to position broadcast. We + // generally run this state every gps_update_interval seconds, and in most cases + // gps_update_interval is faster than the position broadcast interval so there's a + // fresh position ready when the device wants to broadcast one on the mesh. + // + // 1. Got a time for the first time --> set the time, don't publish. + // 2. Got a lock for the first time + // --> If gps_update_interval is <= 10s --> publishUpdate + // --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s) + // 3. Got a lock after turning back on + // --> If gps_update_interval is <= 10s --> publishUpdate + // --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s) + // 4. Hold has expired + // --> If we have a time and a location --> publishUpdate + // --> down() + // 5. Search time has expired + // --> If we have a time and a location --> publishUpdate + // --> If we had a location before but don't now --> publishUpdate + // --> down() if (whileActive()) { // if we have received valid NMEA claim we are connected setConnected(); @@ -1113,55 +1123,81 @@ int32_t GPS::runOnce() if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue()) up(); - // If we've already set time from the GPS, no need to ask the GPS - bool gotTime = (getRTCQuality() >= RTCQualityGPS); - if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time - gotTime = true; - shouldPublish = true; - } - + // quality of the previous fix. We set it to 0 when we go down, so it's a way + // to check if we're getting a lock after being GPS_OFF. uint8_t prev_fixQual = fixQual; - bool gotLoc = lookForLocation(); - if (gotLoc && !hasValidLocation) { // declare that we have location ASAP - LOG_DEBUG("hasValidLocation RISING EDGE"); - hasValidLocation = true; - shouldPublish = true; - // Hold for 20secs after getting a lock to download ephemeris etc - fixHoldEnds = millis() + 20000; - } - if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on. - fixHoldEnds = millis() + 20000; - shouldPublish = true; // Publish immediately, since next publish is at end of hold - } + if (powerState == GPS_ACTIVE) { + // if gps_update_interval is <=10s, GPS never goes off, so we treat that differently + uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval); - bool tooLong = scheduling.searchedTooLong(); - if (tooLong) - LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time"); + // 1. Got a time for the first time + bool gotTime = (getRTCQuality() >= RTCQualityGPS); + if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time + gotTime = true; + } - // Once we get a location we no longer desperately want an update - if ((gotLoc && gotTime) || tooLong) { + // 2. Got a lock for the first time, or 3. Got a lock after turning back on + bool gotLoc = lookForLocation(); + if (gotLoc) { +#ifdef GPS_DEBUG + if (!hasValidLocation) { // declare that we have location ASAP + LOG_DEBUG("hasValidLocation RISING EDGE"); + } +#endif + if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS) { + hasValidLocation = true; + shouldPublish = true; + } else if (!hasValidLocation || prev_fixQual == 0 || (fixHoldEnds + GPS_THREAD_INTERVAL) < millis()) { + hasValidLocation = true; + // Hold for up to 20secs after getting a lock to download ephemeris etc + uint32_t holdTime = updateInterval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS; + if (holdTime > GPS_FIX_HOLD_MAX_MS) + holdTime = GPS_FIX_HOLD_MAX_MS; + fixHoldEnds = millis() + holdTime; +#ifdef GPS_DEBUG + LOG_DEBUG("Holding for %ums after lock", holdTime); +#endif + } + } + + bool tooLong = scheduling.searchedTooLong(); if (tooLong && !gotLoc) { + LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time"); // we didn't get a location during this ack window, therefore declare loss of lock if (hasValidLocation) { - LOG_DEBUG("hasValidLocation FALLING EDGE"); - } - p = meshtastic_Position_init_default; - hasValidLocation = false; - } - if (millis() > fixHoldEnds) { - shouldPublish = true; // publish our update at the end of the lock hold - publishUpdate(); - down(); + p = meshtastic_Position_init_default; + hasValidLocation = false; + shouldPublish = true; #ifdef GPS_DEBUG - } else { + LOG_DEBUG("hasValidLocation FALLING EDGE"); +#endif + } + } + + // Hold has expired , Search time has expired, we got a time only, or we never needed to hold. + bool holdExpired = (fixHoldEnds != 0 && millis() > fixHoldEnds); + if (shouldPublish || tooLong || holdExpired) { + if (gotTime && hasValidLocation) { + shouldPublish = true; + } + if (shouldPublish) { + fixHoldEnds = 0; + publishUpdate(); + } + + // There's a chance we just got a time, so keep going to see if we can get a location too + if (tooLong || holdExpired) { + down(); + } + +#ifdef GPS_DEBUG + } else if (fixHoldEnds != 0) { LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view); #endif } } - - // If state has changed do a publish - publishUpdate(); + // ===================== end GPS_ACTIVE state ======================== if (config.position.fixed_position == true && hasValidLocation) return disable(); // This should trigger when we have a fixed position, and get that first position @@ -1218,163 +1254,197 @@ static const char *DETECTED_MESSAGE = "%s detected"; GnssModel_t GPS::probe(int serialSpeed) { + uint8_t buffer[768] = {0}; + + switch (currentStep) { + case 0: { #if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) - _serial_gps->end(); - _serial_gps->begin(serialSpeed); + _serial_gps->end(); + _serial_gps->begin(serialSpeed); #elif defined(ARCH_RP2040) - _serial_gps->end(); - _serial_gps->setFIFOSize(256); - _serial_gps->begin(serialSpeed); + _serial_gps->end(); + _serial_gps->setFIFOSize(256); + _serial_gps->begin(serialSpeed); #else - if (_serial_gps->baudRate() != serialSpeed) { - LOG_DEBUG("Set Baud to %i", serialSpeed); - _serial_gps->updateBaudRate(serialSpeed); - } + if (_serial_gps->baudRate() != serialSpeed) { + LOG_DEBUG("Set GPS Baud to %i", serialSpeed); + _serial_gps->updateBaudRate(serialSpeed); + } #endif - memset(&ublox_info, 0, sizeof(ublox_info)); - uint8_t buffer[768] = {0}; - delay(100); + memset(&ublox_info, 0, sizeof(ublox_info)); + delay(100); - // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices) - _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); - delay(20); - // Close NMEA sequences on Ublox - _serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n"); - _serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n"); - _serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"); - delay(20); - // Close NMEA sequences on CM121 - _serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n"); - _serial_gps->write("$CFGMSG,0,2,0,1*18\r\n"); - _serial_gps->write("$CFGMSG,0,3,0,1*19\r\n"); - delay(20); - - // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121 - std::vector unicore = { - {"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}}; - PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500); - - std::vector atgm = { - {"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H}, - /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */ - {"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}}; - PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500); - - /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */ - _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume - _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume - _serial_gps->write("$PAIR513*3D\r\n"); // save configuration - std::vector airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335}, - {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352}, - {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}}; - PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000); - - PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500); - PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500); - - // Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms - _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n"); - delay(20); - std::vector mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D}, - {"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B}, - {"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B}, - {"L80", "_3339_", GNSS_MODEL_MTK_L76B}}; - - PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500); - - uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00}; - UBXChecksum(cfg_rate, sizeof(cfg_rate)); - clearBuffer(); - _serial_gps->write(cfg_rate, sizeof(cfg_rate)); - // Check that the returned response class and message ID are correct - GPS_RESPONSE response = getACK(0x06, 0x08, 750); - if (response == GNSS_RESPONSE_NONE) { - LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed); + // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices) + _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); + delay(20); + // Close NMEA sequences on Ublox + _serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n"); + _serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n"); + _serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"); + delay(20); + // Close NMEA sequences on CM121 + _serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n"); + _serial_gps->write("$CFGMSG,0,2,0,1*18\r\n"); + _serial_gps->write("$CFGMSG,0,3,0,1*19\r\n"); + currentDelay = 20; + currentStep = 1; return GNSS_MODEL_UNKNOWN; - } else if (response == GNSS_RESPONSE_FRAME_ERRORS) { - LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed); } + case 1: { - memset(buffer, 0, sizeof(buffer)); - uint8_t _message_MONVER[8] = { - 0xB5, 0x62, // Sync message for UBX protocol - 0x0A, 0x04, // Message class and ID (UBX-MON-VER) - 0x00, 0x00, // Length of payload (we're asking for an answer, so no payload) - 0x00, 0x00 // Checksum - }; - // Get Ublox gnss module hardware and software info - UBXChecksum(_message_MONVER, sizeof(_message_MONVER)); - clearBuffer(); - _serial_gps->write(_message_MONVER, sizeof(_message_MONVER)); + // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121 + std::vector unicore = { + {"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}}; + PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500); + currentDelay = 20; + currentStep = 2; + return GNSS_MODEL_UNKNOWN; + } + case 2: { + std::vector atgm = { + {"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H}, + /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */ + {"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}}; + PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500); + currentDelay = 20; + currentStep = 3; + return GNSS_MODEL_UNKNOWN; + } + case 3: { + /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */ + _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume + _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume + _serial_gps->write("$PAIR513*3D\r\n"); // save configuration + std::vector airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335}, + {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352}, + {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}}; + PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000); + currentDelay = 20; + currentStep = 4; + return GNSS_MODEL_UNKNOWN; + } + case 4: { + PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500); + PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500); + currentDelay = 20; + currentStep = 5; + return GNSS_MODEL_UNKNOWN; + } + case 5: { - uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200); - if (len) { - uint16_t position = 0; - for (int i = 0; i < 30; i++) { - ublox_info.swVersion[i] = buffer[position]; - position++; - } - for (int i = 0; i < 10; i++) { - ublox_info.hwVersion[i] = buffer[position]; - position++; - } + // Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms + _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n"); + delay(20); + std::vector mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D}, + {"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B}, + {"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B}, + {"L80", "_3339_", GNSS_MODEL_MTK_L76B}}; - while (len >= position + 30) { - for (int i = 0; i < 30; i++) { - ublox_info.extension[ublox_info.extensionNo][i] = buffer[position]; - position++; - } - ublox_info.extensionNo++; - if (ublox_info.extensionNo > 9) - break; - } - - LOG_DEBUG("Module Info : "); - LOG_DEBUG("Soft version: %s", ublox_info.swVersion); - LOG_DEBUG("Hard version: %s", ublox_info.hwVersion); - LOG_DEBUG("Extensions:%d", ublox_info.extensionNo); - for (int i = 0; i < ublox_info.extensionNo; i++) { - LOG_DEBUG(" %s", ublox_info.extension[i]); + PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500); + currentDelay = 20; + currentStep = 6; + return GNSS_MODEL_UNKNOWN; + } + case 6: { + uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00}; + UBXChecksum(cfg_rate, sizeof(cfg_rate)); + clearBuffer(); + _serial_gps->write(cfg_rate, sizeof(cfg_rate)); + // Check that the returned response class and message ID are correct + GPS_RESPONSE response = getACK(0x06, 0x08, 750); + if (response == GNSS_RESPONSE_NONE) { + LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed); + currentDelay = 2000; + currentStep = 0; + return GNSS_MODEL_UNKNOWN; + } else if (response == GNSS_RESPONSE_FRAME_ERRORS) { + LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed); } memset(buffer, 0, sizeof(buffer)); + uint8_t _message_MONVER[8] = { + 0xB5, 0x62, // Sync message for UBX protocol + 0x0A, 0x04, // Message class and ID (UBX-MON-VER) + 0x00, 0x00, // Length of payload (we're asking for an answer, so no payload) + 0x00, 0x00 // Checksum + }; + // Get Ublox gnss module hardware and software info + UBXChecksum(_message_MONVER, sizeof(_message_MONVER)); + clearBuffer(); + _serial_gps->write(_message_MONVER, sizeof(_message_MONVER)); - // tips: extensionNo field is 0 on some 6M GNSS modules - for (int i = 0; i < ublox_info.extensionNo; ++i) { - if (!strncmp(ublox_info.extension[i], "MOD=", 4)) { - strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer)); - } else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) { - char *ptr = nullptr; - memset(buffer, 0, sizeof(buffer)); - strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer)); - LOG_DEBUG("Protocol Version:%s", (char *)buffer); - if (strlen((char *)buffer)) { - ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10); - LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version); - } else { - ublox_info.protocol_version = 0; + uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200); + if (len) { + uint16_t position = 0; + for (int i = 0; i < 30; i++) { + ublox_info.swVersion[i] = buffer[position]; + position++; + } + for (int i = 0; i < 10; i++) { + ublox_info.hwVersion[i] = buffer[position]; + position++; + } + + while (len >= position + 30) { + for (int i = 0; i < 30; i++) { + ublox_info.extension[ublox_info.extensionNo][i] = buffer[position]; + position++; + } + ublox_info.extensionNo++; + if (ublox_info.extensionNo > 9) + break; + } + + LOG_DEBUG("Module Info : "); + LOG_DEBUG("Soft version: %s", ublox_info.swVersion); + LOG_DEBUG("Hard version: %s", ublox_info.hwVersion); + LOG_DEBUG("Extensions:%d", ublox_info.extensionNo); + for (int i = 0; i < ublox_info.extensionNo; i++) { + LOG_DEBUG(" %s", ublox_info.extension[i]); + } + + memset(buffer, 0, sizeof(buffer)); + + // tips: extensionNo field is 0 on some 6M GNSS modules + for (int i = 0; i < ublox_info.extensionNo; ++i) { + if (!strncmp(ublox_info.extension[i], "MOD=", 4)) { + strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer)); + } else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) { + char *ptr = nullptr; + memset(buffer, 0, sizeof(buffer)); + strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer)); + LOG_DEBUG("Protocol Version:%s", (char *)buffer); + if (strlen((char *)buffer)) { + ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10); + LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version); + } else { + ublox_info.protocol_version = 0; + } } } - } - if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6"); - return GNSS_MODEL_UBLOX6; - } else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7"); - return GNSS_MODEL_UBLOX7; - } else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8"); - return GNSS_MODEL_UBLOX8; - } else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9"); - return GNSS_MODEL_UBLOX9; - } else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10"); - return GNSS_MODEL_UBLOX10; + if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6"); + return GNSS_MODEL_UBLOX6; + } else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7"); + return GNSS_MODEL_UBLOX7; + } else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8"); + return GNSS_MODEL_UBLOX8; + } else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9"); + return GNSS_MODEL_UBLOX9; + } else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10"); + return GNSS_MODEL_UBLOX10; + } } } + } LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed); + currentDelay = 2000; + currentStep = 0; return GNSS_MODEL_UNKNOWN; } diff --git a/src/gps/GPS.h b/src/gps/GPS.h index cba767460..8ba1ce0a6 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -16,6 +16,9 @@ #define GPS_EN_ACTIVE 1 #endif +static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL; +static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000; + typedef enum { GNSS_MODEL_ATGM336H, GNSS_MODEL_MTK, @@ -151,6 +154,8 @@ class GPS : private concurrency::OSThread TinyGPSPlus reader; uint8_t fixQual = 0; // fix quality from GPGGA uint32_t lastChecksumFailCount = 0; + uint8_t currentStep = 0; + int32_t currentDelay = 2000; #ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS // (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field @@ -173,8 +178,6 @@ class GPS : private concurrency::OSThread */ bool hasValidLocation = false; // default to false, until we complete our first read - bool isInPowersave = false; - bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop() bool hasGPS = false; // Do we have a GPS we are talking to diff --git a/src/graphics/Panel_sdl.cpp b/src/graphics/Panel_sdl.cpp new file mode 100644 index 000000000..bad6072f9 --- /dev/null +++ b/src/graphics/Panel_sdl.cpp @@ -0,0 +1,687 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) + +Porting for SDL: + [imliubo](https://github.com/imliubo) +/----------------------------------------------------------------------------*/ +#include "Panel_sdl.hpp" + +#if defined(SDL_h_) + +// #include "../common.hpp" +// #include "../../misc/common_function.hpp" +// #include "../../Bus.hpp" + +#include +#include +#include +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace lgfx +{ +inline namespace v1 +{ +SDL_Keymod Panel_sdl::_keymod = KMOD_NONE; +static SDL_semaphore *_update_in_semaphore = nullptr; +static SDL_semaphore *_update_out_semaphore = nullptr; +volatile static uint32_t _in_step_exec = 0; +volatile static uint32_t _msec_step_exec = 512; +static bool _inited = false; +static bool _all_close = false; + +volatile uint8_t Panel_sdl::_gpio_dummy_values[EMULATED_GPIO_MAX]; + +static inline void *heap_alloc_dma(size_t length) +{ + return malloc(length); +} // aligned_alloc(16, length); +static inline void heap_free(void *buf) +{ + free(buf); +} + +static std::list _list_monitor; + +static monitor_t *const getMonitorByWindowID(uint32_t windowID) +{ + for (auto &m : _list_monitor) { + if (SDL_GetWindowID(m->window) == windowID) { + return m; + } + } + return nullptr; +} +//---------------------------------------------------------------------------- + +static std::vector _key_code_map; + +void Panel_sdl::addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio) +{ + if (gpio > EMULATED_GPIO_MAX) + return; + KeyCodeMapping_t map; + map.keycode = keyCode; + map.gpio = gpio; + _key_code_map.push_back(map); +} + +int Panel_sdl::getKeyCodeMapping(SDL_KeyCode keyCode) +{ + for (const auto &i : _key_code_map) { + if (i.keycode == keyCode) + return i.gpio; + } + return -1; +} + +void Panel_sdl::_event_proc(void) +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) { + auto mon = getMonitorByWindowID(event.button.windowID); + int gpio = -1; + + /// Check key mapping + gpio = getKeyCodeMapping((SDL_KeyCode)event.key.keysym.sym); + if (gpio < 0) { + switch (event.key.keysym.sym) { /// M5StackのBtnA~BtnCのエミュレート; + // case SDLK_LEFT: gpio = 39; break; + // case SDLK_DOWN: gpio = 38; break; + // case SDLK_RIGHT: gpio = 37; break; + // case SDLK_UP: gpio = 36; break; + + /// L/Rキーで画面回転 + case SDLK_r: + case SDLK_l: + if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) { + if (mon != nullptr) { + mon->frame_rotation = (mon->frame_rotation += event.key.keysym.sym == SDLK_r ? 1 : -1); + int x, y, w, h; + SDL_GetWindowSize(mon->window, &w, &h); + SDL_GetWindowPosition(mon->window, &x, &y); + SDL_SetWindowSize(mon->window, h, w); + SDL_SetWindowPosition(mon->window, x + (w - h) / 2, y + (h - w) / 2); + mon->panel->sdl_invalidate(); + } + } + break; + + /// 1~6キーで画面拡大率変更 + case SDLK_1: + case SDLK_2: + case SDLK_3: + case SDLK_4: + case SDLK_5: + case SDLK_6: + if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) { + if (mon != nullptr) { + int size = 1 + (event.key.keysym.sym - SDLK_1); + _update_scaling(mon, size, size); + } + } + break; + default: + continue; + } + } + + if (event.type == SDL_KEYDOWN) { + Panel_sdl::gpio_lo(gpio); + } else { + Panel_sdl::gpio_hi(gpio); + } + } else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) { + auto mon = getMonitorByWindowID(event.button.windowID); + if (mon != nullptr) { + { + int x, y, w, h; + SDL_GetWindowSize(mon->window, &w, &h); + SDL_GetMouseState(&x, &y); + float sf = sinf(mon->frame_angle * M_PI / 180); + float cf = cosf(mon->frame_angle * M_PI / 180); + x -= w / 2.0f; + y -= h / 2.0f; + float nx = y * sf + x * cf; + float ny = y * cf - x * sf; + if (mon->frame_rotation & 1) { + std::swap(w, h); + } + x = (nx * mon->frame_width / w) + (mon->frame_width >> 1); + y = (ny * mon->frame_height / h) + (mon->frame_height >> 1); + mon->touch_x = x - mon->frame_inner_x; + mon->touch_y = y - mon->frame_inner_y; + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) { + mon->touched = true; + } + if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) { + mon->touched = false; + } + } + } else if (event.type == SDL_WINDOWEVENT) { + auto monitor = getMonitorByWindowID(event.window.windowID); + if (monitor) { + if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + int mw, mh; + SDL_GetRendererOutputSize(monitor->renderer, &mw, &mh); + if (monitor->frame_rotation & 1) { + std::swap(mw, mh); + } + monitor->scaling_x = (mw * 2 / monitor->frame_width) / 2.0f; + monitor->scaling_y = (mh * 2 / monitor->frame_height) / 2.0f; + monitor->panel->sdl_invalidate(); + } else if (event.window.event == SDL_WINDOWEVENT_CLOSE) { + monitor->closing = true; + } + } + } else if (event.type == SDL_QUIT) { + for (auto &m : _list_monitor) { + m->closing = true; + } + } + } +} + +/// デバッガでステップ実行されていることを検出するスレッド用関数。 +static int detectDebugger(bool *running) +{ + uint32_t prev_ms = SDL_GetTicks(); + do { + SDL_Delay(1); + uint32_t ms = SDL_GetTicks(); + /// 時間間隔が広すぎる場合はステップ実行中 (ブレークポイントで止まった)と判断する。 + /// また、解除されたと判断した後も1023msecほど状態を維持する。 + if (ms - prev_ms > 64) { + _in_step_exec = _msec_step_exec; + } else if (_in_step_exec) { + --_in_step_exec; + } + prev_ms = ms; + } while (*running); + return 0; +} + +void Panel_sdl::_update_proc(void) +{ + for (auto it = _list_monitor.begin(); it != _list_monitor.end();) { + if ((*it)->closing) { + if ((*it)->texture_frameimage) { + SDL_DestroyTexture((*it)->texture_frameimage); + } + SDL_DestroyTexture((*it)->texture); + SDL_DestroyRenderer((*it)->renderer); + SDL_DestroyWindow((*it)->window); + _list_monitor.erase(it++); + if (_list_monitor.empty()) { + _all_close = true; + return; + } + continue; + } + (*it)->panel->sdl_update(); + ++it; + } +} + +int Panel_sdl::setup(void) +{ + if (_inited) + return 1; + _inited = true; + + /// Add default keycode mapping + /// M5StackのBtnA~BtnCのエミュレート; + addKeyCodeMapping(SDLK_LEFT, 39); + addKeyCodeMapping(SDLK_DOWN, 38); + addKeyCodeMapping(SDLK_RIGHT, 37); + addKeyCodeMapping(SDLK_UP, 36); + + SDL_CreateThread((SDL_ThreadFunction)detectDebugger, "dbg", &_inited); + + _update_in_semaphore = SDL_CreateSemaphore(0); + _update_out_semaphore = SDL_CreateSemaphore(0); + for (size_t pin = 0; pin < EMULATED_GPIO_MAX; ++pin) { + gpio_hi(pin); + } + /*Initialize the SDL*/ + SDL_Init(SDL_INIT_VIDEO); + SDL_StartTextInput(); + + // SDL_SetThreadPriority(SDL_ThreadPriority::SDL_THREAD_PRIORITY_HIGH); + return 0; +} + +int Panel_sdl::loop(void) +{ + if (!_inited) + return 1; + + _event_proc(); + SDL_SemWaitTimeout(_update_in_semaphore, 1); + _update_proc(); + _event_proc(); + if (SDL_SemValue(_update_out_semaphore) == 0) { + SDL_SemPost(_update_out_semaphore); + } + + return _all_close; +} + +int Panel_sdl::close(void) +{ + if (!_inited) + return 1; + _inited = false; + + SDL_StopTextInput(); + SDL_DestroySemaphore(_update_in_semaphore); + SDL_DestroySemaphore(_update_out_semaphore); + SDL_Quit(); + return 0; +} + +int Panel_sdl::main(int (*fn)(bool *), uint32_t msec_step_exec) +{ + _msec_step_exec = msec_step_exec; + + /// SDLの準備 + if (0 != Panel_sdl::setup()) { + return 1; + } + + /// ユーザコード関数の動作・停止フラグ + bool running = true; + + /// ユーザコード関数を起動する + auto thread = SDL_CreateThread((SDL_ThreadFunction)fn, "fn", &running); + + /// 全部のウィンドウが閉じられるまでSDLのイベント・描画処理を継続 + while (0 == Panel_sdl::loop()) { + }; + + /// ユーザコード関数を終了する + running = false; + SDL_WaitThread(thread, nullptr); + + /// SDLを終了する + return Panel_sdl::close(); +} + +void Panel_sdl::setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y) +{ + monitor.scaling_x = scaling_x; + monitor.scaling_y = scaling_y; +} + +void Panel_sdl::setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y) +{ + monitor.frame_image = frame_image; + monitor.frame_width = frame_width; + monitor.frame_height = frame_height; + monitor.frame_inner_x = inner_x; + monitor.frame_inner_y = inner_y; +} + +void Panel_sdl::setFrameRotation(uint_fast16_t frame_rotation) +{ + monitor.frame_rotation = frame_rotation; + monitor.frame_angle = (monitor.frame_rotation) * 90; +} + +Panel_sdl::~Panel_sdl(void) +{ + _list_monitor.remove(&monitor); + SDL_DestroyMutex(_sdl_mutex); +} + +Panel_sdl::Panel_sdl(void) : Panel_FrameBufferBase() +{ + _sdl_mutex = SDL_CreateMutex(); + _auto_display = true; + monitor.panel = this; +} + +bool Panel_sdl::init(bool use_reset) +{ + initFrameBuffer(_cfg.panel_width * 4, _cfg.panel_height); + bool res = Panel_FrameBufferBase::init(use_reset); + + _list_monitor.push_back(&monitor); + + return res; +} + +color_depth_t Panel_sdl::setColorDepth(color_depth_t depth) +{ + auto bits = depth & color_depth_t::bit_mask; + if (bits >= 16) { + depth = (bits > 16) ? rgb888_3Byte : rgb565_2Byte; + } else { + depth = (depth == color_depth_t::grayscale_8bit) ? grayscale_8bit : rgb332_1Byte; + } + _write_depth = depth; + _read_depth = depth; + + return depth; +} + +Panel_sdl::lock_t::lock_t(Panel_sdl *parent) : _parent{parent} +{ + SDL_LockMutex(parent->_sdl_mutex); +}; + +Panel_sdl::lock_t::~lock_t(void) +{ + ++_parent->_modified_counter; + SDL_UnlockMutex(_parent->_sdl_mutex); + if (SDL_SemValue(_update_in_semaphore) < 2) { + SDL_SemPost(_update_in_semaphore); + if (!_in_step_exec) { + SDL_SemWaitTimeout(_update_out_semaphore, 1); + } + } +}; + +void Panel_sdl::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) +{ + lock_t lock(this); + Panel_FrameBufferBase::drawPixelPreclipped(x, y, rawcolor); +} + +void Panel_sdl::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) +{ + lock_t lock(this); + Panel_FrameBufferBase::writeFillRectPreclipped(x, y, w, h, rawcolor); +} + +void Panel_sdl::writeBlock(uint32_t rawcolor, uint32_t length) +{ + // lock_t lock(this); + Panel_FrameBufferBase::writeBlock(rawcolor, length); +} + +void Panel_sdl::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param, bool use_dma) +{ + lock_t lock(this); + Panel_FrameBufferBase::writeImage(x, y, w, h, param, use_dma); +} + +void Panel_sdl::writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param) +{ + lock_t lock(this); + Panel_FrameBufferBase::writeImageARGB(x, y, w, h, param); +} + +void Panel_sdl::writePixels(pixelcopy_t *param, uint32_t len, bool use_dma) +{ + lock_t lock(this); + Panel_FrameBufferBase::writePixels(param, len, use_dma); +} + +void Panel_sdl::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) +{ + (void)x; + (void)y; + (void)w; + (void)h; + if (_in_step_exec) { + if (_display_counter != _modified_counter) { + do { + SDL_SemPost(_update_in_semaphore); + SDL_SemWaitTimeout(_update_out_semaphore, 1); + } while (_display_counter != _modified_counter); + SDL_Delay(1); + } + } +} + +uint_fast8_t Panel_sdl::getTouchRaw(touch_point_t *tp, uint_fast8_t count) +{ + (void)count; + tp->x = monitor.touch_x; + tp->y = monitor.touch_y; + tp->size = monitor.touched ? 1 : 0; + tp->id = 0; + return monitor.touched; +} + +void Panel_sdl::setWindowTitle(const char *title) +{ + _window_title = title; + if (monitor.window) { + SDL_SetWindowTitle(monitor.window, _window_title); + } +} + +void Panel_sdl::_update_scaling(monitor_t *mon, float sx, float sy) +{ + mon->scaling_x = sx; + mon->scaling_y = sy; + int nw = mon->frame_width; + int nh = mon->frame_height; + if (mon->frame_rotation & 1) { + std::swap(nw, nh); + } + + int x, y, w, h; + int rw, rh; + SDL_GetRendererOutputSize(mon->renderer, &rw, &rh); + SDL_GetWindowSize(mon->window, &w, &h); + nw = nw * sx * w / rw; + nh = nh * sy * h / rh; + SDL_GetWindowPosition(mon->window, &x, &y); + SDL_SetWindowSize(mon->window, nw, nh); + SDL_SetWindowPosition(mon->window, x + (w - nw) / 2, y + (h - nh) / 2); + mon->panel->sdl_invalidate(); +} + +void Panel_sdl::sdl_create(monitor_t *m) +{ + int flag = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; +#if SDL_FULLSCREEN + flag |= SDL_WINDOW_FULLSCREEN; +#endif + + if (m->frame_width < _cfg.panel_width) { + m->frame_width = _cfg.panel_width; + } + if (m->frame_height < _cfg.panel_height) { + m->frame_height = _cfg.panel_height; + } + + int window_width = m->frame_width * m->scaling_x; + int window_height = m->frame_height * m->scaling_y; + int scaling_x = m->scaling_x; + int scaling_y = m->scaling_y; + if (m->frame_rotation & 1) { + std::swap(window_width, window_height); + std::swap(scaling_x, scaling_y); + } + + { + m->window = SDL_CreateWindow(_window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height, + flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/ + } + m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + m->texture = + SDL_CreateTexture(m->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, _cfg.panel_width, _cfg.panel_height); + SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_NONE); + + if (m->frame_image) { + // 枠画像用のサーフェイスを作成 + auto sf = SDL_CreateRGBSurfaceFrom((void *)m->frame_image, m->frame_width, m->frame_height, 32, m->frame_width * 4, + 0xFF000000, 0xFF0000, 0xFF00, 0xFF); + if (sf != nullptr) { + // 枠画像からテクスチャを作成 + m->texture_frameimage = SDL_CreateTextureFromSurface(m->renderer, sf); + SDL_FreeSurface(sf); + } + } + SDL_SetTextureBlendMode(m->texture_frameimage, SDL_BLENDMODE_BLEND); + _update_scaling(m, scaling_x, scaling_y); +} + +void Panel_sdl::sdl_update(void) +{ + if (monitor.renderer == nullptr) { + sdl_create(&monitor); + } + + bool step_exec = _in_step_exec; + + if (_texupdate_counter != _modified_counter) { + pixelcopy_t pc(nullptr, color_depth_t::rgb888_3Byte, _write_depth, false); + if (_write_depth == rgb565_2Byte) { + pc.fp_copy = pixelcopy_t::copy_rgb_fast; + } else if (_write_depth == rgb888_3Byte) { + pc.fp_copy = pixelcopy_t::copy_rgb_fast; + } else if (_write_depth == rgb332_1Byte) { + pc.fp_copy = pixelcopy_t::copy_rgb_fast; + } else if (_write_depth == grayscale_8bit) { + pc.fp_copy = pixelcopy_t::copy_rgb_fast; + } + + if (0 == SDL_LockMutex(_sdl_mutex)) { + _texupdate_counter = _modified_counter; + for (int y = 0; y < _cfg.panel_height; ++y) { + pc.src_x32 = 0; + pc.src_data = _lines_buffer[y]; + pc.fp_copy(&_texturebuf[y * _cfg.panel_width], 0, _cfg.panel_width, &pc); + } + SDL_UnlockMutex(_sdl_mutex); + SDL_UpdateTexture(monitor.texture, nullptr, _texturebuf, _cfg.panel_width * sizeof(rgb888_t)); + } + } + + int angle = monitor.frame_angle; + int target = (monitor.frame_rotation) * 90; + angle = (((target * 4) + (angle * 4) + (angle < target ? 8 : 0)) >> 3); + + if (monitor.frame_angle != angle) { // 表示する向きを変える + monitor.frame_angle = angle; + sdl_invalidate(); + } else if (monitor.frame_rotation & ~3u) { + monitor.frame_rotation &= 3; + monitor.frame_angle = (monitor.frame_rotation) * 90; + sdl_invalidate(); + } + + if (_invalidated || (_display_counter != _texupdate_counter)) { + SDL_RendererInfo info; + if (0 == SDL_GetRendererInfo(monitor.renderer, &info)) { + // ステップ実行中はVSYNCを待機しない + if (((bool)(info.flags & SDL_RENDERER_PRESENTVSYNC)) == step_exec) { + SDL_RenderSetVSync(monitor.renderer, !step_exec); + } + } + { + int red = 0; + int green = 0; + int blue = 0; +#if defined(M5GFX_BACK_COLOR) + red = ((M5GFX_BACK_COLOR) >> 16) & 0xFF; + green = ((M5GFX_BACK_COLOR) >> 8) & 0xFF; + blue = ((M5GFX_BACK_COLOR)) & 0xFF; +#endif + SDL_SetRenderDrawColor(monitor.renderer, red, green, blue, 0xFF); + } + SDL_RenderClear(monitor.renderer); + if (_invalidated) { + _invalidated = false; + int mw, mh; + SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh); + } + render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height, angle); + render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle); + SDL_RenderPresent(monitor.renderer); + _display_counter = _texupdate_counter; + if (_invalidated) { + _invalidated = false; + SDL_SetRenderDrawColor(monitor.renderer, 0, 0, 0, 0xFF); + SDL_RenderClear(monitor.renderer); + render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height, + angle); + render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle); + SDL_RenderPresent(monitor.renderer); + } + } +} + +void Panel_sdl::render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle) +{ + SDL_Point pivot; + pivot.x = (monitor.frame_width / 2.0f - tx) * (float)monitor.scaling_x; + pivot.y = (monitor.frame_height / 2.0f - ty) * (float)monitor.scaling_y; + SDL_Rect dstrect; + dstrect.w = tw * monitor.scaling_x; + dstrect.h = th * monitor.scaling_y; + int mw, mh; + SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh); + dstrect.x = mw / 2.0f - pivot.x; + dstrect.y = mh / 2.0f - pivot.y; + SDL_RenderCopyEx(monitor.renderer, texture, nullptr, &dstrect, angle, &pivot, SDL_RendererFlip::SDL_FLIP_NONE); +} + +bool Panel_sdl::initFrameBuffer(size_t width, size_t height) +{ + uint8_t **lineArray = (uint8_t **)heap_alloc_dma(height * sizeof(uint8_t *)); + if (nullptr == lineArray) { + return false; + } + + _texturebuf = (rgb888_t *)heap_alloc_dma(width * height * sizeof(rgb888_t)); + + /// 8byte alignment; + width = (width + 7) & ~7u; + + _lines_buffer = lineArray; + memset(lineArray, 0, height * sizeof(uint8_t *)); + + uint8_t *framebuffer = (uint8_t *)heap_alloc_dma(width * height + 16); + + auto fb = framebuffer; + { + for (size_t y = 0; y < height; ++y) { + lineArray[y] = fb; + fb += width; + } + } + return true; +} + +void Panel_sdl::deinitFrameBuffer(void) +{ + auto lines = _lines_buffer; + _lines_buffer = nullptr; + if (lines != nullptr) { + heap_free(lines[0]); + heap_free(lines); + } + if (_texturebuf) { + heap_free(_texturebuf); + _texturebuf = nullptr; + } +} + +//---------------------------------------------------------------------------- +} // namespace v1 +} // namespace lgfx + +#endif diff --git a/src/graphics/Panel_sdl.hpp b/src/graphics/Panel_sdl.hpp new file mode 100644 index 000000000..802c6c5dc --- /dev/null +++ b/src/graphics/Panel_sdl.hpp @@ -0,0 +1,166 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) + +Porting for SDL: + [imliubo](https://github.com/imliubo) +/----------------------------------------------------------------------------*/ +#pragma once + +#define SDL_MAIN_HANDLED +// cppcheck-suppress preprocessorErrorDirective +#if __has_include() +#include +#include +#elif __has_include() +#include +#include +#endif + +#if defined(SDL_h_) +#include "lgfx/v1/Touch.hpp" +#include "lgfx/v1/misc/range.hpp" +#include "lgfx/v1/panel/Panel_FrameBufferBase.hpp" +#include + +namespace lgfx +{ +inline namespace v1 +{ + +struct Panel_sdl; +struct monitor_t { + SDL_Window *window = nullptr; + SDL_Renderer *renderer = nullptr; + SDL_Texture *texture = nullptr; + SDL_Texture *texture_frameimage = nullptr; + Panel_sdl *panel = nullptr; + + // 外枠 + const void *frame_image = 0; + uint_fast16_t frame_width = 0; + uint_fast16_t frame_height = 0; + uint_fast16_t frame_inner_x = 0; + uint_fast16_t frame_inner_y = 0; + int_fast16_t frame_rotation = 0; + int_fast16_t frame_angle = 0; + + float scaling_x = 1; + float scaling_y = 1; + int_fast16_t touch_x, touch_y; + bool touched = false; + bool closing = false; +}; +//---------------------------------------------------------------------------- + +struct Touch_sdl : public ITouch { + bool init(void) override { return true; } + void wakeup(void) override {} + void sleep(void) override {} + bool isEnable(void) override { return true; }; + uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override { return 0; } +}; + +//---------------------------------------------------------------------------- + +struct Panel_sdl : public Panel_FrameBufferBase { + static constexpr size_t EMULATED_GPIO_MAX = 128; + static volatile uint8_t _gpio_dummy_values[EMULATED_GPIO_MAX]; + + public: + Panel_sdl(void); + virtual ~Panel_sdl(void); + + bool init(bool use_reset) override; + + color_depth_t setColorDepth(color_depth_t depth) override; + + void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override; + + // void setInvert(bool invert) override {} + void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override; + void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override; + void writeBlock(uint32_t rawcolor, uint32_t length) override; + void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param, + bool use_dma) override; + void writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param) override; + void writePixels(pixelcopy_t *param, uint32_t len, bool use_dma) override; + + uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override; + + void setWindowTitle(const char *title); + void setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y); + void setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y); + void setFrameRotation(uint_fast16_t frame_rotaion); + void setBrightness(uint8_t brightness) override{}; + + static volatile void gpio_hi(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 1; } + static volatile void gpio_lo(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 0; } + static volatile bool gpio_in(uint32_t pin) { return _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)]; } + + static int setup(void); + static int loop(void); + static int close(void); + + static int main(int (*fn)(bool *), uint32_t msec_step_exec = 512); + + static void setShortcutKeymod(SDL_Keymod keymod) { _keymod = keymod; } + + struct KeyCodeMapping_t { + SDL_KeyCode keycode = SDLK_UNKNOWN; + uint8_t gpio = 0; + }; + static void addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio); + static int getKeyCodeMapping(SDL_KeyCode keyCode); + + protected: + const char *_window_title = "LGFX Simulator"; + SDL_mutex *_sdl_mutex = nullptr; + + void sdl_create(monitor_t *m); + void sdl_update(void); + + touch_point_t _touch_point; + monitor_t monitor; + + rgb888_t *_texturebuf = nullptr; + uint_fast16_t _modified_counter; + uint_fast16_t _texupdate_counter; + uint_fast16_t _display_counter; + bool _invalidated; + + static void _event_proc(void); + static void _update_proc(void); + static void _update_scaling(monitor_t *m, float sx, float sy); + void sdl_invalidate(void) { _invalidated = true; } + void render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle); + bool initFrameBuffer(size_t width, size_t height); + void deinitFrameBuffer(void); + + static SDL_Keymod _keymod; + + struct lock_t { + lock_t(Panel_sdl *parent); + ~lock_t(); + + protected: + Panel_sdl *_parent; + }; +}; +//---------------------------------------------------------------------------- +} // namespace v1 +} // namespace lgfx +#endif \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 4a9e98446..e1cc0ccad 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -453,7 +453,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #endif dispdev->displayOn(); -#ifdef HELTEC_TRACKER_V1_X +#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2) ui->init(); #endif #ifdef USE_ST7789 @@ -1428,6 +1428,9 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) } nodeDB->updateGUI = false; break; + case STATUS_TYPE_POWER: + forceDisplay(true); + break; } return 0; @@ -1487,7 +1490,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) strcpy(banner, "Alert Received"); } screen->showSimpleBanner(banner, 3000); - } else if (!channel.settings.mute) { + } else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) { if (longName && longName[0]) { #if defined(M5STACK_UNITC6L) strcpy(banner, "New Message"); @@ -1503,7 +1506,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) screen->showSimpleBanner(banner, 1500); if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || (isAlert && moduleConfig.external_notification.alert_bell_buzzer) || - (!isBroadcast(packet->to) && isToUs(p))) { + (!isBroadcast(packet->to) && isToUs(packet))) { // Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either // - packet contains an alert and alert bell buzzer is enabled // - packet is a non-broadcast that is addressed to this node diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index dcaa5d69b..8e1299f51 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -1,6 +1,8 @@ -#include "graphics/SharedUIDisplay.h" +#include "configuration.h" +#if HAS_SCREEN #include "RTC.h" #include "graphics/ScreenFonts.h" +#include "graphics/SharedUIDisplay.h" #include "graphics/draw/UIRenderer.h" #include "main.h" #include "meshtastic/config.pb.h" @@ -423,3 +425,4 @@ std::string sanitizeString(const std::string &input) } } // namespace graphics +#endif \ No newline at end of file diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 3eeb17ef0..0663602d9 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -751,10 +751,8 @@ static LGFX *tft = nullptr; static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h #elif ARCH_PORTDUINO +#include "Panel_sdl.hpp" #include // Graphics and font library for ST7735 driver chip -#if defined(LGFX_SDL) -#include -#endif class LGFX : public lgfx::LGFX_Device { @@ -783,10 +781,10 @@ class LGFX : public lgfx::LGFX_Device _panel_instance = new lgfx::Panel_ILI9488; else if (portduino_config.displayPanel == hx8357d) _panel_instance = new lgfx::Panel_HX8357D; -#if defined(LGFX_SDL) - else if (portduino_config.displayPanel == x11) { +#if defined(SDL_h_) + + else if (portduino_config.displayPanel == x11) _panel_instance = new lgfx::Panel_sdl; - } #endif else { _panel_instance = new lgfx::Panel_NULL; @@ -799,8 +797,9 @@ class LGFX : public lgfx::LGFX_Device buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable) - _bus_instance.config(buscfg); // applies the set value to the bus. - _panel_instance->setBus(&_bus_instance); // set the bus on the panel. + _bus_instance.config(buscfg); // applies the set value to the bus. + if (portduino_config.displayPanel != x11) + _panel_instance->setBus(&_bus_instance); // set the bus on the panel. auto cfg = _panel_instance->config(); // Gets a structure for display panel settings. LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight); @@ -848,7 +847,7 @@ class LGFX : public lgfx::LGFX_Device _touch_instance->config(touch_cfg); _panel_instance->setTouch(_touch_instance); } -#if defined(LGFX_SDL) +#if defined(SDL_h_) if (portduino_config.displayPanel == x11) { lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance; sdl_panel_->setup(); @@ -1237,7 +1236,7 @@ void TFTDisplay::display(bool fromBlank) void TFTDisplay::sdlLoop() { -#if defined(LGFX_SDL) +#if defined(SDL_h_) static int lastPressed = 0; static int shuttingDown = false; if (portduino_config.displayPanel == x11) { @@ -1247,27 +1246,26 @@ void TFTDisplay::sdlLoop() InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); } - // debounce - if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed)) + if (lastPressed != 0 && !sdl_panel_->gpio_in(lastPressed)) return; - if (!lgfx::v1::gpio_in(37)) { + if (!sdl_panel_->gpio_in(37)) { lastPressed = 37; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); - } else if (!lgfx::v1::gpio_in(36)) { + } else if (!sdl_panel_->gpio_in(36)) { lastPressed = 36; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); - } else if (!lgfx::v1::gpio_in(38)) { + } else if (!sdl_panel_->gpio_in(38)) { lastPressed = 38; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); - } else if (!lgfx::v1::gpio_in(39)) { + } else if (!sdl_panel_->gpio_in(39)) { lastPressed = 39; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); - } else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) { + } else if (!sdl_panel_->gpio_in(SDL_SCANCODE_KP_ENTER)) { lastPressed = SDL_SCANCODE_KP_ENTER; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); diff --git a/src/graphics/VirtualKeyboard.cpp b/src/graphics/VirtualKeyboard.cpp index 84d5551cb..8062a0338 100644 --- a/src/graphics/VirtualKeyboard.cpp +++ b/src/graphics/VirtualKeyboard.cpp @@ -1,5 +1,6 @@ -#include "VirtualKeyboard.h" #include "configuration.h" +#if HAS_SCREEN +#include "VirtualKeyboard.h" #include "graphics/Screen.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" @@ -736,3 +737,4 @@ bool VirtualKeyboard::isTimedOut() const } } // namespace graphics +#endif \ No newline at end of file diff --git a/src/graphics/draw/CompassRenderer.cpp b/src/graphics/draw/CompassRenderer.cpp index 0e5a1d727..629949ffd 100644 --- a/src/graphics/draw/CompassRenderer.cpp +++ b/src/graphics/draw/CompassRenderer.cpp @@ -1,3 +1,5 @@ +#include "configuration.h" +#if HAS_SCREEN #include "CompassRenderer.h" #include "NodeDB.h" #include "UIRenderer.h" @@ -135,3 +137,4 @@ uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight) } // namespace CompassRenderer } // namespace graphics +#endif \ No newline at end of file diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 6c781e94f..701062e08 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -116,6 +116,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration) bannerOptions.bannerCallback = [](int selected) -> void { if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) { config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected); + auto changes = SEGMENT_CONFIG; + // This is needed as we wait til picking the LoRa region to generate keys for the first time. if (!owner.is_licensed) { bool keygenSuccess = false; @@ -124,6 +126,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration) if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { keygenSuccess = true; } + } else { LOG_INFO("Generate new PKI keys"); crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); @@ -141,7 +144,14 @@ void menuHandler::LoraRegionPicker(uint32_t duration) if (myRegion->dutyCycle < 100) { config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit } - service->reloadConfig(SEGMENT_CONFIG); + + if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) { + // Default broker is in use, so subscribe to the appropriate MQTT root topic for this region + sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); + changes |= SEGMENT_MODULECONFIG; + } + + service->reloadConfig(changes); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); } }; @@ -752,6 +762,31 @@ void menuHandler::nodeListMenu() screen->showOverlayBanner(bannerOptions); } +void menuHandler::nodeNameLengthMenu() +{ + enum OptionsNumbers { Back, Long, Short }; + static const char *optionsArray[] = {"Back", "Long", "Short"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Node Name Length"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 3; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Long) { + // Set names to long + LOG_INFO("Setting names to long"); + config.display.use_long_node_name = true; + } else if (selected == Short) { + // Set names to short + LOG_INFO("Setting names to short"); + config.display.use_long_node_name = false; + } else if (selected == Back) { + menuQueue = screen_options_menu; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + void menuHandler::resetNodeDBMenu() { static const char *optionsArray[] = {"Back", "Confirm"}; @@ -1294,11 +1329,16 @@ void menuHandler::screenOptionsMenu() hasSupportBrightness = false; #endif - enum optionsNumbers { Back, Brightness, ScreenColor }; - static const char *optionsArray[4] = {"Back"}; - static int optionsEnumArray[4] = {Back}; + enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor }; + static const char *optionsArray[5] = {"Back"}; + static int optionsEnumArray[5] = {Back}; int options = 1; +#if defined(T_DECK) || defined(T_LORA_PAGER) + optionsArray[options] = "Show Long/Short Name"; + optionsEnumArray[options++] = NodeNameLength; +#endif + // Only show brightness for B&W displays if (hasSupportBrightness) { optionsArray[options] = "Brightness"; @@ -1323,6 +1363,9 @@ void menuHandler::screenOptionsMenu() } else if (selected == ScreenColor) { menuHandler::menuQueue = menuHandler::tftcolormenupicker; screen->runNow(); + } else if (selected == NodeNameLength) { + menuHandler::menuQueue = menuHandler::node_name_length_menu; + screen->runNow(); } else { menuQueue = system_base_menu; screen->runNow(); @@ -1421,6 +1464,8 @@ void menuHandler::FrameToggles_menu() lora, clock, show_favorites, + show_telemetry, + show_power, enumEnd }; static const char *optionsArray[enumEnd] = {"Finish"}; @@ -1459,6 +1504,12 @@ void menuHandler::FrameToggles_menu() optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites"; optionsEnumArray[options++] = show_favorites; + optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Telemetry" : "Show Telemetry"; + optionsEnumArray[options++] = show_telemetry; + + optionsArray[options] = moduleConfig.telemetry.power_screen_enabled ? "Hide Power" : "Show Power"; + optionsEnumArray[options++] = show_power; + BannerOverlayOptions bannerOptions; bannerOptions.message = "Show/Hide Frames"; bannerOptions.optionsArrayPtr = optionsArray; @@ -1513,6 +1564,14 @@ void menuHandler::FrameToggles_menu() screen->toggleFrameVisibility("show_favorites"); menuHandler::menuQueue = menuHandler::FrameToggles; screen->runNow(); + } else if (selected == show_telemetry) { + moduleConfig.telemetry.environment_screen_enabled = !moduleConfig.telemetry.environment_screen_enabled; + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } else if (selected == show_power) { + moduleConfig.telemetry.power_screen_enabled = !moduleConfig.telemetry.power_screen_enabled; + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); } }; screen->showOverlayBanner(bannerOptions); @@ -1584,6 +1643,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display) case brightness_picker: BrightnessPickerMenu(); break; + case node_name_length_menu: + nodeNameLengthMenu(); + break; case reboot_menu: rebootMenu(); break; diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 47dbb2543..1f7bbac8c 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -43,6 +43,7 @@ class menuHandler key_verification_final_prompt, trace_route_menu, throttle_message, + node_name_length_menu, FrameToggles }; static screenMenus menuQueue; @@ -85,6 +86,7 @@ class menuHandler static void notificationsMenu(); static void screenOptionsMenu(); static void powerMenu(); + static void nodeNameLengthMenu(); static void FrameToggles_menu(); static void textMessageMenu(); diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 7d6a38dd3..07577db8c 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -55,26 +55,32 @@ static int scrollIndex = 0; const char *getSafeNodeName(meshtastic_NodeInfoLite *node) { + const char *name = NULL; static char nodeName[16] = "?"; - if (node->has_user && strlen(node->user.short_name) > 0) { - bool valid = true; - const char *name = node->user.short_name; - for (size_t i = 0; i < strlen(name); i++) { - uint8_t c = (uint8_t)name[i]; - if (c < 32 || c > 126) { - valid = false; - break; - } - } - if (valid) { - strncpy(nodeName, name, sizeof(nodeName) - 1); - nodeName[sizeof(nodeName) - 1] = '\0'; + if (config.display.use_long_node_name == true) { + if (node->has_user && strlen(node->user.long_name) > 0) { + name = node->user.long_name; } else { snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF)); } + } else { + if (node->has_user && strlen(node->user.short_name) > 0) { + name = node->user.short_name; + } else { + snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF)); + } + } + + // Use sanitizeString() function and copy directly into nodeName + std::string sanitized_name = sanitizeString(name ? name : ""); + + if (!sanitized_name.empty()) { + strncpy(nodeName, sanitized_name.c_str(), sizeof(nodeName) - 1); + nodeName[sizeof(nodeName) - 1] = '\0'; } else { snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF)); } + return nodeName; } diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index ff8cd20c5..1ff183779 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -563,6 +563,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); int line = 1; + meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); // === Header === #if defined(M5STACK_UNITC6L) @@ -740,7 +741,6 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta int yOffset = (isHighResolution) ? 0 : 5; std::string longNameStr; - meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) { longNameStr = sanitizeString(ourNode->user.long_name); } @@ -1000,24 +1000,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU const char *displayLine = ""; // Initialize to empty string by default meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); - bool usePhoneGPS = (ourNode && nodeDB->hasValidPosition(ourNode) && - config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED); - - if (usePhoneGPS) { - // Phone-provided GPS is active - displayLine = "Phone GPS"; - int yOffset = (isHighResolution) ? 3 : 1; - if (isHighResolution) { - NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width, - imgSatellite_height, imgSatellite, display); - } else { - display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height, - imgSatellite); - } - int xOffset = (isHighResolution) ? 6 : 0; - display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine); - } else if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { - // GPS disabled / not present + if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { if (config.position.fixed_position) { displayLine = "Fixed GPS"; } else { @@ -1108,9 +1091,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU // === Final Row: Altitude === char altitudeLine[32] = {0}; - int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode)) - ? ourNode->position.altitude - : geoCoord.getAltitude(); + int32_t alt = geoCoord.getAltitude(); if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0fft", alt * METERS_TO_FEET); } else { diff --git a/src/graphics/emotes.cpp b/src/graphics/emotes.cpp index e1a105d20..a0227d365 100644 --- a/src/graphics/emotes.cpp +++ b/src/graphics/emotes.cpp @@ -1,3 +1,5 @@ +#include "configuration.h" +#if HAS_SCREEN #include "emotes.h" namespace graphics @@ -275,3 +277,4 @@ const unsigned char bell_icon[] PROGMEM = { #endif } // namespace graphics +#endif \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp index db0805f4e..818c68070 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp @@ -13,45 +13,147 @@ void InkHUD::MapApplet::onRender() return; } + // Helper: draw rounded rectangle centered at x,y + auto fillRoundedRect = [&](int16_t cx, int16_t cy, int16_t w, int16_t h, int16_t r, uint16_t color) { + int16_t x = cx - (w / 2); + int16_t y = cy - (h / 2); + + // center rects + fillRect(x + r, y, w - 2 * r, h, color); + fillRect(x, y + r, r, h - 2 * r, color); + fillRect(x + w - r, y + r, r, h - 2 * r, color); + + // corners + fillCircle(x + r, y + r, r, color); + fillCircle(x + w - r - 1, y + r, r, color); + fillCircle(x + r, y + h - r - 1, r, color); + fillCircle(x + w - r - 1, y + h - r - 1, r, color); + }; + // Find center of map - // - latitude and longitude - // - will be placed at X(0.5), Y(0.5) getMapCenter(&latCenter, &lngCenter); - - // Calculate North+East distance of each node to map center - // - which nodes to use controlled by virtual shouldDrawNode method calculateAllMarkers(); - - // Set the region shown on the map - // - default: fit all nodes, plus padding - // - maybe overriden by derived applet - // - getMapSize *sets* passed parameters (C-style) getMapSize(&widthMeters, &heightMeters); - - // Set the metersToPx conversion value calculateMapScale(); - // Special marker for own node - meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); - if (ourNode && nodeDB->hasValidPosition(ourNode)) - drawLabeledMarker(ourNode); - - // Draw all markers + // Draw all markers first for (Marker m : markers) { int16_t x = X(0.5) + (m.eastMeters * metersToPx); int16_t y = Y(0.5) - (m.northMeters * metersToPx); - // Cross Size - constexpr uint16_t csMin = 5; - constexpr uint16_t csMax = 12; + // Add white halo outline first + constexpr int outlinePad = 1; + int boxSize = 11; + int radius = 2; // rounded corner radius - // Too many hops away - if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) // Too many mops - printAt(x, y, "!", CENTER, MIDDLE); - else if (!m.hasHopsAway) // Unknown hops - drawCross(x, y, csMin); - else // The fewer hops, the larger the cross - drawCross(x, y, map(m.hopsAway, 0, config.lora.hop_limit, csMax, csMin)); + // White halo background + fillRoundedRect(x, y, boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), radius + 1, WHITE); + + // Draw inner box + fillRoundedRect(x, y, boxSize, boxSize, radius, BLACK); + + // Text inside + setFont(fontSmall); + setTextColor(WHITE); + + // Draw actual marker on top + if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) { + printAt(x + 1, y + 1, "X", CENTER, MIDDLE); + } else if (!m.hasHopsAway) { + printAt(x + 1, y + 1, "?", CENTER, MIDDLE); + } else { + char hopStr[4]; + snprintf(hopStr, sizeof(hopStr), "%d", m.hopsAway); + printAt(x, y + 1, hopStr, CENTER, MIDDLE); + } + + // Restore default font and color + setFont(fontSmall); + setTextColor(BLACK); + } + + // Dual map scale bars + int16_t horizPx = width() * 0.25f; + int16_t vertPx = height() * 0.25f; + float horizMeters = horizPx / metersToPx; + float vertMeters = vertPx / metersToPx; + + auto formatDistance = [&](float meters, char *out, size_t len) { + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { + float feet = meters * 3.28084f; + if (feet < 528) + snprintf(out, len, "%.0f ft", feet); + else { + float miles = feet / 5280.0f; + snprintf(out, len, miles < 10 ? "%.1f mi" : "%.0f mi", miles); + } + } else { + if (meters >= 1000) + snprintf(out, len, "%.1f km", meters / 1000.0f); + else + snprintf(out, len, "%.0f m", meters); + } + }; + + // Horizontal scale bar + int16_t horizBarY = height() - 2; + int16_t horizBarX = 1; + drawLine(horizBarX, horizBarY, horizBarX + horizPx, horizBarY, BLACK); + drawLine(horizBarX, horizBarY - 3, horizBarX, horizBarY + 3, BLACK); + drawLine(horizBarX + horizPx, horizBarY - 3, horizBarX + horizPx, horizBarY + 3, BLACK); + + char horizLabel[32]; + formatDistance(horizMeters, horizLabel, sizeof(horizLabel)); + int16_t horizLabelW = getTextWidth(horizLabel); + int16_t horizLabelH = getFont().lineHeight(); + int16_t horizLabelX = horizBarX + horizPx + 4; + int16_t horizLabelY = horizBarY - horizLabelH + 1; + fillRect(horizLabelX - 2, horizLabelY - 1, horizLabelW + 4, horizLabelH + 2, WHITE); + printAt(horizLabelX, horizBarY, horizLabel, LEFT, BOTTOM); + + // Vertical scale bar + int16_t vertBarX = 1; + int16_t vertBarBottom = horizBarY; + int16_t vertBarTop = vertBarBottom - vertPx; + drawLine(vertBarX, vertBarBottom, vertBarX, vertBarTop, BLACK); + drawLine(vertBarX - 3, vertBarBottom, vertBarX + 3, vertBarBottom, BLACK); + drawLine(vertBarX - 3, vertBarTop, vertBarX + 3, vertBarTop, BLACK); + + char vertTopLabel[32]; + formatDistance(vertMeters, vertTopLabel, sizeof(vertTopLabel)); + int16_t topLabelY = vertBarTop - getFont().lineHeight() - 2; + int16_t topLabelW = getTextWidth(vertTopLabel); + int16_t topLabelH = getFont().lineHeight(); + fillRect(vertBarX - 2, topLabelY - 1, topLabelW + 6, topLabelH + 2, WHITE); + printAt(vertBarX + (topLabelW / 2) + 1, topLabelY + (topLabelH / 2), vertTopLabel, CENTER, MIDDLE); + + char vertBottomLabel[32]; + formatDistance(vertMeters, vertBottomLabel, sizeof(vertBottomLabel)); + int16_t bottomLabelY = vertBarBottom + 4; + int16_t bottomLabelW = getTextWidth(vertBottomLabel); + int16_t bottomLabelH = getFont().lineHeight(); + fillRect(vertBarX - 2, bottomLabelY - 1, bottomLabelW + 6, bottomLabelH + 2, WHITE); + printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE); + + // Draw our node LAST with full white fill + outline + meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); + if (ourNode && nodeDB->hasValidPosition(ourNode)) { + Marker self = calculateMarker(ourNode->position.latitude_i * 1e-7, ourNode->position.longitude_i * 1e-7, false, 0); + + int16_t centerX = X(0.5) + (self.eastMeters * metersToPx); + int16_t centerY = Y(0.5) - (self.northMeters * metersToPx); + + // White fill background + halo + fillCircle(centerX, centerY, 8, WHITE); // big white base + drawCircle(centerX, centerY, 8, WHITE); // crisp edge + + // Black bullseye on top + drawCircle(centerX, centerY, 6, BLACK); + fillCircle(centerX, centerY, 2, BLACK); + + // Crosshairs + drawLine(centerX - 8, centerY, centerX + 8, centerY, BLACK); + drawLine(centerX, centerY - 8, centerX, centerY + 8, BLACK); } } @@ -63,110 +165,123 @@ void InkHUD::MapApplet::onRender() void InkHUD::MapApplet::getMapCenter(float *lat, float *lng) { - // Find mean lat long coords - // ============================ - // - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet - // - averages the x, y and z coords - // - uses tan to find angles for lat / long degrees - // - longitude: triangle formed by x and y (on plane of the equator) - // - latitude: triangle formed by z (north south), - // and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface + // If we have a valid position for our own node, use that as the anchor + meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); + if (ourNode && nodeDB->hasValidPosition(ourNode)) { + *lat = ourNode->position.latitude_i * 1e-7; + *lng = ourNode->position.longitude_i * 1e-7; + } else { + // Find mean lat long coords + // ============================ + // - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet + // - averages the x, y and z coords + // - uses tan to find angles for lat / long degrees + // - longitude: triangle formed by x and y (on plane of the equator) + // - latitude: triangle formed by z (north south), + // and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's + // surface - // Working totals, averaged after nodeDB processed - uint32_t positionCount = 0; - float xAvg = 0; - float yAvg = 0; - float zAvg = 0; + // Working totals, averaged after nodeDB processed + uint32_t positionCount = 0; + float xAvg = 0; + float yAvg = 0; + float zAvg = 0; - // For each node in db - for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { - meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); + // For each node in db + for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { + meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); - // Skip if no position - if (!nodeDB->hasValidPosition(node)) - continue; + // Skip if no position + if (!nodeDB->hasValidPosition(node)) + continue; - // Skip if derived applet doesn't want to show this node on the map - if (!shouldDrawNode(node)) - continue; + // Skip if derived applet doesn't want to show this node on the map + if (!shouldDrawNode(node)) + continue; - // Latitude and Longitude of node, in radians - float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD; - float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD; + // Latitude and Longitude of node, in radians + float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD; + float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD; - // Convert to cartesian points, with center of earth at 0, 0, 0 - // Exact distance from center is irrelevant, as we're only interested in the vector - float x = cos(latRad) * cos(lngRad); - float y = cos(latRad) * sin(lngRad); - float z = sin(latRad); + // Convert to cartesian points, with center of earth at 0, 0, 0 + // Exact distance from center is irrelevant, as we're only interested in the vector + float x = cos(latRad) * cos(lngRad); + float y = cos(latRad) * sin(lngRad); + float z = sin(latRad); - // To find mean values shortly - xAvg += x; - yAvg += y; - zAvg += z; - positionCount++; + // To find mean values shortly + xAvg += x; + yAvg += y; + zAvg += z; + positionCount++; + } + + // All NodeDB processed, find mean values + xAvg /= positionCount; + yAvg /= positionCount; + zAvg /= positionCount; + + // Longitude from cartesian coords + // (Angle from 3D coords describing a point of globe's surface) + /* + UK + /-------\ + (Top View) /- -\ + /- (You) -\ + /- . -\ + /- . X -\ + Asia - ... - USA + \- Y -/ + \- -/ + \- -/ + \- -/ + \- -----/ + Pacific + + */ + + *lng = atan2(yAvg, xAvg) * RAD_TO_DEG; + + // Latitude from cartesian coords + // (Angle from 3D coords describing a point on the globe's surface) + // As latitude increases, distance from the Earth's north-south axis out to our surface point decreases. + // Means we need to first find the hypotenuse which becomes base of our triangle in the second step + /* + UK North + /-------\ (Front View) /-------\ + (Top View) /- -\ /- -\ + /- (You) -\ /-(You) -\ + /- /. -\ /- . -\ + /- √X²+Y²/ . X -\ /- Z . -\ + Asia - /... - USA - ..... - + \- Y -/ \- √X²+Y² -/ + \- -/ \- -/ + \- -/ \- -/ + \- -/ \- -/ + \- -----/ \- -----/ + Pacific South + */ + + float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect + *lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG; } - // All NodeDB processed, find mean values - xAvg /= positionCount; - yAvg /= positionCount; - zAvg /= positionCount; - - // Longitude from cartesian coords - // (Angle from 3D coords describing a point of globe's surface) - /* - UK - /-------\ - (Top View) /- -\ - /- (You) -\ - /- . -\ - /- . X -\ - Asia - ... - USA - \- Y -/ - \- -/ - \- -/ - \- -/ - \- -----/ - Pacific - - */ - - *lng = atan2(yAvg, xAvg) * RAD_TO_DEG; - - // Latitude from cartesian coords - // (Angle from 3D coords describing a point on the globe's surface) - // As latitude increases, distance from the Earth's north-south axis out to our surface point decreases. - // Means we need to first find the hypotenuse which becomes base of our triangle in the second step - /* - UK North - /-------\ (Front View) /-------\ - (Top View) /- -\ /- -\ - /- (You) -\ /-(You) -\ - /- /. -\ /- . -\ - /- √X²+Y²/ . X -\ /- Z . -\ - Asia - /... - USA - ..... - - \- Y -/ \- √X²+Y² -/ - \- -/ \- -/ - \- -/ \- -/ - \- -/ \- -/ - \- -----/ \- -----/ - Pacific South - */ - - float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect - *lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG; + // Use either our node position, or the mean fallback as the center + latCenter = *lat; + lngCenter = *lng; // ---------------------------------------------- - // This has given us the "mean position" - // This will be a position *somewhere* near the center of our nodes. - // What we actually want is to place our center so that our outermost nodes end up on the border of our map. - // The only real use of our "mean position" is to give us a reference frame: - // which direction is east, and which is west. + // This has given us either: + // - our actual position (preferred), or + // - a mean position (fallback if we had no fix) + // + // What we actually want is to place our center so that our outermost nodes + // end up on the border of our map. The only real use of our "center" is to give + // us a reference frame: which direction is east, and which is west. //------------------------------------------------ - // Find furthest nodes from "mean lat long" + // Find furthest nodes from our center // ======================================== - float northernmost = latCenter; float southernmost = latCenter; float easternmost = lngCenter; @@ -184,14 +299,14 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng) continue; // Check for a new top or bottom latitude - float lat = node->position.latitude_i * 1e-7; - northernmost = max(northernmost, lat); - southernmost = min(southernmost, lat); + float latNode = node->position.latitude_i * 1e-7; + northernmost = max(northernmost, latNode); + southernmost = min(southernmost, latNode); // Longitude is trickier - float lng = node->position.longitude_i * 1e-7; - float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node - float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node + float lngNode = node->position.longitude_i * 1e-7; + float degEastward = fmod(((lngNode - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node + float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node if (degEastward < degWestward) easternmost = max(easternmost, lngCenter + degEastward); else @@ -250,7 +365,6 @@ InkHUD::MapApplet::Marker InkHUD::MapApplet::calculateMarker(float lat, float ln m.hopsAway = hopsAway; return m; } - // Draw a marker on the map for a node, with a shortname label, and backing box void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node) { @@ -324,6 +438,18 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node) textX = labelX + paddingW; } + // Prevent overlap with scale bars and their labels + // Define a "safe zone" in the bottom-left where the scale bars and text are drawn + constexpr int16_t safeZoneHeight = 28; // adjust based on your label font height + constexpr int16_t safeZoneWidth = 60; // adjust based on horizontal label width zone + bool overlapsScale = (labelY + labelH > height() - safeZoneHeight) && (labelX < safeZoneWidth); + + // If it overlaps, shift label upward slightly above the safe zone + if (overlapsScale) { + labelY = height() - safeZoneHeight - labelH - 2; + textY = labelY + (labelH / 2); + } + // Backing box fillRect(labelX, labelY, labelW, labelH, WHITE); drawRect(labelX, labelY, labelW, labelH, BLACK); diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp index 1b0bfa9d0..5c9906fba 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp @@ -127,6 +127,11 @@ void InkHUD::NodeListApplet::onRender() // Y value (top) of the current card. Increases as we draw. uint16_t cardTopY = headerDivY + padDivH; + // Clean up deleted nodes before drawing + cards.erase( + std::remove_if(cards.begin(), cards.end(), [](const CardInfo &c) { return nodeDB->getMeshNode(c.nodeNum) == nullptr; }), + cards.end()); + // -- Each node in list -- for (auto card = cards.begin(); card != cards.end(); ++card) { @@ -141,6 +146,11 @@ void InkHUD::NodeListApplet::onRender() meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum); + // Skip deleted nodes + if (!node) { + continue; + } + // -- Shortname -- // Parse special chars in the short name // Use "?" if unknown @@ -188,7 +198,7 @@ void InkHUD::NodeListApplet::onRender() drawSignalIndicator(signalX, signalY, signalW, signalH, signal); } // Otherwise, print "hops away" info, if available - else if (hopsAway != CardInfo::HOPS_UNKNOWN) { + else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) { std::string hopString = to_string(node->hops_away); hopString += " Hop"; if (node->hops_away != 1) diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 7876276a8..09f76ed46 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -709,7 +709,7 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t // Voltage float voltage = powerStatus->getBatteryVoltageMv() / 1000.0; char voltageStr[6]; // "XX.XV" - sprintf(voltageStr, "%.1fV", voltage); + sprintf(voltageStr, "%.2fV", voltage); printAt(colC[0], labelT, "Bat", CENTER, TOP); printAt(colC[0], valT, voltageStr, CENTER, TOP); diff --git a/src/main.cpp b/src/main.cpp index bca2da2cb..689e80e35 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ #include "power.h" #if !MESHTASTIC_EXCLUDE_I2C +#include "detect/ScanI2CConsumer.h" #include "detect/ScanI2CTwoWire.h" #include #endif @@ -435,6 +436,12 @@ void setup() LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n"); +#if defined(DEBUG_MUTE) && defined(DEBUG_PORT) + DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n"); + DEBUG_PORT.printf("Version %s for %s from %s\r\n", optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO)); + DEBUG_PORT.printf("Debug mute is enabled, there will be no serial output.\r\n"); +#endif + initDeepSleep(); #if defined(MODEM_POWER_EN) @@ -718,46 +725,21 @@ void setup() LOG_DEBUG("acc_info = %i", acc_info.type); #endif - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_3XX, meshtastic_TelemetrySensorType_BMP3XX); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHTC3, meshtastic_TelemetrySensorType_SHTC3); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LPS22HB, meshtastic_TelemetrySensorType_LPS22); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::CGRADSENS, meshtastic_TelemetrySensorType_RADSENS); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LTR390UV, meshtastic_TelemetrySensorType_LTR390UV); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DPS310, meshtastic_TelemetrySensorType_DPS310); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2561, meshtastic_TelemetrySensorType_TSL2561); - i2cScanner.reset(); #endif #ifdef HAS_SDCARD @@ -865,7 +847,14 @@ void setup() SPI.begin(); } #elif !defined(ARCH_ESP32) // ARCH_RP2040 +#if defined(RAK3401) || defined(RAK13302) + pinMode(WB_IO2, OUTPUT); + digitalWrite(WB_IO2, HIGH); + SPI1.setPins(LORA_MISO, LORA_SCK, LORA_MOSI); + SPI1.begin(); +#else SPI.begin(); +#endif #else // ESP32 #if defined(HW_SPI1_DEVICE) @@ -964,6 +953,12 @@ void setup() // Now that the mesh service is created, create any modules setupModules(); +#if !MESHTASTIC_EXCLUDE_I2C + // Inform modules about I2C devices + ScanI2CCompleted(i2cScanner.get()); + i2cScanner.reset(); +#endif + // warn the user about a low entropy key if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) { LOG_WARN(LOW_ENTROPY_WARNING); @@ -1601,10 +1596,12 @@ void loop() service->loop(); #if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS) - inputBroker->processInputEventQueue(); + if (inputBroker) + inputBroker->processInputEventQueue(); #endif -#if defined(LGFX_SDL) - if (screen) { +#if ARCH_PORTDUINO && HAS_TFT + if (screen && portduino_config.displayPanel == x11 && + config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { auto dispdev = screen->getDisplayDevice(); if (dispdev) static_cast(dispdev)->sdlLoop(); diff --git a/src/mesh/Default.h b/src/mesh/Default.h index 2f05da98d..d0d4678ff 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -27,7 +27,7 @@ #ifdef USERPREFS_RINGTONE_NAG_SECS #define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS #else -#define default_ringtone_nag_secs 60 +#define default_ringtone_nag_secs 15 #endif #define default_mqtt_address "mqtt.meshtastic.org" @@ -84,4 +84,4 @@ class Default return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default) } } -}; \ No newline at end of file +}; diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 0e23405e5..3831a384d 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -218,6 +218,7 @@ template void LR11x0Interface::addReceiveMetadata(meshtastic_Mes // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus()); mp->rx_snr = lora.getSNR(); mp->rx_rssi = lround(lora.getRSSI()); + LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError()); } /** We override to turn on transmitter power as needed. diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h index 4829ddc1d..840184bbf 100644 --- a/src/mesh/LR11x0Interface.h +++ b/src/mesh/LR11x0Interface.h @@ -65,5 +65,7 @@ template class LR11x0Interface : public RadioLibInterface virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void setStandby() override; + + uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); } }; #endif \ No newline at end of file diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index ef5380eb8..cbea85c62 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -65,7 +65,7 @@ void fixPriority(meshtastic_MeshPacket *p) } /** enqueue a packet, return false if full */ -bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p) +bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p, bool *dropped) { // no space - try to replace a lower priority packet in the queue if (queue.size() >= maxLen) { @@ -73,9 +73,16 @@ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p) if (!replaced) { LOG_WARN("TX queue is full, and there is no lower-priority packet available to evict in favour of 0x%08x", p->id); } + if (dropped) { + *dropped = true; + } return replaced; } + if (dropped) { + *dropped = false; + } + // Find the correct position using upper_bound to maintain a stable order auto it = std::upper_bound(queue.begin(), queue.end(), p, CompareMeshPacketFunc); queue.insert(it, p); // Insert packet at the found position diff --git a/src/mesh/MeshPacketQueue.h b/src/mesh/MeshPacketQueue.h index ea52eb5bf..3d3902c1e 100644 --- a/src/mesh/MeshPacketQueue.h +++ b/src/mesh/MeshPacketQueue.h @@ -19,8 +19,10 @@ class MeshPacketQueue public: explicit MeshPacketQueue(size_t _maxLen); - /** enqueue a packet, return false if full */ - bool enqueue(meshtastic_MeshPacket *p); + /** enqueue a packet, return false if full + * @param dropped Optional pointer to a bool that will be set to true if a packet was dropped + */ + bool enqueue(meshtastic_MeshPacket *p, bool *dropped = nullptr); /** return true if the queue is empty */ bool empty(); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5b235485d..dec8411fe 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -256,6 +256,8 @@ NodeDB::NodeDB() owner.role = config.device.role; // Ensure macaddr is set to our macaddr as it will be copied in our info below memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); + // Ensure owner.id is always derived from the node number + snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum()); if (!config.has_security) { config.has_security = true; @@ -1152,6 +1154,20 @@ void NodeDB::loadFromDisk() spiLock->unlock(); #endif #ifdef FSCom +#ifdef FACTORY_INSTALL + spiLock->lock(); + if (!FSCom.exists("/prefs/" xstr(BUILD_EPOCH))) { + LOG_WARN("Factory Install Reset!"); + FSCom.format(); + FSCom.mkdir("/prefs"); + File f2 = FSCom.open("/prefs/" xstr(BUILD_EPOCH), FILE_O_WRITE); + if (f2) { + f2.flush(); + f2.close(); + } + } + spiLock->unlock(); +#endif spiLock->lock(); if (FSCom.exists(legacyPrefFileName)) { spiLock->unlock(); @@ -1677,6 +1693,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde } #endif + // Always ensure user.id is derived from nodeId, regardless of what was received + snprintf(p.id, sizeof(p.id), "!%08x", nodeId); + // Both of info->user and p start as filled with zero so I think this is okay auto lite = TypeConversions::ConvertToUserLite(p); bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex); @@ -1855,6 +1874,13 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n) return info->channel; } +std::string NodeDB::getNodeId() const +{ + char nodeId[16]; + snprintf(nodeId, sizeof(nodeId), "!%08x", myNodeInfo.my_node_num); + return std::string(nodeId); +} + /// Find a node in our DB, return null for missing /// NOTE: This function might be called from an ISR meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index f73f64f92..e8724f2c9 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "MeshTypes.h" @@ -203,6 +204,9 @@ class NodeDB /// @return our node number NodeNum getNodeNum() { return myNodeInfo.my_node_num; } + /// @return our node ID as a string in the format "!xxxxxxxx" + std::string getNodeId() const; + // @return last byte of a NodeNum, 0xFF if it ended at 0x00 uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); } diff --git a/src/mesh/PacketCache.cpp b/src/mesh/PacketCache.cpp new file mode 100644 index 000000000..0edf81741 --- /dev/null +++ b/src/mesh/PacketCache.cpp @@ -0,0 +1,253 @@ +#include "PacketCache.h" +#include "Router.h" + +PacketCache packetCache{}; + +/** + * Allocate a new cache entry and copy the packet header and payload into it + */ +PacketCacheEntry *PacketCache::cache(const meshtastic_MeshPacket *p, bool preserveMetadata) +{ + size_t payload_size = + (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) ? p->encrypted.size : p->decoded.payload.size; + PacketCacheEntry *e = (PacketCacheEntry *)malloc(sizeof(PacketCacheEntry) + payload_size + + (preserveMetadata ? sizeof(PacketCacheMetadata) : 0)); + if (!e) { + LOG_ERROR("Unable to allocate memory for packet cache entry"); + return NULL; + } + + *e = {}; + e->header.from = p->from; + e->header.to = p->to; + e->header.id = p->id; + e->header.channel = p->channel; + e->header.next_hop = p->next_hop; + e->header.relay_node = p->relay_node; + e->header.flags = (p->hop_limit & PACKET_FLAGS_HOP_LIMIT_MASK) | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | + (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0) | + ((p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK); + + PacketCacheMetadata m{}; + if (preserveMetadata) { + e->has_metadata = true; + m.rx_rssi = (uint8_t)(p->rx_rssi + 200); + m.rx_snr = (uint8_t)((p->rx_snr + 30.0f) / 0.25f); + m.rx_time = p->rx_time; + m.transport_mechanism = p->transport_mechanism; + m.priority = p->priority; + } + if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) { + e->encrypted = true; + e->payload_len = p->encrypted.size; + memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->encrypted.bytes, p->encrypted.size); + } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + e->encrypted = false; + if (preserveMetadata) { + m.portnum = p->decoded.portnum; + m.want_response = p->decoded.want_response; + m.emoji = p->decoded.emoji; + m.bitfield = p->decoded.bitfield; + if (p->decoded.reply_id) + m.reply_id = p->decoded.reply_id; + else if (p->decoded.request_id) + m.request_id = p->decoded.request_id; + } + e->payload_len = p->decoded.payload.size; + memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->decoded.payload.bytes, p->decoded.payload.size); + } else { + LOG_ERROR("Unable to cache packet with unknown payload type %d", p->which_payload_variant); + free(e); + return NULL; + } + if (preserveMetadata) + memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry) + e->payload_len, &m, sizeof(m)); + + size += sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0); + insert(e); + return e; +}; + +/** + * Dump a list of packets into the provided buffer + */ +void PacketCache::dump(void *dest, const PacketCacheEntry **entries, size_t num_entries) +{ + unsigned char *pos = (unsigned char *)dest; + for (size_t i = 0; i < num_entries; i++) { + size_t entry_len = + sizeof(PacketCacheEntry) + entries[i]->payload_len + (entries[i]->has_metadata ? sizeof(PacketCacheMetadata) : 0); + memcpy(pos, entries[i], entry_len); + pos += entry_len; + } +} + +/** + * Calculate the length of buffer needed to dump the specified entries + */ +size_t PacketCache::dumpSize(const PacketCacheEntry **entries, size_t num_entries) +{ + size_t total_size = 0; + for (size_t i = 0; i < num_entries; i++) { + total_size += sizeof(PacketCacheEntry) + entries[i]->payload_len; + if (entries[i]->has_metadata) + total_size += sizeof(PacketCacheMetadata); + } + return total_size; +} + +/** + * Find a packet in the cache + */ +PacketCacheEntry *PacketCache::find(NodeNum from, PacketId id) +{ + uint16_t h = PACKET_HASH(from, id); + PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)]; + while (e) { + if (e->header.from == from && e->header.id == id) + return e; + e = e->next; + } + return NULL; +} + +/** + * Find a packet in the cache by its hash + */ +PacketCacheEntry *PacketCache::find(PacketHash h) +{ + PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)]; + while (e) { + if (PACKET_HASH(e->header.from, e->header.id) == h) + return e; + e = e->next; + } + return NULL; +} + +/** + * Load a list of packets from the provided buffer + */ +bool PacketCache::load(void *src, PacketCacheEntry **entries, size_t num_entries) +{ + memset(entries, 0, sizeof(PacketCacheEntry *) * num_entries); + unsigned char *pos = (unsigned char *)src; + for (size_t i = 0; i < num_entries; i++) { + PacketCacheEntry e{}; + memcpy(&e, pos, sizeof(PacketCacheEntry)); + size_t entry_len = sizeof(PacketCacheEntry) + e.payload_len + (e.has_metadata ? sizeof(PacketCacheMetadata) : 0); + entries[i] = (PacketCacheEntry *)malloc(entry_len); + size += entry_len; + if (!entries[i]) { + LOG_ERROR("Unable to allocate memory for packet cache entry"); + for (size_t j = 0; j < i; j++) { + size -= sizeof(PacketCacheEntry) + entries[j]->payload_len + + (entries[j]->has_metadata ? sizeof(PacketCacheMetadata) : 0); + free(entries[j]); + entries[j] = NULL; + } + return false; + } + memcpy(entries[i], pos, entry_len); + pos += entry_len; + } + for (size_t i = 0; i < num_entries; i++) + insert(entries[i]); + return true; +} + +/** + * Copy the cached packet into the provided MeshPacket structure + */ +void PacketCache::rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p) +{ + if (!e || !p) + return; + + *p = {}; + p->from = e->header.from; + p->to = e->header.to; + p->id = e->header.id; + p->channel = e->header.channel; + p->next_hop = e->header.next_hop; + p->relay_node = e->header.relay_node; + p->hop_limit = e->header.flags & PACKET_FLAGS_HOP_LIMIT_MASK; + p->want_ack = !!(e->header.flags & PACKET_FLAGS_WANT_ACK_MASK); + p->via_mqtt = !!(e->header.flags & PACKET_FLAGS_VIA_MQTT_MASK); + p->hop_start = (e->header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT; + p->which_payload_variant = e->encrypted ? meshtastic_MeshPacket_encrypted_tag : meshtastic_MeshPacket_decoded_tag; + + unsigned char *payload = ((unsigned char *)e) + sizeof(PacketCacheEntry); + PacketCacheMetadata m{}; + if (e->has_metadata) { + memcpy(&m, (payload + e->payload_len), sizeof(m)); + p->rx_rssi = ((int)m.rx_rssi) - 200; + p->rx_snr = ((float)m.rx_snr * 0.25f) - 30.0f; + p->rx_time = m.rx_time; + p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)m.transport_mechanism; + p->priority = (meshtastic_MeshPacket_Priority)m.priority; + } + if (e->encrypted) { + memcpy(p->encrypted.bytes, payload, e->payload_len); + p->encrypted.size = e->payload_len; + } else { + memcpy(p->decoded.payload.bytes, payload, e->payload_len); + p->decoded.payload.size = e->payload_len; + if (e->has_metadata) { + // Decrypted-only metadata + p->decoded.portnum = (meshtastic_PortNum)m.portnum; + p->decoded.want_response = m.want_response; + p->decoded.emoji = m.emoji; + p->decoded.bitfield = m.bitfield; + if (m.reply_id) + p->decoded.reply_id = m.reply_id; + else if (m.request_id) + p->decoded.request_id = m.request_id; + } + } +} + +/** + * Release a cache entry + */ +void PacketCache::release(PacketCacheEntry *e) +{ + if (!e) + return; + remove(e); + size -= sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0); + free(e); +} + +/** + * Insert a new entry into the hash table + */ +void PacketCache::insert(PacketCacheEntry *e) +{ + assert(e); + PacketHash h = PACKET_HASH(e->header.from, e->header.id); + PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)]; + e->next = *target; + *target = e; + num_entries++; +} + +/** + * Remove an entry from the hash table + */ +void PacketCache::remove(PacketCacheEntry *e) +{ + assert(e); + PacketHash h = PACKET_HASH(e->header.from, e->header.id); + PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)]; + while (*target) { + if (*target == e) { + *target = e->next; + e->next = NULL; + num_entries--; + break; + } else { + target = &(*target)->next; + } + } +} \ No newline at end of file diff --git a/src/mesh/PacketCache.h b/src/mesh/PacketCache.h new file mode 100644 index 000000000..81ad455da --- /dev/null +++ b/src/mesh/PacketCache.h @@ -0,0 +1,75 @@ +#pragma once +#include "RadioInterface.h" + +#define PACKET_HASH(a, b) ((((a ^ b) >> 16) ^ (a ^ b)) & 0xFFFF) // 16 bit fold of packet (from, id) tuple +typedef uint16_t PacketHash; + +#define PACKET_CACHE_BUCKETS 64 // Number of hash table buckets +#define PACKET_CACHE_BUCKET(h) (((h >> 12) ^ (h >> 6) ^ h) & 0x3F) // Fold hash down to 6-bit bucket index + +typedef struct PacketCacheEntry { + PacketCacheEntry *next; + PacketHeader header; + uint16_t payload_len = 0; + union { + uint16_t bitfield; + struct { + uint8_t encrypted : 1; // Payload is encrypted + uint8_t has_metadata : 1; // Payload includes PacketCacheMetadata + uint8_t : 6; // Reserved for future use + uint16_t : 8; // Reserved for future use + }; + }; +} PacketCacheEntry; + +typedef struct PacketCacheMetadata { + PacketCacheMetadata() : _bitfield(0), reply_id(0), _bitfield2(0) {} + union { + uint32_t _bitfield; + struct { + uint16_t portnum : 9; // meshtastic_MeshPacket::decoded::portnum + uint16_t want_response : 1; // meshtastic_MeshPacket::decoded::want_response + uint16_t emoji : 1; // meshtastic_MeshPacket::decoded::emoji + uint16_t bitfield : 5; // meshtastic_MeshPacket::decoded::bitfield (truncated) + uint8_t rx_rssi : 8; // meshtastic_MeshPacket::rx_rssi (map via actual RSSI + 200) + uint8_t rx_snr : 8; // meshtastic_MeshPacket::rx_snr (map via (p->rx_snr + 30.0f) / 0.25f) + }; + }; + union { + uint32_t reply_id; // meshtastic_MeshPacket::decoded.reply_id + uint32_t request_id; // meshtastic_MeshPacket::decoded.request_id + }; + uint32_t rx_time = 0; // meshtastic_MeshPacket::rx_time + uint8_t transport_mechanism = 0; // meshtastic_MeshPacket::transport_mechanism + struct { + uint8_t _bitfield2; + union { + uint8_t priority : 7; // meshtastic_MeshPacket::priority + uint8_t reserved : 1; // Reserved for future use + }; + }; +} PacketCacheMetadata; + +class PacketCache +{ + public: + PacketCacheEntry *cache(const meshtastic_MeshPacket *p, bool preserveMetadata); + static void dump(void *dest, const PacketCacheEntry **entries, size_t num_entries); + size_t dumpSize(const PacketCacheEntry **entries, size_t num_entries); + PacketCacheEntry *find(NodeNum from, PacketId id); + PacketCacheEntry *find(PacketHash h); + bool load(void *src, PacketCacheEntry **entries, size_t num_entries); + size_t getNumEntries() { return num_entries; } + size_t getSize() { return size; } + void rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p); + void release(PacketCacheEntry *e); + + private: + PacketCacheEntry *buckets[PACKET_CACHE_BUCKETS]{}; + size_t num_entries = 0; + size_t size = 0; + void insert(PacketCacheEntry *e); + void remove(PacketCacheEntry *e); +}; + +extern PacketCache packetCache; \ No newline at end of file diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 07f314415..d1e342c80 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -15,6 +15,7 @@ #include "Router.h" #include "SPILock.h" #include "TypeConversions.h" +#include "concurrency/LockGuard.h" #include "main.h" #include "xmodem.h" @@ -56,6 +57,9 @@ void PhoneAPI::handleStartConfig() #endif } + // Allow subclasses to prepare for high-throughput config traffic + onConfigStart(); + // even if we were already connected - restart our state machine if (config_nonce == SPECIAL_NONCE_ONLY_NODES) { // If client only wants node info, jump directly to sending nodes @@ -70,8 +74,13 @@ void PhoneAPI::handleStartConfig() spiLock->unlock(); LOG_DEBUG("Got %d files in manifest", filesManifest.size()); - LOG_INFO("Start API client config"); - nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos + LOG_INFO("Start API client config millis=%u", millis()); + // Protect against concurrent BLE callbacks: they run in NimBLE's FreeRTOS task and also touch nodeInfoQueue. + { + concurrency::LockGuard guard(&nodeInfoMutex); + nodeInfoForPhone = {}; + nodeInfoQueue.clear(); + } resetReadIndex(); } @@ -93,7 +102,12 @@ void PhoneAPI::close() onConnectionChanged(false); fromRadioScratch = {}; toRadioScratch = {}; - nodeInfoForPhone = {}; + // Clear cached node info under lock because NimBLE callbacks can still be draining it. + { + concurrency::LockGuard guard(&nodeInfoMutex); + nodeInfoForPhone = {}; + nodeInfoQueue.clear(); + } packetForPhone = NULL; filesManifest.clear(); fromRadioNum = 0; @@ -148,6 +162,10 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) #if !MESHTASTIC_EXCLUDE_MQTT case meshtastic_ToRadio_mqttClientProxyMessage_tag: LOG_DEBUG("Got MqttClientProxy message"); + if (state != STATE_SEND_PACKETS) { + LOG_WARN("Ignore MqttClientProxy message while completing config handshake"); + break; + } if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled && (channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) { mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage); @@ -239,13 +257,20 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) LOG_DEBUG("Send My NodeInfo"); auto us = nodeDB->readNextMeshNode(readIndex); if (us) { - nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(us); - nodeInfoForPhone.has_hops_away = false; - nodeInfoForPhone.is_favorite = true; + auto info = TypeConversions::ConvertToNodeInfo(us); + info.has_hops_away = false; + info.is_favorite = true; + { + concurrency::LockGuard guard(&nodeInfoMutex); + nodeInfoForPhone = info; + } fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag; - fromRadioScratch.node_info = nodeInfoForPhone; + fromRadioScratch.node_info = info; // Should allow us to resume sending NodeInfo in STATE_SEND_OTHER_NODEINFOS - nodeInfoForPhone.num = 0; + { + concurrency::LockGuard guard(&nodeInfoMutex); + nodeInfoForPhone.num = 0; + } } if (config_nonce == SPECIAL_NONCE_ONLY_NODES) { // If client only wants node info, jump directly to sending nodes @@ -431,16 +456,44 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) break; case STATE_SEND_OTHER_NODEINFOS: { - LOG_DEBUG("Send known nodes"); - if (nodeInfoForPhone.num != 0) { - LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard, - nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name); + if (readIndex == 2) { // readIndex==2 will be true for the first non-us node + LOG_INFO("Start sending nodeinfos millis=%u", millis()); + } + + meshtastic_NodeInfo infoToSend = {}; + { + concurrency::LockGuard guard(&nodeInfoMutex); + if (nodeInfoForPhone.num == 0 && !nodeInfoQueue.empty()) { + // Serve the next cached node without re-reading from the DB iterator. + nodeInfoForPhone = nodeInfoQueue.front(); + nodeInfoQueue.pop_front(); + } + infoToSend = nodeInfoForPhone; + if (infoToSend.num != 0) + nodeInfoForPhone = {}; + } + + if (infoToSend.num != 0) { + // Just in case we stored a different user.id in the past, but should never happen going forward + sprintf(infoToSend.user.id, "!%08x", infoToSend.num); + + // Logging this really slows down sending nodes on initial connection because the serial console is so slow, so only + // uncomment if you really need to: + // LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard, + // nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name); + + // Occasional progress logging. (readIndex==2 will be true for the first non-us node) + if (readIndex == 2 || readIndex % 20 == 0) { + LOG_DEBUG("nodeinfo: %d/%d", readIndex, nodeDB->getNumMeshNodes()); + } + fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag; - fromRadioScratch.node_info = nodeInfoForPhone; - // Stay in current state until done sending nodeinfos - nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time + fromRadioScratch.node_info = infoToSend; + prefetchNodeInfos(); } else { - LOG_DEBUG("Done sending nodeinfo"); + LOG_DEBUG("Done sending %d of %d nodeinfos millis=%u", readIndex, nodeDB->getNumMeshNodes(), millis()); + concurrency::LockGuard guard(&nodeInfoMutex); + nodeInfoQueue.clear(); state = STATE_SEND_FILEMANIFEST; // Go ahead and send that ID right now return getFromRadio(buf); @@ -520,11 +573,15 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) void PhoneAPI::sendConfigComplete() { - LOG_INFO("Config Send Complete"); + LOG_INFO("Config Send Complete millis=%u", millis()); fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag; fromRadioScratch.config_complete_id = config_nonce; config_nonce = 0; state = STATE_SEND_PACKETS; + + // Allow subclasses to know we've entered steady-state so they can lower power consumption + onConfigComplete(); + pauseBluetoothLogging = false; } @@ -544,6 +601,33 @@ void PhoneAPI::releaseQueueStatusPhonePacket() } } +void PhoneAPI::prefetchNodeInfos() +{ + bool added = false; + // Keep the queue topped up so BLE reads stay responsive even if DB fetches take a moment. + { + concurrency::LockGuard guard(&nodeInfoMutex); + while (nodeInfoQueue.size() < kNodePrefetchDepth) { + auto nextNode = nodeDB->readNextMeshNode(readIndex); + if (!nextNode) + break; + + auto info = TypeConversions::ConvertToNodeInfo(nextNode); + bool isUs = info.num == nodeDB->getNodeNum(); + info.hops_away = isUs ? 0 : info.hops_away; + info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard; + info.snr = isUs ? 0 : info.snr; + info.via_mqtt = isUs ? false : info.via_mqtt; + info.is_favorite = info.is_favorite || isUs; + nodeInfoQueue.push_back(info); + added = true; + } + } + + if (added) + onNowHasData(0); +} + void PhoneAPI::releaseMqttClientProxyPhonePacket() { if (mqttClientProxyMessageForPhone) { @@ -579,21 +663,17 @@ bool PhoneAPI::available() case STATE_SEND_COMPLETE_ID: return true; - case STATE_SEND_OTHER_NODEINFOS: - if (nodeInfoForPhone.num == 0) { - auto nextNode = nodeDB->readNextMeshNode(readIndex); - if (nextNode) { - nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode); - bool isUs = nodeInfoForPhone.num == nodeDB->getNodeNum(); - nodeInfoForPhone.hops_away = isUs ? 0 : nodeInfoForPhone.hops_away; - nodeInfoForPhone.last_heard = isUs ? getValidTime(RTCQualityFromNet) : nodeInfoForPhone.last_heard; - nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr; - nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt; - nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite - onNowHasData(0); - } + case STATE_SEND_OTHER_NODEINFOS: { + concurrency::LockGuard guard(&nodeInfoMutex); + if (nodeInfoQueue.empty()) { + // Drop the lock before prefetching; prefetchNodeInfos() will re-acquire it. + goto PREFETCH_NODEINFO; } + } return true; // Always say we have something, because we might need to advance our state machine + PREFETCH_NODEINFO: + prefetchNodeInfos(); + return true; case STATE_SEND_PACKETS: { if (!queueStatusPacketForPhone) queueStatusPacketForPhone = service->getQueueStatusForPhone(); @@ -732,7 +812,7 @@ int PhoneAPI::onNotify(uint32_t newValue) LOG_INFO("Tell client we have new packets %u", newValue); onNowHasData(newValue); } else { - LOG_DEBUG("(Client not yet interested in packets)"); + LOG_DEBUG("Client not yet interested in packets (state=%d)", state); } return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 0d7772d17..d6682684f 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -1,8 +1,10 @@ #pragma once #include "Observer.h" +#include "concurrency/Lock.h" #include "mesh-pb-constants.h" #include "meshtastic/portnums.pb.h" +#include #include #include #include @@ -79,6 +81,12 @@ class PhoneAPI /// We temporarily keep the nodeInfo here between the call to available and getFromRadio meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default; + // Prefetched node info entries ready for immediate transmission to the phone. + std::deque nodeInfoQueue; + // Tunable size of the node info cache so we can keep BLE reads non-blocking. + static constexpr size_t kNodePrefetchDepth = 4; + // Protect nodeInfoForPhone + nodeInfoQueue because NimBLE callbacks run in a separate FreeRTOS task. + concurrency::Lock nodeInfoMutex; meshtastic_ToRadio toRadioScratch = { 0}; // this is a static scratch object, any data must be copied elsewhere before returning @@ -128,6 +136,7 @@ class PhoneAPI bool available(); bool isConnected() { return state != STATE_SEND_NOTHING; } + bool isSendingPackets() { return state == STATE_SEND_PACKETS; } protected: /// Our fromradio packet while it is being assembled @@ -150,6 +159,11 @@ class PhoneAPI */ virtual void onNowHasData(uint32_t fromRadioNum) {} + /// Subclasses can use these lifecycle hooks for transport-specific behavior around config/steady-state + /// (i.e. BLE connection params) + virtual void onConfigStart() {} + virtual void onConfigComplete() {} + /// begin a new connection void handleStartConfig(); @@ -158,6 +172,8 @@ class PhoneAPI void releaseQueueStatusPhonePacket(); + void prefetchNodeInfos(); + void releaseMqttClientProxyPhonePacket(); void releaseClientNotification(); diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h index 327e57900..ffd8ae008 100644 --- a/src/mesh/RF95Interface.h +++ b/src/mesh/RF95Interface.h @@ -65,8 +65,10 @@ class RF95Interface : public RadioLibInterface */ virtual void configHardwareForSend() override; + uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(*lora, pl, received); } + private: /** Some boards require GPIO control of tx vs rx paths */ void setTransmitEnable(bool txon); }; -#endif \ No newline at end of file +#endif diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 29f91060c..3c0da4494 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -230,33 +230,7 @@ The band is from 902 to 928 MHz. It mentions channel number and its respective c separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. */ -/** - * Calculate airtime per - * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf - * section 4 - * - * @return num msecs for the packet - */ -uint32_t RadioInterface::getPacketTime(uint32_t pl) -{ - float bandwidthHz = bw * 1000.0f; - bool headDisable = false; // we currently always use the header - float tSym = (1 << sf) / bandwidthHz; - - bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms - - float tPreamble = (preambleLength + 4.25f) * tSym; - float numPayloadSym = - 8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f); - float tPayload = numPayloadSym * tSym; - float tPacket = tPreamble + tPayload; - - uint32_t msecs = tPacket * 1000; - - return msecs; -} - -uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p) +uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received) { uint32_t pl = 0; if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) { @@ -265,7 +239,7 @@ uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p) size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded); pl = numbytes + sizeof(PacketHeader); } - return getPacketTime(pl); + return getPacketTime(pl, received); } /** The delay to use for retransmitting dropped packets */ @@ -298,10 +272,10 @@ uint32_t RadioInterface::getTxDelayMsec() uint8_t RadioInterface::getCWsize(float snr) { // The minimum value for a LoRa SNR - const uint32_t SNR_MIN = -20; + const int32_t SNR_MIN = -20; // The maximum value for a LoRa SNR - const uint32_t SNR_MAX = 10; + const int32_t SNR_MAX = 10; return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax); } @@ -624,8 +598,7 @@ void RadioInterface::applyModemConfig() saveFreq(freq + loraConfig.frequency_offset); slotTimeMsec = computeSlotTimeMsec(); - preambleTimeMsec = getPacketTime((uint32_t)0); - maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader)); + preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw); LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset); LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset, @@ -635,7 +608,7 @@ void RadioInterface::applyModemConfig() LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw); LOG_INFO("channel_num: %d", channel_num + 1); LOG_INFO("frequency: %f", getFreq()); - LOG_INFO("Slot time: %u msec", slotTimeMsec); + LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec); } /** Slottime is the time to detect a transmission has started, consisting of: @@ -674,23 +647,24 @@ void RadioInterface::limitPower(int8_t loraMaxPower) } #ifndef NUM_PA_POINTS - if (TX_GAIN_LORA > 0) { + if (TX_GAIN_LORA > 0 && !devicestate.owner.is_licensed) { LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA); power -= TX_GAIN_LORA; } #else - // we have an array of PA gain values. Find the highest power setting that works. - const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; - for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) { - if (((radio_dbm + tx_gain[radio_dbm]) > power) || - ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { - // we've exceeded the power limit, or hit the max we can do - LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]); - power -= tx_gain[radio_dbm]; - break; + if (!devicestate.owner.is_licensed) { + // we have an array of PA gain values. Find the highest power setting that works. + const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; + for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) { + if (((radio_dbm + tx_gain[radio_dbm]) > power) || + ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { + // we've exceeded the power limit, or hit the max we can do + LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]); + power -= tx_gain[radio_dbm]; + break; + } } } - #endif if (power > loraMaxPower) // Clamp power to maximum defined level power = loraMaxPower; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 0c5b6cd1a..6049a11cc 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -87,9 +87,8 @@ class RadioInterface const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48 const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280 uint32_t slotTimeMsec = computeSlotTimeMsec(); - uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving - uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast - uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast + uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving + uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast const uint32_t PROCESSING_TIME_MSEC = 4500; // time to construct, process and construct a packet again (empirically determined) const uint8_t CWmin = 3; // minimum CWsize @@ -202,8 +201,8 @@ class RadioInterface * * @return num msecs for the packet */ - uint32_t getPacketTime(const meshtastic_MeshPacket *p); - uint32_t getPacketTime(uint32_t totalPacketLen); + uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false); + virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0; /** * Get the channel we saved. diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 3717e8780..80e51b8bc 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -116,16 +116,21 @@ bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidF if (detected) { if (!activeReceiveStart) { activeReceiveStart = millis(); - } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(irq & syncWordHeaderValidFlag)) { - // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag - activeReceiveStart = 0; - LOG_DEBUG("Ignore false preamble detection"); - return false; - } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { - // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag - activeReceiveStart = 0; - LOG_DEBUG("Ignore false header detection"); - return false; + } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec)) { + if (!(irq & syncWordHeaderValidFlag)) { + // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag + activeReceiveStart = 0; + LOG_DEBUG("Ignore false preamble detection"); + return false; + } else { + uint32_t maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader)); + if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { + // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag + activeReceiveStart = 0; + LOG_DEBUG("Ignore false header detection"); + return false; + } + } } } return detected; @@ -172,7 +177,12 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p) printPacket("enqueue for send", p); LOG_DEBUG("txGood=%d,txRelay=%d,rxGood=%d,rxBad=%d", txGood, txRelay, rxGood, rxBad); - ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN; + bool dropped = false; + ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN; + + if (dropped) { + txDrop++; + } if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks packetPool.release(p); @@ -279,12 +289,7 @@ void RadioLibInterface::onNotify(uint32_t notification) // actual transmission as short as possible txp = txQueue.dequeue(); assert(txp); - bool sent = startSend(txp); - if (sent) { - // Packet has been sent, count it toward our TX airtime utilization. - uint32_t xmitMsec = getPacketTime(txp); - airTime->logAirtime(TX_LOG, xmitMsec); - } + startSend(txp); LOG_DEBUG("%d packets remain in the TX queue", txQueue.getMaxLen() - txQueue.getFree()); } } @@ -354,11 +359,15 @@ void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id) meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false); if (p) { p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr); - if (txQueue.enqueue(p)) { + bool dropped = false; + if (txQueue.enqueue(p, &dropped)) { LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis()); } else { packetPool.release(p); } + if (dropped) { + txDrop++; + } } } @@ -399,6 +408,10 @@ void RadioLibInterface::completeSending() sendingPacket = NULL; if (p) { + // Packet has been sent, count it toward our TX airtime utilization. + uint32_t xmitMsec = getPacketTime(p); + airTime->logAirtime(TX_LOG, xmitMsec); + txGood++; if (!isFromUs(p)) txRelay++; @@ -411,8 +424,6 @@ void RadioLibInterface::completeSending() void RadioLibInterface::handleReceiveInterrupt() { - uint32_t xmitMsec; - // when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race // Condition? if (!isReceiving) { @@ -425,12 +436,12 @@ void RadioLibInterface::handleReceiveInterrupt() // read the number of actually received bytes size_t length = iface->getPacketLength(); - xmitMsec = getPacketTime(length); + uint32_t rxMsec = getPacketTime(length, true); #ifndef DISABLE_WELCOME_UNSET if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { LOG_WARN("lora rx disabled: Region unset"); - airTime->logAirtime(RX_ALL_LOG, xmitMsec); + airTime->logAirtime(RX_ALL_LOG, rxMsec); return; } #endif @@ -446,7 +457,7 @@ void RadioLibInterface::handleReceiveInterrupt() radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags); rxBad++; - airTime->logAirtime(RX_ALL_LOG, xmitMsec); + airTime->logAirtime(RX_ALL_LOG, rxMsec); } else { // Skip the 4 headers that are at the beginning of the rxBuf @@ -456,7 +467,7 @@ void RadioLibInterface::handleReceiveInterrupt() if (payloadLen < 0) { LOG_WARN("Ignore received packet too short"); rxBad++; - airTime->logAirtime(RX_ALL_LOG, xmitMsec); + airTime->logAirtime(RX_ALL_LOG, rxMsec); } else { rxGood++; // altered packet with "from == 0" can do Remote Node Administration without permission @@ -494,7 +505,7 @@ void RadioLibInterface::handleReceiveInterrupt() printPacket("Lora RX", mp); - airTime->logAirtime(RX_LOG, xmitMsec); + airTime->logAirtime(RX_LOG, rxMsec); deliverToReceiver(mp); } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 3444b1a2c..833c88710 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -61,6 +61,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE); protected: + ModemType_t modemType = RADIOLIB_MODEM_LORA; + DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; } + PacketConfig_t getPacketConfig() const + { + return {.lora = {.preambleLength = preambleLength, + .implicitHeader = false, + .crcEnabled = true, + // We use auto LDRO, meaning it is enabled if the symbol time is >= 16msec + .ldrOptimize = (1 << sf) / bw >= 16}}; + } + /** * We use a meshtastic sync word, but hashed with the Channel name. For releases before 1.2 we used 0x12 (or for very old * loads 0x14) Note: do not use 0x34 - that is reserved for lorawan @@ -105,6 +116,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified * Debugging counts */ uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0; + uint16_t txDrop = 0; public: RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, @@ -209,6 +221,36 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ virtual void setStandby(); + /** + * Derive packet time either for a received (using header info) or a transmitted packet + */ + template uint32_t computePacketTime(T &lora, uint32_t pl, bool received) + { + if (received) { + // First get the actual coding rate and CRC status from the received packet + uint8_t rxCR; + bool hasCRC; + lora.getLoRaRxHeaderInfo(&rxCR, &hasCRC); + // Go from raw header value to denominator + if (rxCR < 5) { + rxCR += 4; + } else if (rxCR == 7) { + rxCR = 8; + } + + // Received packet configuration must be the same as configured, except for coding rate and CRC + DataRate_t dr = getDataRate(); + dr.lora.codingRate = rxCR; + + PacketConfig_t pc = getPacketConfig(); + pc.lora.crcEnabled = hasCRC; + + return lora.calculateTimeOnAir(modemType, dr, pc, pl) / 1000; + } + + return lora.getTimeOnAir(pl) / 1000; + } + const char *radioLibErr = "RadioLib err="; /** diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index b31c352fe..00066a7a3 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) If we don't add this, we will likely retransmit too early. */ for (auto i = pending.begin(); i != pending.end(); i++) { - i->second.nextTxMsec += iface->getPacketTime(p); + i->second.nextTxMsec += iface->getPacketTime(p, true); } return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 60637cbd1..5cf8bfa7d 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -35,6 +35,15 @@ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ 2) // max number of packets which can be in flight (either queued from reception or queued for sending) +static MemoryDynamic dynamicPool; +Allocator &packetPool = dynamicPool; +#elif defined(ARCH_STM32WL) +// On STM32 there isn't enough heap left over for the rest of the firmware if we allocate this statically. +// For now, make it dynamic again. +#define MAX_PACKETS \ + (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ + 2) // max number of packets which can be in flight (either queued from reception or queued for sending) + static MemoryDynamic dynamicPool; Allocator &packetPool = dynamicPool; #else diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 785338483..e1f07a32b 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -52,7 +52,7 @@ template bool SX126xInterface::init() pinMode(SX126X_POWER_EN, OUTPUT); #endif -#ifdef HELTEC_V4 +#if defined(USE_GC1109_PA) pinMode(LORA_PA_POWER, OUTPUT); digitalWrite(LORA_PA_POWER, HIGH); @@ -266,6 +266,7 @@ template void SX126xInterface::addReceiveMetadata(meshtastic_Mes // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus()); mp->rx_snr = lora.getSNR(); mp->rx_rssi = lround(lora.getRSSI()); + LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError()); } /** We override to turn on transmitter power as needed. @@ -352,7 +353,7 @@ template bool SX126xInterface::sleep() digitalWrite(SX126X_POWER_EN, LOW); #endif -#ifdef HELTEC_V4 +#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. @@ -367,7 +368,7 @@ template bool SX126xInterface::sleep() /** Some boards require GPIO control of tx vs rx paths */ template void SX126xInterface::setTransmitEnable(bool txon) { -#ifdef HELTEC_V4 +#if defined(USE_GC1109_PA) digitalWrite(LORA_PA_POWER, HIGH); digitalWrite(LORA_PA_EN, HIGH); digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0); diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index dc7024daa..b8f16ac6d 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -72,6 +72,8 @@ template class SX126xInterface : public RadioLibInterface virtual void setStandby() override; + uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); } + private: /** Some boards require GPIO control of tx vs rx paths */ void setTransmitEnable(bool txon); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index cbc98eeb1..80872af07 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -204,6 +204,7 @@ template void SX128xInterface::addReceiveMetadata(meshtastic_Mes // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus()); mp->rx_snr = lora.getSNR(); mp->rx_rssi = lround(lora.getRSSI()); + LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError()); } /** We override to turn on transmitter power as needed. diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h index bba31dab4..acdcbbb27 100644 --- a/src/mesh/SX128xInterface.h +++ b/src/mesh/SX128xInterface.h @@ -67,4 +67,6 @@ template class SX128xInterface : public RadioLibInterface virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void setStandby() override; + + uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); } }; diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index db9dedaaf..f4c33bd79 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size -#define meshtastic_ChannelSet_size 695 +#define meshtastic_ChannelSet_size 679 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h index d5573a1e2..9dc757ab4 100644 --- a/src/mesh/generated/meshtastic/channel.pb.h +++ b/src/mesh/generated/meshtastic/channel.pb.h @@ -34,9 +34,9 @@ typedef enum _meshtastic_Channel_Role { typedef struct _meshtastic_ModuleSettings { /* Bits of precision for the location sent in position packets. */ uint32_t position_precision; - /* Controls whether or not the phone / clients should mute the current channel + /* Controls whether or not the client / device should mute the current channel Useful for noisy public channels you don't necessarily want to disable */ - bool is_client_muted; + bool is_muted; } meshtastic_ModuleSettings; typedef PB_BYTES_ARRAY_T(32) meshtastic_ChannelSettings_psk_t; @@ -97,8 +97,6 @@ typedef struct _meshtastic_ChannelSettings { /* Per-channel module settings. */ bool has_module_settings; meshtastic_ModuleSettings module_settings; - /* Whether or not we should receive notifactions / alerts through this channel */ - bool mute; } meshtastic_ChannelSettings; /* A pair of a channel number, mode and the (sharable) settings for that channel */ @@ -130,16 +128,16 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0} +#define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default} #define meshtastic_ModuleSettings_init_default {0, 0} #define meshtastic_Channel_init_default {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN} -#define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0} +#define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero} #define meshtastic_ModuleSettings_init_zero {0, 0} #define meshtastic_Channel_init_zero {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_ModuleSettings_position_precision_tag 1 -#define meshtastic_ModuleSettings_is_client_muted_tag 2 +#define meshtastic_ModuleSettings_is_muted_tag 2 #define meshtastic_ChannelSettings_channel_num_tag 1 #define meshtastic_ChannelSettings_psk_tag 2 #define meshtastic_ChannelSettings_name_tag 3 @@ -147,7 +145,6 @@ extern "C" { #define meshtastic_ChannelSettings_uplink_enabled_tag 5 #define meshtastic_ChannelSettings_downlink_enabled_tag 6 #define meshtastic_ChannelSettings_module_settings_tag 7 -#define meshtastic_ChannelSettings_mute_tag 8 #define meshtastic_Channel_index_tag 1 #define meshtastic_Channel_settings_tag 2 #define meshtastic_Channel_role_tag 3 @@ -160,15 +157,14 @@ X(a, STATIC, SINGULAR, STRING, name, 3) \ X(a, STATIC, SINGULAR, FIXED32, id, 4) \ X(a, STATIC, SINGULAR, BOOL, uplink_enabled, 5) \ X(a, STATIC, SINGULAR, BOOL, downlink_enabled, 6) \ -X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7) \ -X(a, STATIC, SINGULAR, BOOL, mute, 8) +X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7) #define meshtastic_ChannelSettings_CALLBACK NULL #define meshtastic_ChannelSettings_DEFAULT NULL #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings #define meshtastic_ModuleSettings_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, position_precision, 1) \ -X(a, STATIC, SINGULAR, BOOL, is_client_muted, 2) +X(a, STATIC, SINGULAR, BOOL, is_muted, 2) #define meshtastic_ModuleSettings_CALLBACK NULL #define meshtastic_ModuleSettings_DEFAULT NULL @@ -191,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size -#define meshtastic_ChannelSettings_size 74 -#define meshtastic_Channel_size 89 +#define meshtastic_ChannelSettings_size 72 +#define meshtastic_Channel_size 87 #define meshtastic_ModuleSettings_size 8 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h index d9eb90773..b99fb10b9 100644 --- a/src/mesh/generated/meshtastic/device_ui.pb.h +++ b/src/mesh/generated/meshtastic/device_ui.pb.h @@ -68,6 +68,8 @@ typedef enum _meshtastic_Language { meshtastic_Language_BULGARIAN = 17, /* Czech */ meshtastic_Language_CZECH = 18, + /* Danish */ + meshtastic_Language_DANISH = 19, /* Simplified Chinese (experimental) */ meshtastic_Language_SIMPLIFIED_CHINESE = 30, /* Traditional Chinese (experimental) */ diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index b5b116137..7fab82ff7 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -360,8 +360,8 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2293 -#define meshtastic_ChannelFile_size 734 +#define meshtastic_BackupPreferences_size 2277 +#define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 #define meshtastic_PositionLite_size 28 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index d8d2f2e8a..059af57ae 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -282,6 +282,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 = 113, /* LilyGo T-Watch Ultra */ meshtastic_HardwareModel_T_WATCH_ULTRA = 114, + /* Elecrow ThinkNode M3 */ + meshtastic_HardwareModel_THINKNODE_M3 = 115, /* ------------------------------------------------------------------------------------------------------------------------------------------ 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/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 9af095e78..dec89ba15 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -101,7 +101,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* SEN5X PM SENSORS */ meshtastic_TelemetrySensorType_SEN5X = 43, /* TSL2561 light sensor */ - meshtastic_TelemetrySensorType_TSL2561 = 44 + meshtastic_TelemetrySensorType_TSL2561 = 44, + /* BH1750 light sensor */ + meshtastic_TelemetrySensorType_BH1750 = 45 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -357,6 +359,8 @@ typedef struct _meshtastic_LocalStats { uint32_t heap_total_bytes; /* Number of bytes free in the heap */ uint32_t heap_free_bytes; + /* Number of packets that were dropped because the transmit queue was full. */ + uint16_t num_tx_dropped; } meshtastic_LocalStats; /* Health telemetry metrics */ @@ -436,8 +440,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL2561 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL2561+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BH1750 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BH1750+1)) @@ -454,7 +458,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} @@ -463,7 +467,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -551,6 +555,7 @@ extern "C" { #define meshtastic_LocalStats_num_tx_relay_canceled_tag 11 #define meshtastic_LocalStats_heap_total_bytes_tag 12 #define meshtastic_LocalStats_heap_free_bytes_tag 13 +#define meshtastic_LocalStats_num_tx_dropped_tag 14 #define meshtastic_HealthMetrics_heart_bpm_tag 1 #define meshtastic_HealthMetrics_spO2_tag 2 #define meshtastic_HealthMetrics_temperature_tag 3 @@ -672,7 +677,8 @@ X(a, STATIC, SINGULAR, UINT32, num_rx_dupe, 9) \ X(a, STATIC, SINGULAR, UINT32, num_tx_relay, 10) \ X(a, STATIC, SINGULAR, UINT32, num_tx_relay_canceled, 11) \ X(a, STATIC, SINGULAR, UINT32, heap_total_bytes, 12) \ -X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13) +X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13) \ +X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14) #define meshtastic_LocalStats_CALLBACK NULL #define meshtastic_LocalStats_DEFAULT NULL @@ -749,7 +755,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_EnvironmentMetrics_size 113 #define meshtastic_HealthMetrics_size 11 #define meshtastic_HostMetrics_size 264 -#define meshtastic_LocalStats_size 72 +#define meshtastic_LocalStats_size 76 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 81 #define meshtastic_Telemetry_size 272 diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 1133ad424..18f67706a 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -94,11 +94,13 @@ static void onNetworkConnected() // ESPmDNS (ESP32) and SimpleMDNS (RP2040) have slightly different APIs for adding TXT records #ifdef ARCH_ESP32 MDNS.addServiceTxt("meshtastic", "tcp", "shortname", String(owner.short_name)); - MDNS.addServiceTxt("meshtastic", "tcp", "id", String(owner.id)); + MDNS.addServiceTxt("meshtastic", "tcp", "id", String(nodeDB->getNodeId().c_str())); + MDNS.addServiceTxt("meshtastic", "tcp", "pio_env", optstr(APP_ENV)); // ESP32 prints obtained IP address in WiFiEvent #elif defined(ARCH_RP2040) MDNS.addServiceTxt("meshtastic", "shortname", owner.short_name); - MDNS.addServiceTxt("meshtastic", "id", owner.id); + MDNS.addServiceTxt("meshtastic", "id", nodeDB->getNodeId().c_str()); + MDNS.addServiceTxt("meshtastic", "pio_env", optstr(APP_ENV)); LOG_INFO("Obtained IP address: %s", WiFi.localIP().toString().c_str()); #endif } diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 7544db121..d300ff53b 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -554,10 +554,8 @@ void AdminModule::handleSetOwner(const meshtastic_User &o) changed |= strcmp(owner.short_name, o.short_name); strncpy(owner.short_name, o.short_name, sizeof(owner.short_name)); } - if (*o.id) { - changed |= strcmp(owner.id, o.id); - strncpy(owner.id, o.id, sizeof(owner.id)); - } + snprintf(owner.id, sizeof(owner.id), "!%08x", nodeDB->getNodeNum()); + if (owner.is_licensed != o.is_licensed) { changed = 1; owner.is_licensed = o.is_licensed; @@ -784,11 +782,24 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) if (myRegion->dutyCycle < 100) { config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit } + // Compare the entire string, we are sure of the length as a topic has never been set if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) { sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG; } } + if (config.lora.region != myRegion->code) { + // Region has changed so check whether there is a regulatory one we should be using instead. + // Additionally as a side-effect, assume a new value under myRegion + initRegion(); + + if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) { + // Default root is in use, so subscribe to the appropriate MQTT topic for this region + sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); + } + + changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG; + } break; } case meshtastic_Config_bluetooth_tag: diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index e9f52bb7d..9f95a9e20 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -255,7 +255,7 @@ void CannedMessageModule::updateDestinationSelectionList() for (size_t i = 0; i < numMeshNodes; ++i) { meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); - if (!node || node->num == myNodeNum) + if (!node || node->num == myNodeNum || !node->has_user || node->user.public_key.size != 32) continue; const String &nodeName = node->user.long_name; @@ -976,6 +976,8 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha LOG_INFO("Proactively adding %x as favorite node", p->to); nodeDB->set_favorite(true, p->to); screen->setFrames(graphics::Screen::FOCUS_PRESERVE); + p->pki_encrypted = true; + p->channel = 0; } // Send to mesh and phone (even if no phone connected, to track ACKs) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 2346cd299..2b1730e9c 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -94,8 +94,8 @@ int32_t ExternalNotificationModule::runOnce() // audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying(); #endif - if ((nagCycleCutoff < millis()) && !isRtttlPlaying) { - // let the song finish if we reach timeout + if ((nagCycleCutoff <= millis())) { + // Turn off external notification immediately when timeout is reached, regardless of song state nagCycleCutoff = UINT32_MAX; LOG_INFO("Turning off external notification: "); for (int i = 0; i < 3; i++) { @@ -103,7 +103,6 @@ int32_t ExternalNotificationModule::runOnce() externalTurnedOn[i] = 0; LOG_INFO("%d ", i); } - LOG_INFO(""); #ifdef HAS_I2S // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library // T-Deck uses GPIO0 as trackball button, so restore the mode @@ -510,7 +509,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - if (moduleConfig.external_notification.alert_message && !ch.settings.mute) { + if (moduleConfig.external_notification.alert_message && + (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { LOG_INFO("externalNotificationModule - Notification Module"); isNagging = true; setExternalState(0, true); @@ -521,7 +521,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - if (moduleConfig.external_notification.alert_message_vibra && !ch.settings.mute) { + if (moduleConfig.external_notification.alert_message_vibra && + (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { LOG_INFO("externalNotificationModule - Notification Module (Vibra)"); isNagging = true; setExternalState(1, true); @@ -532,7 +533,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - if (moduleConfig.external_notification.alert_message_buzzer && !ch.settings.mute) { + if (moduleConfig.external_notification.alert_message_buzzer && + (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { LOG_INFO("externalNotificationModule - Notification Module (Buzzer)"); if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || (!isBroadcast(mp.to) && isToUs(&mp))) { diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 02962dd59..e477574dd 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -148,7 +148,8 @@ void setupModules() } #endif #if !MESHTASTIC_EXCLUDE_ATAK - if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK, meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) { + if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) { atakPluginModule = new AtakPluginModule(); } #endif diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 2c3c274d2..aaab019d6 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -30,14 +30,32 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes bool wasBroadcast = isBroadcast(mp.to); + // LOG_DEBUG("did encode"); // if user has changed while packet was not for us, inform phone - if (hasChanged && !wasBroadcast && !isToUs(&mp)) - service->sendToPhone(packetPool.allocCopy(mp)); + if (hasChanged && !wasBroadcast && !isToUs(&mp)) { + auto packetCopy = packetPool.allocCopy(mp); // Keep a copy of the packet for later analysis + + // Re-encode the user protobuf, as we have stripped out the user.id + packetCopy->decoded.payload.size = pb_encode_to_bytes( + packetCopy->decoded.payload.bytes, sizeof(packetCopy->decoded.payload.bytes), &meshtastic_User_msg, &p); + + service->sendToPhone(packetCopy); + } // LOG_DEBUG("did handleReceived"); return false; // Let others look at this message also if they want } +void NodeInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p) +{ + // Coerce user.id to be derived from the node number + snprintf(p->id, sizeof(p->id), "!%08x", getFrom(&mp)); + + // Re-encode the altered protobuf back into the packet + mp.decoded.payload.size = + pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_User_msg, p); +} + void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t channel, bool _shorterTimeout) { // cancel any not yet sent (now stale) position packets @@ -95,6 +113,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() u.public_key.size = 0; } + // FIXME: Clear the user.id field since it should be derived from node number on the receiving end + // u.id[0] = '\0'; + + // Ensure our user.id is derived correctly + strcpy(u.id, nodeDB->getNodeId().c_str()); + LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name); lastSentToMesh = millis(); return allocDataProtobuf(u); diff --git a/src/modules/NodeInfoModule.h b/src/modules/NodeInfoModule.h index c1fb9ccce..572b81700 100644 --- a/src/modules/NodeInfoModule.h +++ b/src/modules/NodeInfoModule.h @@ -30,6 +30,9 @@ class NodeInfoModule : public ProtobufModule, private concurren */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *p) override; + /** Called to alter received User protobuf */ + virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p) override; + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked * so that subclasses can (optionally) send a response back to the original sender. */ virtual meshtastic_MeshPacket *allocReply() override; diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 3d78d0dc9..026b3028d 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -159,6 +159,7 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket LOG_DEBUG("---- Received Packet:"); LOG_DEBUG("mp.from %d", mp.from); LOG_DEBUG("mp.rx_snr %f", mp.rx_snr); + LOG_DEBUG("mp.rx_rssi %f", mp.rx_rssi); LOG_DEBUG("mp.hop_limit %d", mp.hop_limit); LOG_DEBUG("---- Node Information of Received Packet (mp.from):"); LOG_DEBUG("n->user.long_name %s", n->user.long_name); @@ -234,8 +235,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) } // Print the CSV header - if (fileToWrite.println( - "time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload")) { + if (fileToWrite.println("time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx " + "snr,distance,hop limit,payload,rx rssi")) { LOG_INFO("File was written"); } else { LOG_ERROR("File write failed"); @@ -297,6 +298,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) // TODO: If quotes are found in the payload, it has to be escaped. fileToAppend.printf("\"%s\"\n", p.payload.bytes); + fileToAppend.printf("%i,", mp.rx_rssi); // RX RSSI + fileToAppend.flush(); fileToAppend.close(); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 7485f1c2d..a9ec8f6a8 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -67,7 +67,7 @@ SerialModuleRadio *serialModuleRadio; defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial; -#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) +#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL) SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial1; #else diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index ad148b759..066b9361d 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -26,7 +26,8 @@ int32_t DeviceTelemetryModule::runOnce() Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() && - config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { + config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN && + moduleConfig.telemetry.device_telemetry_enabled) { sendTelemetry(); lastSentToMesh = uptimeLastMs; } else if (service->isToPhoneQueueEmpty()) { @@ -121,6 +122,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad; telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad; telemetry.variant.local_stats.num_tx_relay = RadioLibInterface::instance->txRelay; + telemetry.variant.local_stats.num_tx_dropped = RadioLibInterface::instance->txDrop; } #ifdef ARCH_PORTDUINO if (SimRadio::instance) { @@ -128,6 +130,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() telemetry.variant.local_stats.num_packets_rx = SimRadio::instance->rxGood + SimRadio::instance->rxBad; telemetry.variant.local_stats.num_packets_rx_bad = SimRadio::instance->rxBad; telemetry.variant.local_stats.num_tx_relay = SimRadio::instance->txRelay; + telemetry.variant.local_stats.num_tx_dropped = SimRadio::instance->txDrop; } #else telemetry.variant.local_stats.heap_total_bytes = memGet.getHeapSize(); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 8ac160f8b..2337af808 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -35,175 +35,103 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c } #if __has_include() #include "Sensor/AHT10.h" -AHT10Sensor aht10Sensor; -#else -NullSensor aht10Sensor; #endif + #if __has_include() #include "Sensor/BME280Sensor.h" -BME280Sensor bme280Sensor; -#else -NullSensor bmp280Sensor; #endif #if __has_include() #include "Sensor/BMP085Sensor.h" -BMP085Sensor bmp085Sensor; -#else -NullSensor bmp085Sensor; #endif #if __has_include() #include "Sensor/BMP280Sensor.h" -BMP280Sensor bmp280Sensor; -#else -NullSensor bme280Sensor; #endif #if __has_include() #include "Sensor/LTR390UVSensor.h" -LTR390UVSensor ltr390uvSensor; -#else -NullSensor ltr390uvSensor; #endif #if __has_include() #include "Sensor/BME680Sensor.h" -BME680Sensor bme680Sensor; -#else -NullSensor bme680Sensor; #endif #if __has_include() #include "Sensor/DPS310Sensor.h" -DPS310Sensor dps310Sensor; -#else -NullSensor dps310Sensor; #endif #if __has_include() #include "Sensor/MCP9808Sensor.h" -MCP9808Sensor mcp9808Sensor; -#else -NullSensor mcp9808Sensor; #endif #if __has_include() #include "Sensor/SHT31Sensor.h" -SHT31Sensor sht31Sensor; -#else -NullSensor sht31Sensor; #endif #if __has_include() #include "Sensor/LPS22HBSensor.h" -LPS22HBSensor lps22hbSensor; -#else -NullSensor lps22hbSensor; #endif #if __has_include() #include "Sensor/SHTC3Sensor.h" -SHTC3Sensor shtc3Sensor; -#else -NullSensor shtc3Sensor; #endif #if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && RAK_4631 == 1 #include "Sensor/RAK12035Sensor.h" -RAK12035Sensor rak12035Sensor; -#else -NullSensor rak12035Sensor; #endif #if __has_include() #include "Sensor/VEML7700Sensor.h" -VEML7700Sensor veml7700Sensor; -#else -NullSensor veml7700Sensor; #endif #if __has_include() #include "Sensor/TSL2591Sensor.h" -TSL2591Sensor tsl2591Sensor; -#else -NullSensor tsl2591Sensor; #endif #if __has_include() #include "Sensor/OPT3001Sensor.h" -OPT3001Sensor opt3001Sensor; -#else -NullSensor opt3001Sensor; #endif #if __has_include() #include "Sensor/SHT4XSensor.h" -SHT4XSensor sht4xSensor; -#else -NullSensor sht4xSensor; #endif #if __has_include() #include "Sensor/MLX90632Sensor.h" -MLX90632Sensor mlx90632Sensor; -#else -NullSensor mlx90632Sensor; #endif #if __has_include() #include "Sensor/DFRobotLarkSensor.h" -DFRobotLarkSensor dfRobotLarkSensor; -#else -NullSensor dfRobotLarkSensor; #endif #if __has_include() #include "Sensor/DFRobotGravitySensor.h" -DFRobotGravitySensor dfRobotGravitySensor; -#else -NullSensor dfRobotGravitySensor; #endif #if __has_include() #include "Sensor/NAU7802Sensor.h" -NAU7802Sensor nau7802Sensor; -#else -NullSensor nau7802Sensor; #endif #if __has_include() #include "Sensor/BMP3XXSensor.h" -BMP3XXSensor bmp3xxSensor; -#else -NullSensor bmp3xxSensor; #endif #if __has_include() #include "Sensor/PCT2075Sensor.h" -PCT2075Sensor pct2075Sensor; -#else -NullSensor pct2075Sensor; #endif -RCWL9620Sensor rcwl9620Sensor; -CGRadSensSensor cgRadSens; - #endif #ifdef T1000X_SENSOR_EN #include "Sensor/T1000xSensor.h" -T1000xSensor t1000xSensor; #endif + #ifdef SENSECAP_INDICATOR #include "Sensor/IndicatorSensor.h" -IndicatorSensor indicatorSensor; #endif #if __has_include() #include "Sensor/TSL2561Sensor.h" -TSL2561Sensor tsl2561Sensor; -#else -NullSensor tsl2561Sensor; #endif #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 @@ -212,6 +140,132 @@ NullSensor tsl2561Sensor; #include "graphics/ScreenFonts.h" #include +#include + +static std::forward_list sensors; + +template void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type) +{ + ScanI2C::FoundDevice dev = i2cScanner->find(type); + if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) { + TelemetrySensor *sensor = new T(); +#if WIRE_INTERFACES_COUNT > 1 + TwoWire *bus = ScanI2CTwoWire::fetchI2CBus(dev.address); + if (dev.address.port != ScanI2C::I2CPort::WIRE1 && sensor->onlyWire1()) { + // This sensor only works on Wire (Wire1 is not supported) + delete sensor; + return; + } +#else + TwoWire *bus = &Wire; +#endif + if (sensor->initDevice(bus, &dev)) { + sensors.push_front(sensor); + return; + } + // destroy sensor + delete sensor; + } +} + +void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) +{ + if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) { + return; + } + LOG_INFO("Environment Telemetry adding I2C devices..."); + + // order by priority of metrics/values (low top, high bottom) + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#ifdef T1000X_SENSOR_EN + // Not a real I2C device + addSensor(i2cScanner, ScanI2C::DeviceType::NONE); +#else +#ifdef SENSECAP_INDICATOR + // Not a real I2C device, uses UART + addSensor(i2cScanner, ScanI2C::DeviceType::NONE); +#endif + addSensor(i2cScanner, ScanI2C::DeviceType::RCWL9620); + addSensor(i2cScanner, ScanI2C::DeviceType::CGRADSENS); +#endif +#endif + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::AHT10); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::BMP_085); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::BME_280); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::LTR390UV); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::BME_680); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::BMP_280); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::DPS310); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::MCP9808); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::SHT31); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::LPS22HB); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::SHTC3); +#endif +#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && RAK_4631 == 1 + addSensor(i2cScanner, ScanI2C::DeviceType::RAK12035); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::VEML7700); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::TSL2591); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::OPT3001); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::SHT4X); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::MLX90632); +#endif + +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::BMP_3XX); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::PCT2075); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::TSL2561); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::NAU7802); +#endif + +#endif +} + int32_t EnvironmentTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { @@ -244,81 +298,27 @@ int32_t EnvironmentTelemetryModule::runOnce() if (moduleConfig.telemetry.environment_measurement_enabled || ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) { LOG_INFO("Environment Telemetry: init"); -#ifdef SENSECAP_INDICATOR - result = indicatorSensor.runOnce(); -#endif + + // check if we have at least one sensor + if (!sensors.empty()) { + result = DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + #ifdef T1000X_SENSOR_EN - result = t1000xSensor.runOnce(); #elif !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL - if (dfRobotLarkSensor.hasSensor()) - result = dfRobotLarkSensor.runOnce(); - if (dfRobotGravitySensor.hasSensor()) - result = dfRobotGravitySensor.runOnce(); - if (bmp085Sensor.hasSensor()) - result = bmp085Sensor.runOnce(); -#if __has_include() - if (bmp280Sensor.hasSensor()) - result = bmp280Sensor.runOnce(); -#endif - if (bme280Sensor.hasSensor()) - result = bme280Sensor.runOnce(); - if (ltr390uvSensor.hasSensor()) - result = ltr390uvSensor.runOnce(); - if (bmp3xxSensor.hasSensor()) - result = bmp3xxSensor.runOnce(); - if (bme680Sensor.hasSensor()) - result = bme680Sensor.runOnce(); - if (dps310Sensor.hasSensor()) - result = dps310Sensor.runOnce(); - if (mcp9808Sensor.hasSensor()) - result = mcp9808Sensor.runOnce(); - if (shtc3Sensor.hasSensor()) - result = shtc3Sensor.runOnce(); - if (lps22hbSensor.hasSensor()) - result = lps22hbSensor.runOnce(); - if (sht31Sensor.hasSensor()) - result = sht31Sensor.runOnce(); - if (sht4xSensor.hasSensor()) - result = sht4xSensor.runOnce(); if (ina219Sensor.hasSensor()) result = ina219Sensor.runOnce(); if (ina260Sensor.hasSensor()) result = ina260Sensor.runOnce(); if (ina3221Sensor.hasSensor()) result = ina3221Sensor.runOnce(); - if (veml7700Sensor.hasSensor()) - result = veml7700Sensor.runOnce(); - if (tsl2591Sensor.hasSensor()) - result = tsl2591Sensor.runOnce(); - if (opt3001Sensor.hasSensor()) - result = opt3001Sensor.runOnce(); - if (rcwl9620Sensor.hasSensor()) - result = rcwl9620Sensor.runOnce(); - if (aht10Sensor.hasSensor()) - result = aht10Sensor.runOnce(); - if (mlx90632Sensor.hasSensor()) - result = mlx90632Sensor.runOnce(); - if (nau7802Sensor.hasSensor()) - result = nau7802Sensor.runOnce(); if (max17048Sensor.hasSensor()) result = max17048Sensor.runOnce(); - if (cgRadSens.hasSensor()) - result = cgRadSens.runOnce(); - if (tsl2561Sensor.hasSensor()) - result = tsl2561Sensor.runOnce(); - if (pct2075Sensor.hasSensor()) - result = pct2075Sensor.runOnce(); // this only works on the wismesh hub with the solar option. This is not an I2C sensor, so we don't need the // sensormap here. #ifdef HAS_RAKPROT - result = rak9154Sensor.runOnce(); #endif -#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && RAK_4631 == 1 - if (rak12035Sensor.hasSensor()) { - result = rak12035Sensor.runOnce(); - } -#endif #endif } // it's possible to have this module enabled, only for displaying values on the screen. @@ -328,11 +328,13 @@ int32_t EnvironmentTelemetryModule::runOnce() // if we somehow got to a second run of this module with measurement disabled, then just wait forever if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) { return disable(); - } else { -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL - if (bme680Sensor.hasSensor()) - result = bme680Sensor.runTrigger(); -#endif + } + + for (TelemetrySensor *sensor : sensors) { + uint32_t delay = sensor->runOnce(); + if (delay < result) { + result = delay; + } } if (((lastSentToMesh == 0) || @@ -359,6 +361,7 @@ bool EnvironmentTelemetryModule::wantUIFrame() return moduleConfig.telemetry.environment_screen_enabled; } +#if HAS_SCREEN void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // === Setup display === @@ -508,6 +511,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt currentY += rowHeight; } } +#endif bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) { @@ -550,72 +554,12 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m m->which_variant = meshtastic_Telemetry_environment_metrics_tag; m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero; -#ifdef SENSECAP_INDICATOR - valid = valid && indicatorSensor.getMetrics(m); - hasSensor = true; -#endif -#ifdef T1000X_SENSOR_EN // add by WayenWeng - valid = valid && t1000xSensor.getMetrics(m); - hasSensor = true; -#else - if (dfRobotLarkSensor.hasSensor()) { - valid = valid && dfRobotLarkSensor.getMetrics(m); - hasSensor = true; - } - if (dfRobotGravitySensor.hasSensor()) { - valid = valid && dfRobotGravitySensor.getMetrics(m); - hasSensor = true; - } - if (sht31Sensor.hasSensor()) { - valid = valid && sht31Sensor.getMetrics(m); - hasSensor = true; - } - if (sht4xSensor.hasSensor()) { - valid = valid && sht4xSensor.getMetrics(m); - hasSensor = true; - } - if (lps22hbSensor.hasSensor()) { - valid = valid && lps22hbSensor.getMetrics(m); - hasSensor = true; - } - if (shtc3Sensor.hasSensor()) { - valid = valid && shtc3Sensor.getMetrics(m); - hasSensor = true; - } - if (bmp085Sensor.hasSensor()) { - valid = valid && bmp085Sensor.getMetrics(m); - hasSensor = true; - } -#if __has_include() - if (bmp280Sensor.hasSensor()) { - valid = valid && bmp280Sensor.getMetrics(m); - hasSensor = true; - } -#endif - if (bme280Sensor.hasSensor()) { - valid = valid && bme280Sensor.getMetrics(m); - hasSensor = true; - } - if (ltr390uvSensor.hasSensor()) { - valid = valid && ltr390uvSensor.getMetrics(m); - hasSensor = true; - } - if (bmp3xxSensor.hasSensor()) { - valid = valid && bmp3xxSensor.getMetrics(m); - hasSensor = true; - } - if (bme680Sensor.hasSensor()) { - valid = valid && bme680Sensor.getMetrics(m); - hasSensor = true; - } - if (dps310Sensor.hasSensor()) { - valid = valid && dps310Sensor.getMetrics(m); - hasSensor = true; - } - if (mcp9808Sensor.hasSensor()) { - valid = valid && mcp9808Sensor.getMetrics(m); + for (TelemetrySensor *sensor : sensors) { + valid = valid && sensor->getMetrics(m); hasSensor = true; } + +#ifndef T1000X_SENSOR_EN if (ina219Sensor.hasSensor()) { valid = valid && ina219Sensor.getMetrics(m); hasSensor = true; @@ -628,78 +572,14 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && ina3221Sensor.getMetrics(m); hasSensor = true; } - if (veml7700Sensor.hasSensor()) { - valid = valid && veml7700Sensor.getMetrics(m); - hasSensor = true; - } - if (tsl2591Sensor.hasSensor()) { - valid = valid && tsl2591Sensor.getMetrics(m); - hasSensor = true; - } - if (opt3001Sensor.hasSensor()) { - valid = valid && opt3001Sensor.getMetrics(m); - hasSensor = true; - } - if (mlx90632Sensor.hasSensor()) { - valid = valid && mlx90632Sensor.getMetrics(m); - hasSensor = true; - } - if (rcwl9620Sensor.hasSensor()) { - valid = valid && rcwl9620Sensor.getMetrics(m); - hasSensor = true; - } - if (nau7802Sensor.hasSensor()) { - valid = valid && nau7802Sensor.getMetrics(m); - hasSensor = true; - } - if (tsl2561Sensor.hasSensor()) { - valid = valid && tsl2561Sensor.getMetrics(m); - hasSensor = true; - } - if (aht10Sensor.hasSensor()) { - if (!bmp280Sensor.hasSensor() && !bmp3xxSensor.hasSensor()) { - valid = valid && aht10Sensor.getMetrics(m); - hasSensor = true; - } else if (bmp280Sensor.hasSensor()) { - // prefer bmp280 temp if both sensors are present, fetch only humidity - meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero; - LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0"); - aht10Sensor.getMetrics(&m_ahtx); - m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; - m->variant.environment_metrics.has_relative_humidity = m_ahtx.variant.environment_metrics.has_relative_humidity; - } else { - // prefer bmp3xx temp if both sensors are present, fetch only humidity - meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero; - LOG_INFO("AHTX0+BMP3XX module detected: using temp from BMP3XX and humy from AHTX0"); - aht10Sensor.getMetrics(&m_ahtx); - m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; - m->variant.environment_metrics.has_relative_humidity = m_ahtx.variant.environment_metrics.has_relative_humidity; - } - } if (max17048Sensor.hasSensor()) { valid = valid && max17048Sensor.getMetrics(m); hasSensor = true; } - if (cgRadSens.hasSensor()) { - valid = valid && cgRadSens.getMetrics(m); - hasSensor = true; - } - if (pct2075Sensor.hasSensor()) { - valid = valid && pct2075Sensor.getMetrics(m); - hasSensor = true; - } +#endif #ifdef HAS_RAKPROT valid = valid && rak9154Sensor.getMetrics(m); hasSensor = true; -#endif -#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && \ - RAK_4631 == \ - 1 // Not really needed, but may as well just skip at a lower level it if no library or not a RAK_4631 - if (rak12035Sensor.hasSensor()) { - valid = valid && rak12035Sensor.getMetrics(m); - hasSensor = true; - } -#endif #endif return valid && hasSensor; } @@ -737,11 +617,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; m.which_variant = meshtastic_Telemetry_environment_metrics_tag; m.time = getTime(); -#ifdef T1000X_SENSOR_EN - if (t1000xSensor.getMetrics(&m)) { -#else + if (getEnvironmentTelemetry(&m)) { -#endif LOG_INFO("Send: barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f", m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, @@ -803,71 +680,13 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule { AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED; #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL - if (dfRobotLarkSensor.hasSensor()) { - result = dfRobotLarkSensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (dfRobotGravitySensor.hasSensor()) { - result = dfRobotGravitySensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (sht31Sensor.hasSensor()) { - result = sht31Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (lps22hbSensor.hasSensor()) { - result = lps22hbSensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (shtc3Sensor.hasSensor()) { - result = shtc3Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (bmp085Sensor.hasSensor()) { - result = bmp085Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (bmp280Sensor.hasSensor()) { - result = bmp280Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (bme280Sensor.hasSensor()) { - result = bme280Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (ltr390uvSensor.hasSensor()) { - result = ltr390uvSensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (bmp3xxSensor.hasSensor()) { - result = bmp3xxSensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (bme680Sensor.hasSensor()) { - result = bme680Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (dps310Sensor.hasSensor()) { - result = dps310Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (mcp9808Sensor.hasSensor()) { - result = mcp9808Sensor.handleAdminMessage(mp, request, response); + + for (TelemetrySensor *sensor : sensors) { + result = sensor->handleAdminMessage(mp, request, response); if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (ina219Sensor.hasSensor()) { result = ina219Sensor.handleAdminMessage(mp, request, response); if (result != AdminMessageHandleResult::NOT_HANDLED) @@ -883,60 +702,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } - if (veml7700Sensor.hasSensor()) { - result = veml7700Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (tsl2591Sensor.hasSensor()) { - result = tsl2591Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (opt3001Sensor.hasSensor()) { - result = opt3001Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (mlx90632Sensor.hasSensor()) { - result = mlx90632Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (rcwl9620Sensor.hasSensor()) { - result = rcwl9620Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (nau7802Sensor.hasSensor()) { - result = nau7802Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } - if (aht10Sensor.hasSensor()) { - result = aht10Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } if (max17048Sensor.hasSensor()) { result = max17048Sensor.handleAdminMessage(mp, request, response); if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } - if (cgRadSens.hasSensor()) { - result = cgRadSens.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } -#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && \ - RAK_4631 == \ - 1 // Not really needed, but may as well just skip it at a lower level if no library or not a RAK_4631 - if (rak12035Sensor.hasSensor()) { - result = rak12035Sensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } -#endif #endif return result; } diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index d70c063fc..6e4ce82e7 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -11,10 +11,13 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "NodeDB.h" #include "ProtobufModule.h" +#include "detect/ScanI2CConsumer.h" #include #include -class EnvironmentTelemetryModule : private concurrency::OSThread, public ProtobufModule +class EnvironmentTelemetryModule : private concurrency::OSThread, + public ScanI2CConsumer, + public ProtobufModule { CallbackObserver nodeStatusObserver = CallbackObserver(this, @@ -22,7 +25,7 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu public: EnvironmentTelemetryModule() - : concurrency::OSThread("EnvironmentTelemetry"), + : concurrency::OSThread("EnvironmentTelemetry"), ScanI2CConsumer(), ProtobufModule("EnvironmentTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) { lastMeasurementPacket = nullptr; @@ -56,6 +59,8 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu meshtastic_AdminMessage *request, meshtastic_AdminMessage *response) override; + void i2cScanFinished(ScanI2C *i2cScanner); + private: bool firstTime = 1; meshtastic_MeshPacket *lastMeasurementPacket; diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 479861a2e..e69ee3931 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -108,6 +108,7 @@ bool PowerTelemetryModule::wantUIFrame() return moduleConfig.telemetry.power_screen_enabled; } +#if HAS_SCREEN void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->clear(); @@ -165,6 +166,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s drawLine("Ch3", m.ch3_voltage, m.ch3_current); } } +#endif bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) { diff --git a/src/modules/Telemetry/Sensor/AHT10.cpp b/src/modules/Telemetry/Sensor/AHT10.cpp index 35934533b..52fdc05c0 100644 --- a/src/modules/Telemetry/Sensor/AHT10.cpp +++ b/src/modules/Telemetry/Sensor/AHT10.cpp @@ -15,20 +15,16 @@ AHT10Sensor::AHT10Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_AHT10, "AHT10") {} -int32_t AHT10Sensor::runOnce() +bool AHT10Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } aht10 = Adafruit_AHTX0(); - status = aht10.begin(nodeTelemetrySensorsMap[sensorType].second, 0, nodeTelemetrySensorsMap[sensorType].first); + status = aht10.begin(bus, 0, dev->address.address); - return initI2CSensor(); + initI2CSensor(); + return status; } -void AHT10Sensor::setup() {} - bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement) { LOG_DEBUG("AHT10 getMetrics"); @@ -36,11 +32,16 @@ bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement) sensors_event_t humidity, temp; aht10.getEvent(&humidity, &temp); - measurement->variant.environment_metrics.has_temperature = true; - measurement->variant.environment_metrics.has_relative_humidity = true; + // prefer other sensors like bmp280, bmp3xx + if (!measurement->variant.environment_metrics.has_temperature) { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.temperature = temp.temperature; + } - measurement->variant.environment_metrics.temperature = temp.temperature; - measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity; + if (!measurement->variant.environment_metrics.has_relative_humidity) { + measurement->variant.environment_metrics.has_relative_humidity = true; + measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity; + } return true; } diff --git a/src/modules/Telemetry/Sensor/AHT10.h b/src/modules/Telemetry/Sensor/AHT10.h index a6fa19952..ab3f5806c 100644 --- a/src/modules/Telemetry/Sensor/AHT10.h +++ b/src/modules/Telemetry/Sensor/AHT10.h @@ -15,13 +15,10 @@ class AHT10Sensor : public TelemetrySensor private: Adafruit_AHTX0 aht10; - protected: - virtual void setup() override; - public: AHT10Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.cpp b/src/modules/Telemetry/Sensor/BME280Sensor.cpp index d7b0a8a38..779b2e603 100644 --- a/src/modules/Telemetry/Sensor/BME280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME280Sensor.cpp @@ -10,13 +10,13 @@ BME280Sensor::BME280Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME280, "BME280") {} -int32_t BME280Sensor::runOnce() +bool BME280Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + status = bme280.begin(dev->address.address, bus); + if (!status) { + return status; } - status = bme280.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); bme280.setSampling(Adafruit_BME280::MODE_FORCED, Adafruit_BME280::SAMPLING_X1, // Temp. oversampling @@ -24,11 +24,10 @@ int32_t BME280Sensor::runOnce() Adafruit_BME280::SAMPLING_X1, // Humidity oversampling Adafruit_BME280::FILTER_OFF, Adafruit_BME280::STANDBY_MS_1000); - return initI2CSensor(); + initI2CSensor(); + return status; } -void BME280Sensor::setup() {} - bool BME280Sensor::getMetrics(meshtastic_Telemetry *measurement) { measurement->variant.environment_metrics.has_temperature = true; diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.h b/src/modules/Telemetry/Sensor/BME280Sensor.h index d1e21c8d5..fadae46cd 100644 --- a/src/modules/Telemetry/Sensor/BME280Sensor.h +++ b/src/modules/Telemetry/Sensor/BME280Sensor.h @@ -11,13 +11,10 @@ class BME280Sensor : public TelemetrySensor private: Adafruit_BME280 bme280; - protected: - virtual void setup() override; - public: BME280Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index fce029deb..95f3dc5f0 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -10,7 +10,7 @@ BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} -int32_t BME680Sensor::runTrigger() +int32_t BME680Sensor::runOnce() { if (!bme680.run()) { checkStatus("runTrigger"); @@ -18,13 +18,10 @@ int32_t BME680Sensor::runTrigger() return 35; } -int32_t BME680Sensor::runOnce() +bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { - - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } - if (!bme680.begin(nodeTelemetrySensorsMap[sensorType].first, *nodeTelemetrySensorsMap[sensorType].second)) + status = 0; + if (!bme680.begin(dev->address.address, *bus)) checkStatus("begin"); if (bme680.status == BSEC_OK) { @@ -40,17 +37,15 @@ int32_t BME680Sensor::runOnce() } LOG_INFO("Init sensor: %s with the BSEC Library version %d.%d.%d.%d ", sensorName, bme680.version.major, bme680.version.minor, bme680.version.major_bugfix, bme680.version.minor_bugfix); - } else { - status = 0; } + if (status == 0) LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status); - return initI2CSensor(); + initI2CSensor(); + return status; } -void BME680Sensor::setup() {} - bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) { if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0) diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index ce1fa4f3b..f4ead95f7 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -18,7 +18,6 @@ class BME680Sensor : public TelemetrySensor Bsec2 bme680; protected: - virtual void setup() override; const char *bsecConfigFileName = "/prefs/bsec.dat"; uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0}; uint8_t accuracy = 0; @@ -38,9 +37,9 @@ class BME680Sensor : public TelemetrySensor public: BME680Sensor(); - int32_t runTrigger(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp index 8087eb4b9..1fb2ecc28 100644 --- a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp @@ -10,20 +10,17 @@ BMP085Sensor::BMP085Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP085, "BMP085") {} -int32_t BMP085Sensor::runOnce() +bool BMP085Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } + bmp085 = Adafruit_BMP085(); - status = bmp085.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); + status = bmp085.begin(dev->address.address, bus); - return initI2CSensor(); + initI2CSensor(); + return status; } -void BMP085Sensor::setup() {} - bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement) { measurement->variant.environment_metrics.has_temperature = true; diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.h b/src/modules/Telemetry/Sensor/BMP085Sensor.h index 8dadceab4..12ccf35a1 100644 --- a/src/modules/Telemetry/Sensor/BMP085Sensor.h +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.h @@ -11,13 +11,10 @@ class BMP085Sensor : public TelemetrySensor private: Adafruit_BMP085 bmp085; - protected: - virtual void setup() override; - public: BMP085Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp index 47069b8e0..2b7407c43 100644 --- a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp @@ -10,25 +10,25 @@ BMP280Sensor::BMP280Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP280, "BMP280") {} -int32_t BMP280Sensor::runOnce() +bool BMP280Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + + bmp280 = Adafruit_BMP280(bus); + status = bmp280.begin(dev->address.address); + if (!status) { + return status; } - bmp280 = Adafruit_BMP280(nodeTelemetrySensorsMap[sensorType].second); - status = bmp280.begin(nodeTelemetrySensorsMap[sensorType].first); bmp280.setSampling(Adafruit_BMP280::MODE_FORCED, Adafruit_BMP280::SAMPLING_X1, // Temp. oversampling Adafruit_BMP280::SAMPLING_X1, // Pressure oversampling Adafruit_BMP280::FILTER_OFF, Adafruit_BMP280::STANDBY_MS_1000); - return initI2CSensor(); + initI2CSensor(); + return status; } -void BMP280Sensor::setup() {} - bool BMP280Sensor::getMetrics(meshtastic_Telemetry *measurement) { measurement->variant.environment_metrics.has_temperature = true; diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.h b/src/modules/Telemetry/Sensor/BMP280Sensor.h index d615411b2..2199fc0cd 100644 --- a/src/modules/Telemetry/Sensor/BMP280Sensor.h +++ b/src/modules/Telemetry/Sensor/BMP280Sensor.h @@ -11,13 +11,10 @@ class BMP280Sensor : public TelemetrySensor private: Adafruit_BMP280 bmp280; - protected: - virtual void setup() override; - public: BMP280Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp index 28a71b48f..ac80732bf 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp @@ -6,20 +6,18 @@ BMP3XXSensor::BMP3XXSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP3XX, "BMP3XX") {} -void BMP3XXSensor::setup() {} - -int32_t BMP3XXSensor::runOnce() +bool BMP3XXSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } // Get a singleton instance and initialise the bmp3xx if (bmp3xx == nullptr) { bmp3xx = BMP3XXSingleton::GetInstance(); } - status = bmp3xx->begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); + status = bmp3xx->begin_I2C(dev->address.address, bus); + if (!status) { + return status; + } // set up oversampling and filter initialization bmp3xx->setTemperatureOversampling(BMP3_OVERSAMPLING_4X); @@ -31,7 +29,8 @@ int32_t BMP3XXSensor::runOnce() for (int i = 0; i < 3; i++) { bmp3xx->performReading(); } - return initI2CSensor(); + initI2CSensor(); + return status; } bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement) diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.h b/src/modules/Telemetry/Sensor/BMP3XXSensor.h index 6ab0f533d..7ce14d9db 100644 --- a/src/modules/Telemetry/Sensor/BMP3XXSensor.h +++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.h @@ -43,12 +43,11 @@ class BMP3XXSensor : public TelemetrySensor { protected: BMP3XXSingleton *bmp3xx = nullptr; - virtual void setup() override; public: BMP3XXSensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif diff --git a/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp b/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp index ac5df1b81..e7b191398 100644 --- a/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp +++ b/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp @@ -14,22 +14,16 @@ CGRadSensSensor::CGRadSensSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RADSENS, "RadSens") {} -int32_t CGRadSensSensor::runOnce() +bool CGRadSensSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { // Initialize the sensor following the same pattern as RCWL9620Sensor LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } - status = true; - begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first); - - return initI2CSensor(); + begin(bus, dev->address.address); + initI2CSensor(); + return status; } -void CGRadSensSensor::setup() {} - void CGRadSensSensor::begin(TwoWire *wire, uint8_t addr) { // Store the Wire and address to the sensor following the same pattern as RCWL9620Sensor diff --git a/src/modules/Telemetry/Sensor/CGRadSensSensor.h b/src/modules/Telemetry/Sensor/CGRadSensSensor.h index 3b15a19a2..c677e8899 100644 --- a/src/modules/Telemetry/Sensor/CGRadSensSensor.h +++ b/src/modules/Telemetry/Sensor/CGRadSensSensor.h @@ -17,14 +17,13 @@ class CGRadSensSensor : public TelemetrySensor TwoWire *_wire = &Wire; protected: - virtual void setup() override; void begin(TwoWire *wire = &Wire, uint8_t addr = 0x66); float getStaticRadiation(); public: CGRadSensSensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp index 9581057b0..59a98e291 100644 --- a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp +++ b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp @@ -10,31 +10,39 @@ DFRobotGravitySensor::DFRobotGravitySensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_RAIN, "DFROBOT_RAIN") {} -int32_t DFRobotGravitySensor::runOnce() +DFRobotGravitySensor::~DFRobotGravitySensor() { - LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + if (gravity) { + delete gravity; + gravity = nullptr; } - - gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second); - status = gravity.begin(); - - return initI2CSensor(); } -void DFRobotGravitySensor::setup() +bool DFRobotGravitySensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { - LOG_DEBUG("%s VID: %x, PID: %x, Version: %s", sensorName, gravity.vid, gravity.pid, gravity.getFirmwareVersion().c_str()); + LOG_INFO("Init sensor: %s", sensorName); + + gravity = new DFRobot_RainfallSensor_I2C(bus); + status = gravity->begin(); + + LOG_DEBUG("%s VID: %x, PID: %x, Version: %s", sensorName, gravity->vid, gravity->pid, gravity->getFirmwareVersion().c_str()); + + initI2CSensor(); + return status; } bool DFRobotGravitySensor::getMetrics(meshtastic_Telemetry *measurement) { + if (!gravity) { + LOG_ERROR("DFRobotGravitySensor not initialized"); + return false; + } + measurement->variant.environment_metrics.has_rainfall_1h = true; measurement->variant.environment_metrics.has_rainfall_24h = true; - measurement->variant.environment_metrics.rainfall_1h = gravity.getRainfall(1); - measurement->variant.environment_metrics.rainfall_24h = gravity.getRainfall(24); + measurement->variant.environment_metrics.rainfall_1h = gravity->getRainfall(1); + measurement->variant.environment_metrics.rainfall_24h = gravity->getRainfall(24); LOG_INFO("Rain 1h: %f mm", measurement->variant.environment_metrics.rainfall_1h); LOG_INFO("Rain 24h: %f mm", measurement->variant.environment_metrics.rainfall_24h); diff --git a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h index dfd81a913..2b4890e30 100644 --- a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h +++ b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h @@ -14,15 +14,13 @@ class DFRobotGravitySensor : public TelemetrySensor { private: - DFRobot_RainfallSensor_I2C gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second); - - protected: - virtual void setup() override; + DFRobot_RainfallSensor_I2C *gravity = nullptr; public: DFRobotGravitySensor(); - virtual int32_t runOnce() override; + ~DFRobotGravitySensor(); virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp index d962f1634..2c2aeed6d 100644 --- a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp +++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp @@ -11,14 +11,10 @@ DFRobotLarkSensor::DFRobotLarkSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_LARK, "DFROBOT_LARK") {} -int32_t DFRobotLarkSensor::runOnce() +bool DFRobotLarkSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } - - lark = DFRobot_LarkWeatherStation_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); + lark = DFRobot_LarkWeatherStation_I2C(dev->address.address, bus); if (lark.begin() == 0) // DFRobotLarkSensor init { @@ -28,11 +24,10 @@ int32_t DFRobotLarkSensor::runOnce() LOG_ERROR("DFRobotLarkSensor Init Failed"); status = false; } - return initI2CSensor(); + initI2CSensor(); + return status; } -void DFRobotLarkSensor::setup() {} - bool DFRobotLarkSensor::getMetrics(meshtastic_Telemetry *measurement) { measurement->variant.environment_metrics.has_temperature = true; diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h index 7b67bc5b6..f3e4661a1 100644 --- a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h +++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h @@ -16,13 +16,10 @@ class DFRobotLarkSensor : public TelemetrySensor private: DFRobot_LarkWeatherStation_I2C lark = DFRobot_LarkWeatherStation_I2C(); - protected: - virtual void setup() override; - public: DFRobotLarkSensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif diff --git a/src/modules/Telemetry/Sensor/DPS310Sensor.cpp b/src/modules/Telemetry/Sensor/DPS310Sensor.cpp index cc9b83af8..19e54aa4b 100644 --- a/src/modules/Telemetry/Sensor/DPS310Sensor.cpp +++ b/src/modules/Telemetry/Sensor/DPS310Sensor.cpp @@ -9,23 +9,22 @@ DPS310Sensor::DPS310Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DPS310, "DPS310") {} -int32_t DPS310Sensor::runOnce() +bool DPS310Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + status = dps310.begin_I2C(dev->address.address, bus); + if (!status) { + return status; } - status = dps310.begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); dps310.configurePressure(DPS310_1HZ, DPS310_4SAMPLES); dps310.configureTemperature(DPS310_1HZ, DPS310_4SAMPLES); dps310.setMode(DPS310_CONT_PRESTEMP); - return initI2CSensor(); + initI2CSensor(); + return status; } -void DPS310Sensor::setup() {} - bool DPS310Sensor::getMetrics(meshtastic_Telemetry *measurement) { sensors_event_t temp, press; diff --git a/src/modules/Telemetry/Sensor/DPS310Sensor.h b/src/modules/Telemetry/Sensor/DPS310Sensor.h index e9b4ece89..4de8b2d1a 100644 --- a/src/modules/Telemetry/Sensor/DPS310Sensor.h +++ b/src/modules/Telemetry/Sensor/DPS310Sensor.h @@ -11,13 +11,10 @@ class DPS310Sensor : public TelemetrySensor private: Adafruit_DPS310 dps310; - protected: - virtual void setup() override; - public: DPS310Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/IndicatorSensor.cpp b/src/modules/Telemetry/Sensor/IndicatorSensor.cpp index 317357137..26a4bc5fc 100644 --- a/src/modules/Telemetry/Sensor/IndicatorSensor.cpp +++ b/src/modules/Telemetry/Sensor/IndicatorSensor.cpp @@ -61,11 +61,11 @@ static int cmd_send(uint8_t cmd, const char *p_data, uint8_t len) return -1; } -int32_t IndicatorSensor::runOnce() +bool IndicatorSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("%s: init", sensorName); setup(); - return 2 * DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; // give it some time to start up + return true; } void IndicatorSensor::setup() diff --git a/src/modules/Telemetry/Sensor/IndicatorSensor.h b/src/modules/Telemetry/Sensor/IndicatorSensor.h index 48ecef8de..22a0d9c83 100644 --- a/src/modules/Telemetry/Sensor/IndicatorSensor.h +++ b/src/modules/Telemetry/Sensor/IndicatorSensor.h @@ -7,13 +7,13 @@ class IndicatorSensor : public TelemetrySensor { - protected: - virtual void setup() override; - public: IndicatorSensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; + + private: + void setup(); }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp index cf0fbe4a9..4ed78dcb0 100644 --- a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp +++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp @@ -10,19 +10,17 @@ LPS22HBSensor::LPS22HBSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_LPS22, "LPS22HB") {} -int32_t LPS22HBSensor::runOnce() +bool LPS22HBSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + status = lps22hb.begin_I2C(dev->address.address, bus); + if (!status) { + return status; } - status = lps22hb.begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); - return initI2CSensor(); -} - -void LPS22HBSensor::setup() -{ lps22hb.setDataRate(LPS22_RATE_10_HZ); + + initI2CSensor(); + return status; } bool LPS22HBSensor::getMetrics(meshtastic_Telemetry *measurement) diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.h b/src/modules/Telemetry/Sensor/LPS22HBSensor.h index 24d75e903..90b006fa2 100644 --- a/src/modules/Telemetry/Sensor/LPS22HBSensor.h +++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.h @@ -12,13 +12,10 @@ class LPS22HBSensor : public TelemetrySensor private: Adafruit_LPS22 lps22hb; - protected: - virtual void setup() override; - public: LPS22HBSensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp b/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp index fb84700c4..cb7290fee 100644 --- a/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp +++ b/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp @@ -9,23 +9,23 @@ LTR390UVSensor::LTR390UVSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_LTR390UV, "LTR390UV") {} -int32_t LTR390UVSensor::runOnce() +bool LTR390UVSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + + status = ltr390uv.begin(bus); + if (!status) { + return status; } - status = ltr390uv.begin(nodeTelemetrySensorsMap[sensorType].second); ltr390uv.setMode(LTR390_MODE_UVS); ltr390uv.setGain(LTR390_GAIN_18); // Datasheet default ltr390uv.setResolution(LTR390_RESOLUTION_20BIT); // Datasheet default - return initI2CSensor(); + initI2CSensor(); + return status; } -void LTR390UVSensor::setup() {} - bool LTR390UVSensor::getMetrics(meshtastic_Telemetry *measurement) { LOG_DEBUG("LTR390UV getMetrics"); diff --git a/src/modules/Telemetry/Sensor/LTR390UVSensor.h b/src/modules/Telemetry/Sensor/LTR390UVSensor.h index 40206bce8..e12d17274 100644 --- a/src/modules/Telemetry/Sensor/LTR390UVSensor.h +++ b/src/modules/Telemetry/Sensor/LTR390UVSensor.h @@ -13,13 +13,10 @@ class LTR390UVSensor : public TelemetrySensor float lastLuxReading = 0; float lastUVReading = 0; - protected: - virtual void setup() override; - public: LTR390UVSensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp index 906634c40..c93d6a927 100644 --- a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp +++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp @@ -9,19 +9,17 @@ MCP9808Sensor::MCP9808Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MCP9808, "MCP9808") {} -int32_t MCP9808Sensor::runOnce() +bool MCP9808Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + status = mcp9808.begin(dev->address.address, bus); + if (!status) { + return status; } - status = mcp9808.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); - return initI2CSensor(); -} - -void MCP9808Sensor::setup() -{ mcp9808.setResolution(2); + + initI2CSensor(); + return status; } bool MCP9808Sensor::getMetrics(meshtastic_Telemetry *measurement) diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.h b/src/modules/Telemetry/Sensor/MCP9808Sensor.h index 705a71700..cef7a48c2 100644 --- a/src/modules/Telemetry/Sensor/MCP9808Sensor.h +++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.h @@ -11,13 +11,10 @@ class MCP9808Sensor : public TelemetrySensor private: Adafruit_MCP9808 mcp9808; - protected: - virtual void setup() override; - public: MCP9808Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp index dfc049023..eb84edffc 100644 --- a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp +++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp @@ -8,16 +8,12 @@ MLX90632Sensor::MLX90632Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MLX90632, "MLX90632") {} -int32_t MLX90632Sensor::runOnce() +bool MLX90632Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } MLX90632::status returnError; - if (mlx.begin(nodeTelemetrySensorsMap[sensorType].first, *nodeTelemetrySensorsMap[sensorType].second, returnError) == - true) // MLX90632 init + if (mlx.begin(dev->address.address, *bus, returnError) == true) // MLX90632 init { LOG_DEBUG("MLX90632 Init Succeed"); status = true; @@ -25,11 +21,10 @@ int32_t MLX90632Sensor::runOnce() LOG_ERROR("MLX90632 Init Failed"); status = false; } - return initI2CSensor(); + initI2CSensor(); + return status; } -void MLX90632Sensor::setup() {} - bool MLX90632Sensor::getMetrics(meshtastic_Telemetry *measurement) { measurement->variant.environment_metrics.has_temperature = true; diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.h b/src/modules/Telemetry/Sensor/MLX90632Sensor.h index ef7be180a..566db8319 100644 --- a/src/modules/Telemetry/Sensor/MLX90632Sensor.h +++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.h @@ -11,13 +11,10 @@ class MLX90632Sensor : public TelemetrySensor private: MLX90632 mlx = MLX90632(); - protected: - virtual void setup() override; - public: MLX90632Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp index b6b5d89f7..e67b78145 100644 --- a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp +++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp @@ -16,24 +16,23 @@ meshtastic_Nau7802Config nau7802config = meshtastic_Nau7802Config_init_zero; NAU7802Sensor::NAU7802Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_NAU7802, "NAU7802") {} -int32_t NAU7802Sensor::runOnce() +bool NAU7802Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + status = nau7802.begin(*bus); + if (!status) { + return status; } - status = nau7802.begin(*nodeTelemetrySensorsMap[sensorType].second); nau7802.setSampleRate(NAU7802_SPS_320); if (!loadCalibrationData()) { LOG_ERROR("Failed to load calibration data"); } nau7802.calibrateAFE(); LOG_INFO("Offset: %d, Calibration factor: %.2f", nau7802.getZeroOffset(), nau7802.getCalibrationFactor()); - return initI2CSensor(); + initI2CSensor(); + return status; } -void NAU7802Sensor::setup() {} - bool NAU7802Sensor::getMetrics(meshtastic_Telemetry *measurement) { LOG_DEBUG("NAU7802 getMetrics"); diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.h b/src/modules/Telemetry/Sensor/NAU7802Sensor.h index cb9e64829..a45e9a78a 100644 --- a/src/modules/Telemetry/Sensor/NAU7802Sensor.h +++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.h @@ -13,15 +13,14 @@ class NAU7802Sensor : public TelemetrySensor NAU7802 nau7802; protected: - virtual void setup() override; const char *nau7802ConfigFileName = "/prefs/nau7802.dat"; bool saveCalibrationData(); bool loadCalibrationData(); public: NAU7802Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; void tare(); void calibrate(float weight); AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp index 1f0407374..3407f2f0f 100644 --- a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp +++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp @@ -9,20 +9,15 @@ OPT3001Sensor::OPT3001Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_OPT3001, "OPT3001") {} -int32_t OPT3001Sensor::runOnce() +bool OPT3001Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } - auto errorCode = opt3001.begin(nodeTelemetrySensorsMap[sensorType].first); + auto errorCode = opt3001.begin(dev->address.address); status = errorCode == NO_ERROR; + if (!status) { + return status; + } - return initI2CSensor(); -} - -void OPT3001Sensor::setup() -{ OPT3001_Config newConfig; newConfig.RangeNumber = 0b1100; @@ -34,6 +29,10 @@ void OPT3001Sensor::setup() if (errorConfig != NO_ERROR) { LOG_ERROR("OPT3001 configuration error #%d", errorConfig); } + status = errorConfig == NO_ERROR; + + initI2CSensor(); + return status; } bool OPT3001Sensor::getMetrics(meshtastic_Telemetry *measurement) diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.h b/src/modules/Telemetry/Sensor/OPT3001Sensor.h index a9da2d705..c8a140b51 100644 --- a/src/modules/Telemetry/Sensor/OPT3001Sensor.h +++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.h @@ -12,13 +12,13 @@ class OPT3001Sensor : public TelemetrySensor private: ClosedCube_OPT3001 opt3001; - protected: - virtual void setup() override; - public: OPT3001Sensor(); - virtual int32_t runOnce() override; +#if WIRE_INTERFACES_COUNT > 1 + virtual bool onlyWire1() { return true; } +#endif virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp b/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp index d2b50d983..189317bf2 100644 --- a/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp +++ b/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp @@ -9,24 +9,18 @@ PCT2075Sensor::PCT2075Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PCT2075, "PCT2075") {} -int32_t PCT2075Sensor::runOnce() +bool PCT2075Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } + status = pct2075.begin(dev->address.address, bus); - status = pct2075.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); - - return initI2CSensor(); + initI2CSensor(); + return status; } -void PCT2075Sensor::setup() {} - bool PCT2075Sensor::getMetrics(meshtastic_Telemetry *measurement) { measurement->variant.environment_metrics.has_temperature = true; - measurement->variant.environment_metrics.temperature = pct2075.getTemperature(); return true; diff --git a/src/modules/Telemetry/Sensor/PCT2075Sensor.h b/src/modules/Telemetry/Sensor/PCT2075Sensor.h index 842c973d0..55f9423d4 100644 --- a/src/modules/Telemetry/Sensor/PCT2075Sensor.h +++ b/src/modules/Telemetry/Sensor/PCT2075Sensor.h @@ -12,13 +12,10 @@ class PCT2075Sensor : public TelemetrySensor private: Adafruit_PCT2075 pct2075; - protected: - virtual void setup() override; - public: PCT2075Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp index 7a1bb01ce..ff0628cc3 100644 --- a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp @@ -6,16 +6,12 @@ RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {} -int32_t RAK12035Sensor::runOnce() +bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } - // TODO:: check for up to 2 additional sensors and start them if present. sensor.set_sensor_addr(RAK120351_ADDR); delay(100); - sensor.begin(nodeTelemetrySensorsMap[sensorType].first); + sensor.begin(dev->address.address); // Get sensor firmware version uint8_t data = 0; @@ -31,8 +27,13 @@ int32_t RAK12035Sensor::runOnce() LOG_ERROR("RAK12035Sensor Init Failed"); status = false; } + if (!status) { + return status; + } + setup(); - return initI2CSensor(); + initI2CSensor(); + return status; } void RAK12035Sensor::setup() diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.h b/src/modules/Telemetry/Sensor/RAK12035Sensor.h index 2c32a840d..6a38d2eb3 100644 --- a/src/modules/Telemetry/Sensor/RAK12035Sensor.h +++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.h @@ -16,13 +16,14 @@ class RAK12035Sensor : public TelemetrySensor { private: RAK12035 sensor; - - protected: - virtual void setup() override; + void setup(); public: RAK12035Sensor(); - virtual int32_t runOnce() override; +#if WIRE_INTERFACES_COUNT > 1 + virtual bool onlyWire1() { return true; } +#endif virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp index 9f7a55cc5..3dbd06e8d 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp @@ -8,19 +8,15 @@ RCWL9620Sensor::RCWL9620Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RCWL9620, "RCWL9620") {} -int32_t RCWL9620Sensor::runOnce() +bool RCWL9620Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } status = 1; - begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first); - return initI2CSensor(); + begin(bus, dev->address.address); + initI2CSensor(); + return status; } -void RCWL9620Sensor::setup() {} - bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement) { measurement->variant.environment_metrics.has_distance = true; diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h index 7f9486d25..408db3633 100644 --- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h +++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h @@ -16,14 +16,13 @@ class RCWL9620Sensor : public TelemetrySensor uint32_t _speed = 200000UL; protected: - virtual void setup() override; void begin(TwoWire *wire = &Wire, uint8_t addr = 0x57, uint8_t sda = -1, uint8_t scl = -1, uint32_t speed = 200000UL); float getDistance(); public: RCWL9620Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp index 8619a7905..67a36933d 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp @@ -9,20 +9,13 @@ SHT31Sensor::SHT31Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT31, "SHT31") {} -int32_t SHT31Sensor::runOnce() +bool SHT31Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } - sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second); - status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first); - return initI2CSensor(); -} - -void SHT31Sensor::setup() -{ - // Set up oversampling and filter initialization + sht31 = Adafruit_SHT31(bus); + status = sht31.begin(dev->address.address); + initI2CSensor(); + return status; } bool SHT31Sensor::getMetrics(meshtastic_Telemetry *measurement) diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h index c3d81af95..ecb7d63a6 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.h +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h @@ -11,13 +11,10 @@ class SHT31Sensor : public TelemetrySensor private: Adafruit_SHT31 sht31; - protected: - virtual void setup() override; - public: SHT31Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp index 83fdaf6c6..b11795d97 100644 --- a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp @@ -9,16 +9,16 @@ SHT4XSensor::SHT4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT4X, "SHT4X") {} -int32_t SHT4XSensor::runOnce() +bool SHT4XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } uint32_t serialNumber = 0; - sht4x.begin(nodeTelemetrySensorsMap[sensorType].second); + status = sht4x.begin(bus); + if (!status) { + return status; + } serialNumber = sht4x.readSerial(); if (serialNumber != 0) { @@ -29,12 +29,8 @@ int32_t SHT4XSensor::runOnce() status = 0; } - return initI2CSensor(); -} - -void SHT4XSensor::setup() -{ - // Set up oversampling and filter initialization + initI2CSensor(); + return status; } bool SHT4XSensor::getMetrics(meshtastic_Telemetry *measurement) diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.h b/src/modules/Telemetry/Sensor/SHT4XSensor.h index da608cb82..7311d2366 100644 --- a/src/modules/Telemetry/Sensor/SHT4XSensor.h +++ b/src/modules/Telemetry/Sensor/SHT4XSensor.h @@ -11,13 +11,10 @@ class SHT4XSensor : public TelemetrySensor private: Adafruit_SHT4x sht4x = Adafruit_SHT4x(); - protected: - virtual void setup() override; - public: SHT4XSensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp index e9c4d2a0b..fdab0b266 100644 --- a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp @@ -9,19 +9,13 @@ SHTC3Sensor::SHTC3Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHTC3, "SHTC3") {} -int32_t SHTC3Sensor::runOnce() +bool SHTC3Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } - status = shtc3.begin(nodeTelemetrySensorsMap[sensorType].second); - return initI2CSensor(); -} + status = shtc3.begin(bus); -void SHTC3Sensor::setup() -{ - // Set up oversampling and filter initialization + initI2CSensor(); + return status; } bool SHTC3Sensor::getMetrics(meshtastic_Telemetry *measurement) diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.h b/src/modules/Telemetry/Sensor/SHTC3Sensor.h index 458af6465..51cee18f7 100644 --- a/src/modules/Telemetry/Sensor/SHTC3Sensor.h +++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.h @@ -11,13 +11,10 @@ class SHTC3Sensor : public TelemetrySensor private: Adafruit_SHTC3 shtc3 = Adafruit_SHTC3(); - protected: - virtual void setup() override; - public: SHTC3Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.cpp b/src/modules/Telemetry/Sensor/T1000xSensor.cpp index 068969e8e..b123450ec 100644 --- a/src/modules/Telemetry/Sensor/T1000xSensor.cpp +++ b/src/modules/Telemetry/Sensor/T1000xSensor.cpp @@ -38,18 +38,10 @@ int8_t ntc_temp2[136] = { T1000xSensor::T1000xSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SENSOR_UNSET, "T1000x") {} -int32_t T1000xSensor::runOnce() +bool T1000xSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; - } - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; -} - -void T1000xSensor::setup() -{ - // Set up oversampling and filter initialization + return true; } float T1000xSensor::getLux() diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.h b/src/modules/Telemetry/Sensor/T1000xSensor.h index a1c771cfa..b840a2d88 100644 --- a/src/modules/Telemetry/Sensor/T1000xSensor.h +++ b/src/modules/Telemetry/Sensor/T1000xSensor.h @@ -7,13 +7,10 @@ class T1000xSensor : public TelemetrySensor { - protected: - virtual void setup() override; - public: T1000xSensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; virtual float getLux(); virtual float getTemp(); }; diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp index 9f3b7e460..4e02af642 100644 --- a/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp +++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp @@ -9,22 +9,19 @@ TSL2561Sensor::TSL2561Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL2561, "TSL2561") {} -int32_t TSL2561Sensor::runOnce() +bool TSL2561Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + + status = tsl.begin(bus); + if (!status) { + return status; } - - status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second); - - return initI2CSensor(); -} - -void TSL2561Sensor::setup() -{ tsl.setGain(TSL2561_GAIN_1X); tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS); + + initI2CSensor(); + return status; } bool TSL2561Sensor::getMetrics(meshtastic_Telemetry *measurement) diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.h b/src/modules/Telemetry/Sensor/TSL2561Sensor.h index 0329becd8..abf5a8f73 100644 --- a/src/modules/Telemetry/Sensor/TSL2561Sensor.h +++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.h @@ -12,12 +12,9 @@ class TSL2561Sensor : public TelemetrySensor // The magic number is a sensor id, the actual value doesn't matter Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_LOW, 12345); - protected: - virtual void setup() override; - public: TSL2561Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp index 04443ebec..0899d4470 100644 --- a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp +++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp @@ -10,21 +10,18 @@ TSL2591Sensor::TSL2591Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL25911FN, "TSL2591") {} -int32_t TSL2591Sensor::runOnce() +bool TSL2591Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + status = tsl.begin(bus); + if (!status) { + return status; } - status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second); - - return initI2CSensor(); -} - -void TSL2591Sensor::setup() -{ tsl.setGain(TSL2591_GAIN_LOW); // 1x gain tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS); + + initI2CSensor(); + return status; } bool TSL2591Sensor::getMetrics(meshtastic_Telemetry *measurement) diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.h b/src/modules/Telemetry/Sensor/TSL2591Sensor.h index edf7698b1..1ac430a03 100644 --- a/src/modules/Telemetry/Sensor/TSL2591Sensor.h +++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.h @@ -11,12 +11,9 @@ class TSL2591Sensor : public TelemetrySensor private: Adafruit_TSL2591 tsl; - protected: - virtual void setup() override; - public: TSL2591Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h index 83d7b38b0..3c3e61808 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.h +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h @@ -6,6 +6,7 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "MeshModule.h" #include "NodeDB.h" +#include "detect/ScanI2C.h" #include #if !ARCH_PORTDUINO @@ -42,22 +43,32 @@ class TelemetrySensor initialized = true; return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } - virtual void setup() = 0; + + // TODO: check is setup used at all? + virtual void setup() {} public: + virtual ~TelemetrySensor() {} + virtual AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, meshtastic_AdminMessage *response) { return AdminMessageHandleResult::NOT_HANDLED; } + // TODO: delete after migration bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; } - virtual int32_t runOnce() = 0; +#if WIRE_INTERFACES_COUNT > 1 + // Set to true if Implementation only works first I2C port (Wire) + virtual bool onlyWire1() { return false; } +#endif + virtual int32_t runOnce() { return INT32_MAX; } virtual bool isInitialized() { return initialized; } virtual bool isRunning() { return status > 0; } virtual bool getMetrics(meshtastic_Telemetry *measurement) = 0; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { return false; }; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp index b075bf405..c89463be5 100644 --- a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp @@ -11,23 +11,22 @@ VEML7700Sensor::VEML7700Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_VEML7700, "VEML7700") {} -int32_t VEML7700Sensor::runOnce() +bool VEML7700Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { LOG_INFO("Init sensor: %s", sensorName); - if (!hasSensor()) { - return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + status = veml7700.begin(bus); + if (!status) { + return status; } - status = veml7700.begin(nodeTelemetrySensorsMap[sensorType].second); veml7700.setLowThreshold(10000); veml7700.setHighThreshold(20000); veml7700.interruptEnable(true); - return initI2CSensor(); + initI2CSensor(); + return status; } -void VEML7700Sensor::setup() {} - /*! * @brief Copmute lux from ALS reading. * @param rawALS raw ALS register value diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.h b/src/modules/Telemetry/Sensor/VEML7700Sensor.h index f40384ad3..92883df08 100644 --- a/src/modules/Telemetry/Sensor/VEML7700Sensor.h +++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.h @@ -16,12 +16,9 @@ class VEML7700Sensor : public TelemetrySensor float computeLux(uint16_t rawALS, bool corrected); float getResolution(void); - protected: - virtual void setup() override; - public: VEML7700Sensor(); - virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/nullSensor.cpp b/src/modules/Telemetry/Sensor/nullSensor.cpp index c84b9d27f..1d545186a 100644 --- a/src/modules/Telemetry/Sensor/nullSensor.cpp +++ b/src/modules/Telemetry/Sensor/nullSensor.cpp @@ -11,7 +11,7 @@ NullSensor::NullSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SENSOR int32_t NullSensor::runOnce() { - return 0; + return INT32_MAX; } void NullSensor::setup() {} diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 7f7a9d511..33887557f 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -60,7 +60,9 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length) return; } const meshtastic_Channel &ch = channels.getByName(e.channel_id); - if (strcmp(e.gateway_id, owner.id) == 0) { + // Generate node ID from nodenum for comparison + std::string nodeId = nodeDB->getNodeId(); + if (strcmp(e.gateway_id, nodeId.c_str()) == 0) { // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node // receives it when we get our own packet back. Then we'll stop our retransmissions. @@ -128,8 +130,10 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length) // returns true if this is a valid JSON envelope which we accept on downlink inline bool isValidJsonEnvelope(JSONObject &json) { + // Generate node ID from nodenum for comparison + std::string nodeId = nodeDB->getNodeId(); // if "sender" is provided, avoid processing packets we uplinked - return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && + return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(nodeId) != 0) : true) && (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number (json.find("from") != json.end()) && json["from"]->IsNumber() && (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us @@ -297,7 +301,9 @@ bool connectPubSub(const PubSubConfig &config, PubSubClient &pubSub, Client &cli LOG_INFO("Connecting directly to MQTT server %s, port: %d, username: %s, password: %s", config.serverAddr.c_str(), config.serverPort, config.mqttUsername, config.mqttPassword); - const bool connected = pubSub.connect(owner.id, config.mqttUsername, config.mqttPassword); + // Generate node ID from nodenum for client identification + std::string nodeId = nodeDB->getNodeId(); + const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword); if (connected) { LOG_INFO("MQTT connected"); } else { @@ -467,7 +473,9 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; - strcpy(msg->topic, topic); + strlcpy(msg->topic, topic, sizeof(msg->topic)); + if (length > sizeof(msg->payload_variant.data.bytes)) + length = sizeof(msg->payload_variant.data.bytes); msg->payload_variant.data.size = length; memcpy(msg->payload_variant.data.bytes, payload, length); msg->retained = retained; @@ -687,11 +695,14 @@ void MQTT::publishQueuedMessages() if (jsonString.length() == 0) return; + // Generate node ID from nodenum for topic + std::string nodeId = nodeDB->getNodeId(); + std::string topicJson; if (env.packet->pki_encrypted) { - topicJson = jsonTopic + "PKI/" + owner.id; + topicJson = jsonTopic + "PKI/" + nodeId; } else { - topicJson = jsonTopic + env.channel_id + "/" + owner.id; + topicJson = jsonTopic + env.channel_id + "/" + nodeId; } LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str()); publish(topicJson.c_str(), jsonString.c_str(), false); @@ -749,10 +760,14 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me return; // Don't upload a still-encrypted PKI packet if not encryption_enabled } - const meshtastic_ServiceEnvelope env = { - .packet = const_cast(p), .channel_id = const_cast(channelId), .gateway_id = owner.id}; + // Generate node ID from nodenum for service envelope + std::string nodeId = nodeDB->getNodeId(); + + const meshtastic_ServiceEnvelope env = {.packet = const_cast(p), + .channel_id = const_cast(channelId), + .gateway_id = const_cast(nodeId.c_str())}; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &env); - std::string topic = cryptTopic + channelId + "/" + owner.id; + std::string topic = cryptTopic + channelId + "/" + nodeId; if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes); @@ -766,7 +781,9 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me auto jsonString = MeshPacketSerializer::JsonSerialize(&mp_decoded); if (jsonString.length() == 0) return; - std::string topicJson = jsonTopic + channelId + "/" + owner.id; + // Generate node ID from nodenum for JSON topic + std::string nodeIdForJson = nodeDB->getNodeId(); + std::string topicJson = jsonTopic + channelId + "/" + nodeIdForJson; LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str()); publish(topicJson.c_str(), jsonString.c_str(), false); #endif // ARCH_NRF52 NRF52_USE_JSON @@ -845,11 +862,14 @@ void MQTT::perhapsReportToMap() mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_MapReport_msg, &mapReport); + // Generate node ID from nodenum for service envelope + std::string nodeId = nodeDB->getNodeId(); + // Encode the MeshPacket into a binary ServiceEnvelope and publish const meshtastic_ServiceEnvelope se = { .packet = mp, .channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()), // Use primary channel as the channel_id - .gateway_id = owner.id}; + .gateway_id = const_cast(nodeId.c_str())}; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &se); LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str()); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 5524765f3..6238031f6 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -3,12 +3,15 @@ #include "BluetoothCommon.h" #include "NimbleBluetooth.h" #include "PowerFSM.h" +#include "StaticPointerQueue.h" +#include "concurrency/OSThread.h" #include "main.h" #include "mesh/PhoneAPI.h" #include "mesh/mesh-pb-constants.h" #include "sleep.h" #include +#include #include #ifdef NIMBLE_TWO @@ -17,45 +20,291 @@ #include "PowerStatus.h" #endif +#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) +#if defined(CONFIG_NIMBLE_CPP_IDF) +#include "host/ble_gap.h" +#else +#include "nimble/nimble/host/include/host/ble_gap.h" +#endif + +namespace +{ +constexpr uint16_t kPreferredBleMtu = 517; +constexpr uint16_t kPreferredBleTxOctets = 251; +constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8; +} // namespace +#endif + +// Debugging options: careful, they slow things down quite a bit! +// #define DEBUG_NIMBLE_ON_READ_TIMING // uncomment to time onRead duration +// #define DEBUG_NIMBLE_ON_WRITE_TIMING // uncomment to time onWrite duration +// #define DEBUG_NIMBLE_NOTIFY // uncomment to enable notify logging + +#define NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE 3 +#define NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE 3 + NimBLECharacteristic *fromNumCharacteristic; NimBLECharacteristic *BatteryCharacteristic; NimBLECharacteristic *logRadioCharacteristic; NimBLEServer *bleServer; static bool passkeyShowing; +static std::atomic nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection" class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread { + /* + CAUTION: There's a lot going on here and lots of room to break things. + + This NimbleBluetooth.cpp file does some tricky synchronization between the NimBLE FreeRTOS task (which runs the onRead and + onWrite callbacks) and the main task (which runs runOnce and the rest of PhoneAPI). + + The main idea is to add a little bit of synchronization here to make it so that the rest of the codebase doesn't have to + know about concurrency and mutexes, and can just run happily ever after as a cooperative multitasking OSThread system, where + locking isn't something that anyone has to worry about too much! :) + + We achieve this by having some queues and mutexes in this file only, and ensuring that all calls to getFromRadio and + handleToRadio are only made from the main FreeRTOS task. This way, the rest of the codebase doesn't have to worry about + being run concurrently, which would make everything else much much much more complicated. + + PHONE -> RADIO: + - [NimBLE FreeRTOS task:] onWrite callback holds fromPhoneMutex and pushes received packets into fromPhoneQueue. + - [Main task:] runOnceHandleFromPhoneQueue in main task holds fromPhoneMutex, pulls packets from fromPhoneQueue, and calls + handleToRadio **in main task**. + + RADIO -> PHONE: + - [NimBLE FreeRTOS task:] onRead callback sets onReadCallbackIsWaitingForData flag and polls in a busy loop. (unless + there's already a packet waiting in toPhoneQueue) + - [Main task:] runOnceHandleToPhoneQueue sees onReadCallbackIsWaitingForData flag, calls getFromRadio **in main task** to + get packets from radio, holds toPhoneMutex, pushes the packet into toPhoneQueue, and clears the + onReadCallbackIsWaitingForData flag. + - [NimBLE FreeRTOS task:] onRead callback sees that the onReadCallbackIsWaitingForData flag cleared, holds toPhoneMutex, + pops the packet from toPhoneQueue, and returns it to NimBLE. + + MUTEXES: + - fromPhoneMutex protects fromPhoneQueue and fromPhoneQueueSize + - toPhoneMutex protects toPhoneQueue, toPhoneQueueByteSizes, and toPhoneQueueSize + + ATOMICS: + - fromPhoneQueueSize is only increased by onWrite, and only decreased by runOnceHandleFromPhoneQueue (or onDisconnect). + - toPhoneQueueSize is only increased by runOnceHandleToPhoneQueue, and only decreased by onRead (or onDisconnect). + - onReadCallbackIsWaitingForData is a flag. It's only set by onRead, and only cleared by runOnceHandleToPhoneQueue (or + onDisconnect). + + PRELOADING: see comments in runOnceToPhoneCanPreloadNextPacket about when it's safe to preload packets from getFromRadio. + + BLE CONNECTION PARAMS: + - During config, we request a high-throughput, low-latency BLE connection for speed. + - After config, we switch to a lower-power BLE connection for steady-state use to extend battery life. + + MEMORY MANAGEMENT: + - We keep packets on the stack and do not allocate heap. + - We use std::array for fromPhoneQueue and toPhoneQueue to avoid mallocs and frees across FreeRTOS tasks. + - Yes, we have to do some copy operations on pop because of this, but it's worth it to avoid cross-task memory management. + + NOTIFY IS BROKEN: + - Adding NIMBLE_PROPERTY::NOTIFY to FromRadioCharacteristic appears to break things. It is NOT backwards compatible. + + ZERO-SIZE READS: + - Returning a zero-size read from onRead breaks some clients during the config phase. So we have to block onRead until we + have data. + - During the STATE_SEND_PACKETS phase, it's totally OK to return zero-size reads, as clients are expected to do reads + until they get a 0-byte response. + + CROSS-TASK WAKEUP: + - If you call: bluetoothPhoneAPI->setIntervalFromNow(0); to schedule immediate processing of new data, + - Then you should also call: concurrency::mainDelay.interrupt(); to wake up the main loop if it's sleeping. + - Otherwise, you're going to wait ~100ms or so until the main loop wakes up from some other cause. + */ + public: - BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") { nimble_queue.resize(3); } - std::vector nimble_queue; - std::mutex nimble_mutex; - uint8_t queue_size = 0; - bool has_fromRadio = false; - uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; - size_t numBytes = 0; - bool hasChecked = false; - bool phoneWants = false; + BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") {} + + /* Packets from phone (BLE onWrite callback) */ + std::mutex fromPhoneMutex; + std::atomic fromPhoneQueueSize{0}; + // We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks. + std::array fromPhoneQueue{}; + + /* Packets to phone (BLE onRead callback) */ + std::mutex toPhoneMutex; + std::atomic toPhoneQueueSize{0}; + // We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks. + std::array, NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE> toPhoneQueue{}; + std::array toPhoneQueueByteSizes{}; + // The onReadCallbackIsWaitingForData flag provides synchronization between the NimBLE task's onRead callback and our main + // task's runOnce. It's only set by onRead, and only cleared by runOnce. + std::atomic onReadCallbackIsWaitingForData{false}; + + /* Statistics/logging helpers */ + std::atomic readCount{0}; + std::atomic notifyCount{0}; + std::atomic writeCount{0}; protected: virtual int32_t runOnce() override { - std::lock_guard guard(nimble_mutex); - if (queue_size > 0) { - for (uint8_t i = 0; i < queue_size; i++) { - handleToRadio(nimble_queue.at(i).data(), nimble_queue.at(i).length()); - } - LOG_DEBUG("Queue_size %u", queue_size); - queue_size = 0; - } - if (hasChecked == false && phoneWants == true) { - numBytes = getFromRadio(fromRadioBytes); - hasChecked = true; + while (runOnceHasWorkToDo()) { + /* + PROCESS fromPhoneQueue BEFORE toPhoneQueue: + + In normal STATE_SEND_PACKETS operation, it's unlikely that we'll have both writes and reads to process at the same + time, because either onWrite or onRead will trigger this runOnce. And in STATE_SEND_PACKETS, it's generally ok to + service either the reads or writes first. + + However, during the initial setup wantConfig packet, the clients send a write and immediately send a read, and they + expect the read will respond to the write. (This also happens when a client goes from STATE_SEND_PACKETS back to + another wantConfig, like the iOS client does when requesting the nodedb after requesting the main config only.) + + So it's safest to always service writes (fromPhoneQueue) before reads (toPhoneQueue), so that any "synchronous" + write-then-read sequences from the client work as expected, even if this means we block onRead for a while: this is + what the client wants! + */ + + // PHONE -> RADIO: + runOnceHandleFromPhoneQueue(); // pull data from onWrite to handleToRadio + + // RADIO -> PHONE: + runOnceHandleToPhoneQueue(); // push data from getFromRadio to onRead } // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback return INT32_MAX; } + + virtual void onConfigStart() override + { + LOG_INFO("BLE onConfigStart"); + + // Prefer high throughput during config/setup, at the cost of high power consumption (for a few seconds) + if (bleServer && isConnected()) { + uint16_t conn_handle = nimbleBluetoothConnHandle.load(); + if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { + requestHighThroughputConnection(conn_handle); + } + } + } + + virtual void onConfigComplete() override + { + LOG_INFO("BLE onConfigComplete"); + + // Switch to lower power consumption BLE connection params for steady-state use after config/setup is complete + if (bleServer && isConnected()) { + uint16_t conn_handle = nimbleBluetoothConnHandle.load(); + if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { + requestLowerPowerConnection(conn_handle); + } + } + } + + bool runOnceHasWorkToDo() { return runOnceHasWorkToPhone() || runOnceHasWorkFromPhone(); } + + bool runOnceHasWorkToPhone() { return onReadCallbackIsWaitingForData || runOnceToPhoneCanPreloadNextPacket(); } + + bool runOnceToPhoneCanPreloadNextPacket() + { + /* + * PRELOADING getFromRadio RESPONSES: + * + * It's not safe to preload packets if we're in STATE_SEND_PACKETS, because there may be a while between the time we call + * getFromRadio and when the client actually reads it. If the connection drops in that time, we might lose that packet + * forever. In STATE_SEND_PACKETS, if we wait for onRead before we call getFromRadio, we minimize the time window where + * the client might disconnect before completing the read. + * + * However, if we're in the setup states (sending config, nodeinfo, etc), it's safe and beneficial to preload packets into + * toPhoneQueue because the client will just reconnect after a disconnect, losing nothing. + */ + + if (!isConnected()) { + return false; + } else if (isSendingPackets()) { + // If we're in STATE_SEND_PACKETS, we must wait for onRead before calling getFromRadio. + return false; + } else { + // In other states, we can preload as long as there's space in the toPhoneQueue. + return toPhoneQueueSize < NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE; + } + } + + void runOnceHandleToPhoneQueue() + { + // Stack buffer for getFromRadio packet + uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; + size_t numBytes = 0; + + if (onReadCallbackIsWaitingForData || runOnceToPhoneCanPreloadNextPacket()) { + numBytes = getFromRadio(fromRadioBytes); + + if (numBytes == 0) { + /* + Client expected a read, but we have nothing to send. + + In STATE_SEND_PACKETS, it is 100% OK to return a 0-byte response, as we expect clients to do read beyond + notifies regularly, to make sure they have nothing else to read. + + In other states, this is fine **so long as we've already processed pending onWrites first**, because the client + may requesting wantConfig and immediately doing a read. + */ + } else { + // Push to toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible. + if (toPhoneQueueSize < NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE) { + // Note: the comparison above is safe without a mutex because we are the only method that *increases* + // toPhoneQueueSize. (It's okay if toPhoneQueueSize *decreases* in the NimBLE task meanwhile.) + + { // scope for toPhoneMutex mutex + std::lock_guard guard(toPhoneMutex); + size_t storeAtIndex = toPhoneQueueSize.load(); + memcpy(toPhoneQueue[storeAtIndex].data(), fromRadioBytes, numBytes); + toPhoneQueueByteSizes[storeAtIndex] = numBytes; + toPhoneQueueSize++; + } +#ifdef DEBUG_NIMBLE_ON_READ_TIMING + LOG_DEBUG("BLE getFromRadio returned numBytes=%u, pushed toPhoneQueueSize=%u", numBytes, + toPhoneQueueSize.load()); +#endif + } else { + // Shouldn't happen because the onRead callback shouldn't be waiting if the queue is full! + LOG_ERROR("Shouldn't happen! Drop FromRadio packet, toPhoneQueue full (%u bytes)", numBytes); + } + } + + // Clear the onReadCallbackIsWaitingForData flag so onRead knows it can proceed. + onReadCallbackIsWaitingForData = false; // only clear this flag AFTER the push + } + } + + bool runOnceHasWorkFromPhone() { return fromPhoneQueueSize > 0; } + + void runOnceHandleFromPhoneQueue() + { + // Handle packets we received from onWrite from the phone. + if (fromPhoneQueueSize > 0) { + // Note: the comparison above is safe without a mutex because we are the only method that *decreases* + // fromPhoneQueueSize. (It's okay if fromPhoneQueueSize *increases* in the NimBLE task meanwhile.) + + LOG_DEBUG("NimbleBluetooth: handling ToRadio packet, fromPhoneQueueSize=%u", fromPhoneQueueSize.load()); + + // Pop the front of fromPhoneQueue, holding the mutex only briefly while we pop. + NimBLEAttValue val; + { // scope for fromPhoneMutex mutex + std::lock_guard guard(fromPhoneMutex); + val = fromPhoneQueue[0]; + + // Shift the rest of the queue down + for (uint8_t i = 1; i < fromPhoneQueueSize; i++) { + fromPhoneQueue[i - 1] = fromPhoneQueue[i]; + } + + // Safe decrement due to onDisconnect + if (fromPhoneQueueSize > 0) + fromPhoneQueueSize--; + } + + handleToRadio(val.data(), val.length()); + } + } + /** * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) */ @@ -63,14 +312,22 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread { PhoneAPI::onNowHasData(fromRadioNum); + int currentNotifyCount = notifyCount.fetch_add(1); + uint8_t cc = bleServer->getConnectedCount(); - LOG_DEBUG("BLE notify fromNum: %d connections: %d", fromRadioNum, cc); + +#ifdef DEBUG_NIMBLE_NOTIFY + // This logging slows things down when there are lots of packets going to the phone, like initial connection: + LOG_DEBUG("BLE notify(%d) fromNum: %d connections: %d", currentNotifyCount, fromRadioNum, cc); +#endif uint8_t val[4]; put_le32(val, fromRadioNum); fromNumCharacteristic->setValue(val, sizeof(val)); #ifdef NIMBLE_TWO + // NOTE: I don't have any NIMBLE_TWO devices, but this line makes me suspicious, and I suspect it needs to just be + // notify(). fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE); #else fromNumCharacteristic->notify(); @@ -79,6 +336,54 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread /// Check the current underlying physical link to see if the client is currently connected virtual bool checkIsConnected() { return bleServer && bleServer->getConnectedCount() > 0; } + + void requestHighThroughputConnection(uint16_t conn_handle) + { + /* Request a lower-latency, higher-throughput BLE connection. + + This comes at the cost of higher power consumption, so we may want to only use this for initial setup, and then switch to + a slower mode. + + See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS + constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple + recommendations.) + + Selected settings: + minInterval (units of 1.25ms): 7.5ms = 6 (lower than the Apple recommended minimum, but allows faster when the client + supports it.) + maxInterval (units of 1.25ms): 15ms = 12 + latency: 0 (don't allow peripheral to skip any connection events) + timeout (units of 10ms): 6 seconds = 600 (supervision timeout) + + These are intentionally aggressive to prioritize speed over power consumption, but are only used for a few seconds at + setup. Not worth adjusting much. + */ + LOG_INFO("BLE requestHighThroughputConnection"); + bleServer->updateConnParams(conn_handle, 6, 12, 0, 600); + } + + void requestLowerPowerConnection(uint16_t conn_handle) + { + /* Request a lower power consumption (but higher latency, lower throughput) BLE connection. + + This is suitable for steady-state operation after initial setup is complete. + + See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS + constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple + recommendations.) + + Selected settings: + minInterval (units of 1.25ms): 30ms = 24 + maxInterval (units of 1.25ms): 50ms = 40 + latency: 2 (allow peripheral to skip up to 2 consecutive connection events to save power) + timeout (units of 10ms): 6 seconds = 600 (supervision timeout) + + There's an opportunity for tuning here if anyone wants to do some power measurements, but these should allow 10-20 packets + per second. + */ + LOG_INFO("BLE requestLowerPowerConnection"); + bleServer->updateConnParams(conn_handle, 24, 40, 2, 600); + } }; static BluetoothPhoneAPI *bluetoothPhoneAPI; @@ -98,16 +403,45 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks #endif { + // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce. + // Assumption: onWrite is serialized by NimBLE, so we don't need to lock here against multiple concurrent onWrite calls. + + int currentWriteCount = bluetoothPhoneAPI->writeCount.fetch_add(1); + +#ifdef DEBUG_NIMBLE_ON_WRITE_TIMING + int startMillis = millis(); + LOG_DEBUG("BLE onWrite(%d): start millis=%d", currentWriteCount, startMillis); +#endif + auto val = pCharacteristic->getValue(); if (memcmp(lastToRadio, val.data(), val.length()) != 0) { - if (bluetoothPhoneAPI->queue_size < 3) { + if (bluetoothPhoneAPI->fromPhoneQueueSize < NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE) { + // Note: the comparison above is safe without a mutex because we are the only method that *increases* + // fromPhoneQueueSize. (It's okay if fromPhoneQueueSize *decreases* in the main task meanwhile.) memcpy(lastToRadio, val.data(), val.length()); - std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); - bluetoothPhoneAPI->nimble_queue.at(bluetoothPhoneAPI->queue_size) = val; - bluetoothPhoneAPI->queue_size++; + + { // scope for fromPhoneMutex mutex + // Append to fromPhoneQueue, protected by fromPhoneMutex. Hold the mutex as briefly as possible. + std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex); + bluetoothPhoneAPI->fromPhoneQueue.at(bluetoothPhoneAPI->fromPhoneQueueSize) = val; + bluetoothPhoneAPI->fromPhoneQueueSize++; + } + + // After releasing the mutex, schedule immediate processing of the new packet. bluetoothPhoneAPI->setIntervalFromNow(0); + concurrency::mainDelay.interrupt(); // wake up main loop if sleeping + +#ifdef DEBUG_NIMBLE_ON_WRITE_TIMING + int finishMillis = millis(); + LOG_DEBUG("BLE onWrite(%d): append to fromPhoneQueue took %u ms. numBytes=%d", currentWriteCount, + finishMillis - startMillis, val.length()); +#endif + } else { + LOG_WARN("BLE onWrite(%d): Drop ToRadio packet, fromPhoneQueue full (%u bytes)", currentWriteCount, val.length()); } + } else { + LOG_DEBUG("BLE onWrite(%d): Drop duplicate ToRadio packet (%u bytes)", currentWriteCount, val.length()); } } }; @@ -120,21 +454,107 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks virtual void onRead(NimBLECharacteristic *pCharacteristic) #endif { - int tries = 0; - bluetoothPhoneAPI->phoneWants = true; - while (!bluetoothPhoneAPI->hasChecked && tries < 100) { - bluetoothPhoneAPI->setIntervalFromNow(0); - delay(20); - tries++; - } - std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); - pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes); + // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce. - if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload + int currentReadCount = bluetoothPhoneAPI->readCount.fetch_add(1); + int tries = 0; + int startMillis = millis(); + +#ifdef DEBUG_NIMBLE_ON_READ_TIMING + LOG_DEBUG("BLE onRead(%d): start millis=%d", currentReadCount, startMillis); +#endif + + // Is there a packet ready to go, or do we have to ask the main task to get one for us? + if (bluetoothPhoneAPI->toPhoneQueueSize > 0) { + // Note: the comparison above is safe without a mutex because we are the only method that *decreases* + // toPhoneQueueSize. (It's okay if toPhoneQueueSize *increases* in the main task meanwhile.) + + // There's already a packet queued. Great! We don't need to wait for onReadCallbackIsWaitingForData. +#ifdef DEBUG_NIMBLE_ON_READ_TIMING + LOG_DEBUG("BLE onRead(%d): packet already waiting, no need to set onReadCallbackIsWaitingForData", currentReadCount); +#endif + } else { + // Tell the main task that we'd like a packet. + bluetoothPhoneAPI->onReadCallbackIsWaitingForData = true; + + // Wait for the main task to produce a packet for us, up to about 20 seconds. + // It normally takes just a few milliseconds, but at initial startup, etc, the main task can get blocked for longer + // doing various setup tasks. + while (bluetoothPhoneAPI->onReadCallbackIsWaitingForData && tries < 4000) { + // Schedule the main task runOnce to run ASAP. + bluetoothPhoneAPI->setIntervalFromNow(0); + concurrency::mainDelay.interrupt(); // wake up main loop if sleeping + + if (!bluetoothPhoneAPI->onReadCallbackIsWaitingForData) { + // we may be able to break even before a delay, if the call to interrupt woke up the main loop and it ran + // already +#ifdef DEBUG_NIMBLE_ON_READ_TIMING + LOG_DEBUG("BLE onRead(%d): broke before delay after %u ms, %d tries", currentReadCount, + millis() - startMillis, tries); +#endif + break; + } + + // This delay happens in the NimBLE FreeRTOS task, which really can't do anything until we get a value back. + // No harm in polling pretty frequently. + delay(tries < 20 ? 1 : 5); + tries++; + + if (tries == 4000) { + LOG_WARN( + "BLE onRead(%d): timeout waiting for data after %u ms, %d tries, giving up and returning 0-size response", + currentReadCount, millis() - startMillis, tries); + } + } + } + + // Pop from toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible. + uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; // Stack buffer for getFromRadio packet + size_t numBytes = 0; + { // scope for toPhoneMutex mutex + std::lock_guard guard(bluetoothPhoneAPI->toPhoneMutex); + size_t toPhoneQueueSize = bluetoothPhoneAPI->toPhoneQueueSize.load(); + if (toPhoneQueueSize > 0) { + // Copy from the front of the toPhoneQueue + memcpy(fromRadioBytes, bluetoothPhoneAPI->toPhoneQueue[0].data(), bluetoothPhoneAPI->toPhoneQueueByteSizes[0]); + numBytes = bluetoothPhoneAPI->toPhoneQueueByteSizes[0]; + + // Shift the rest of the queue down + for (uint8_t i = 1; i < toPhoneQueueSize; i++) { + memcpy(bluetoothPhoneAPI->toPhoneQueue[i - 1].data(), bluetoothPhoneAPI->toPhoneQueue[i].data(), + bluetoothPhoneAPI->toPhoneQueueByteSizes[i]); + // The above line is similar to: + // bluetoothPhoneAPI->toPhoneQueue[i - 1] = bluetoothPhoneAPI->toPhoneQueue[i] + // but is usually faster because it doesn't have to copy all the trailing bytes beyond + // toPhoneQueueByteSizes[i]. + // + // We deliberately use an array here (and pay the CPU cost of some memcpy) to avoid synchronizing dynamic + // memory allocations and frees across FreeRTOS tasks. + + bluetoothPhoneAPI->toPhoneQueueByteSizes[i - 1] = bluetoothPhoneAPI->toPhoneQueueByteSizes[i]; + } + + // Safe decrement due to onDisconnect + if (bluetoothPhoneAPI->toPhoneQueueSize > 0) + bluetoothPhoneAPI->toPhoneQueueSize--; + } else { + // nothing in the toPhoneQueue; that's fine, and we'll just have numBytes=0. + } + } + +#ifdef DEBUG_NIMBLE_ON_READ_TIMING + int finishMillis = millis(); + LOG_DEBUG("BLE onRead(%d): onReadCallbackIsWaitingForData took %u ms, %d tries. numBytes=%d", currentReadCount, + finishMillis - startMillis, tries, numBytes); +#endif + + pCharacteristic->setValue(fromRadioBytes, numBytes); + + // If we sent something, wake up the main loop if it's sleeping in case there are more packets ready to enqueue. + if (numBytes != 0) { bluetoothPhoneAPI->setIntervalFromNow(0); - bluetoothPhoneAPI->numBytes = 0; - bluetoothPhoneAPI->hasChecked = false; - bluetoothPhoneAPI->phoneWants = false; + concurrency::mainDelay.interrupt(); // wake up main loop if sleeping + } } }; @@ -216,12 +636,40 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks if (screen) screen->endAlert(); } + + // Store the connection handle for future use +#ifdef NIMBLE_TWO + nimbleBluetoothConnHandle = connInfo.getConnHandle(); +#else + nimbleBluetoothConnHandle = desc->conn_handle; +#endif } #ifdef NIMBLE_TWO virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) { LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str()); + +#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) + const uint16_t connHandle = connInfo.getConnHandle(); + int phyResult = + ble_gap_set_prefered_le_phy(connHandle, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_CODED_ANY); + if (phyResult == 0) { + LOG_INFO("BLE conn %u requested 2M PHY", connHandle); + } else { + LOG_WARN("Failed to prefer 2M PHY for conn %u, rc=%d", connHandle, phyResult); + } + + int dataLenResult = ble_gap_set_data_len(connHandle, kPreferredBleTxOctets, kPreferredBleTxTimeUs); + if (dataLenResult == 0) { + LOG_INFO("BLE conn %u requested data length %u bytes", connHandle, kPreferredBleTxOctets); + } else { + LOG_WARN("Failed to raise data length for conn %u, rc=%d", connHandle, dataLenResult); + } + + LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu); + pServer->updateConnParams(connHandle, 6, 12, 0, 200); +#endif } virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) @@ -241,19 +689,41 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks bluetoothStatus->updateStatus(&newStatus); if (bluetoothPhoneAPI) { - std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); bluetoothPhoneAPI->close(); - bluetoothPhoneAPI->hasChecked = false; - bluetoothPhoneAPI->phoneWants = false; - bluetoothPhoneAPI->numBytes = 0; - bluetoothPhoneAPI->queue_size = 0; + + { // scope for fromPhoneMutex mutex + std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex); + bluetoothPhoneAPI->fromPhoneQueueSize = 0; + } + + bluetoothPhoneAPI->onReadCallbackIsWaitingForData = false; + { // scope for toPhoneMutex mutex + std::lock_guard guard(bluetoothPhoneAPI->toPhoneMutex); + bluetoothPhoneAPI->toPhoneQueueSize = 0; + } + + bluetoothPhoneAPI->readCount = 0; + bluetoothPhoneAPI->notifyCount = 0; + bluetoothPhoneAPI->writeCount = 0; } // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection memset(lastToRadio, 0, sizeof(lastToRadio)); + + nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; // BLE_HS_CONN_HANDLE_NONE means "no connection" + #ifdef NIMBLE_TWO // Restart Advertising ble->startAdvertising(); +#else + NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + if (!pAdvertising->start(0)) { + if (pAdvertising->isAdvertising()) { + LOG_DEBUG("BLE advertising already running"); + } else { + LOG_ERROR("BLE failed to restart advertising"); + } + } #endif } }; @@ -328,6 +798,30 @@ void NimbleBluetooth::setup() NimBLEDevice::init(getDeviceName()); NimBLEDevice::setPower(ESP_PWR_LVL_P9); +#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) + int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu); + if (mtuResult == 0) { + LOG_INFO("BLE MTU request set to %u", kPreferredBleMtu); + } else { + LOG_WARN("Unable to request MTU %u, rc=%d", kPreferredBleMtu, mtuResult); + } + + int phyResult = ble_gap_set_prefered_default_le_phy(BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK); + if (phyResult == 0) { + LOG_INFO("BLE default PHY preference set to 2M"); + } else { + LOG_WARN("Failed to prefer 2M PHY by default, rc=%d", phyResult); + } + + int dataLenResult = ble_gap_write_sugg_def_data_len(kPreferredBleTxOctets, kPreferredBleTxTimeUs); + if (dataLenResult == 0) { + LOG_INFO("BLE suggested data length set to %u bytes", kPreferredBleTxOctets); + } else { + LOG_WARN("Failed to raise suggested data length (%u/%u), rc=%d", kPreferredBleTxOctets, kPreferredBleTxTimeUs, + dataLenResult); + } +#endif + if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC); NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); @@ -353,6 +847,7 @@ void NimbleBluetooth::setupService() // Define the characteristics that the app is looking for if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE); + // Allow notifications so phones can stream FromRadio without polling. FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ); fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); logRadioCharacteristic = @@ -471,4 +966,4 @@ void clearNVS() ESP.restart(); #endif } -#endif \ No newline at end of file +#endif diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 6b658c2a3..53b23124d 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -201,6 +201,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4 #elif defined(M5STACK_UNITC6L) #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L +#elif defined(HELTEC_WIRELESS_TRACKER_V2) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 #endif // ----------------------------------------------------------------------------- diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index b167882d4..cee0e5a10 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -55,6 +55,8 @@ #define HW_VENDOR meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER #elif defined(NOMADSTAR_METEOR_PRO) #define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO +#elif defined(R1_NEO) +#define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO // MAke sure all custom RAK4630 boards are defined before the generic RAK4630 #elif defined(RAK4630) #define HW_VENDOR meshtastic_HardwareModel_RAK4631 @@ -98,8 +100,6 @@ #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK #elif defined(SEEED_WIO_TRACKER_L1) #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 -#elif defined(R1_NEO) -#define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO #elif defined(HELTEC_MESH_SOLAR) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR #else diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index b11d2547b..dbc90f9a4 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -202,7 +202,7 @@ void portduinoSetup() exit(EXIT_SUCCESS); } - if (portduino_config.lora_module == use_simradio) { + if (portduino_config.force_simradio) { std::cout << "Running in simulated mode." << std::endl; portduino_config.MaxNodes = 200; // Default to 200 nodes // Set the random seed equal to TCPPort to have a different seed per instance diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index ec6209487..3fe017d5e 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -444,12 +444,16 @@ extern struct portduino_config_struct { switch (configDisplayMode) { case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; + break; case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; + break; case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; + break; case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; + break; } out << YAML::EndMap; // Config diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp index cea1eab3a..6e7fe24cb 100644 --- a/src/platform/portduino/SimRadio.cpp +++ b/src/platform/portduino/SimRadio.cpp @@ -13,7 +13,12 @@ ErrorCode SimRadio::send(meshtastic_MeshPacket *p) { printPacket("enqueuing for send", p); - ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN; + bool dropped = false; + ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN; + + if (dropped) { + txDrop++; + } if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks packetPool.release(p); @@ -182,7 +187,7 @@ void SimRadio::onNotify(uint32_t notification) assert(txp); startSend(txp); // Packet has been sent, count it toward our TX airtime utilization. - uint32_t xmitMsec = getPacketTime(txp); + uint32_t xmitMsec = RadioInterface::getPacketTime(txp); airTime->logAirtime(TX_LOG, xmitMsec); notifyLater(xmitMsec, ISR_TX, false); // Model the time it is busy sending @@ -252,7 +257,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p) if (isActivelyReceiving()) { LOG_WARN("Collision detected, dropping current and previous packet!"); rxBad++; - airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket)); + airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket, true)); packetPool.release(receivingPacket); receivingPacket = nullptr; return; @@ -270,7 +275,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p) } isReceiving = true; receivingPacket = packetPool.allocCopy(*p); - uint32_t airtimeMsec = getPacketTime(p); + uint32_t airtimeMsec = getPacketTime(p, true); notifyLater(airtimeMsec, ISR_RX, false); // Model the time it is busy receiving #else isReceiving = true; @@ -311,7 +316,7 @@ void SimRadio::handleReceiveInterrupt() printPacket("Lora RX", mp); - airTime->logAirtime(RX_LOG, getPacketTime(mp)); + airTime->logAirtime(RX_LOG, RadioInterface::getPacketTime(mp, true)); deliverToReceiver(mp); } @@ -332,4 +337,29 @@ int16_t SimRadio::readData(uint8_t *data, size_t len) } return state; +} + +/** + * Calculate airtime per + * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf + * section 4 + * + * @return num msecs for the packet + */ +uint32_t SimRadio::getPacketTime(uint32_t pl, bool received) +{ + float bandwidthHz = bw * 1000.0f; + bool headDisable = false; // we currently always use the header + float tSym = (1 << sf) / bandwidthHz; + + bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms + + float tPreamble = (preambleLength + 4.25f) * tSym; + float numPayloadSym = + 8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f); + float tPayload = numPayloadSym * tSym; + float tPacket = tPreamble + tPayload; + + uint32_t msecs = tPacket * 1000; + return msecs; } \ No newline at end of file diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h index d8b53739f..6f80989da 100644 --- a/src/platform/portduino/SimRadio.h +++ b/src/platform/portduino/SimRadio.h @@ -52,6 +52,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr * Debugging counts */ uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0; + uint16_t txDrop = 0; protected: /// are _trying_ to receive a packet currently (note - we might just be waiting for one) @@ -88,6 +89,8 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr /** * If a send was in progress finish it and return the buffer to the pool */ void completeSending(); + + virtual uint32_t getPacketTime(uint32_t pl, bool received = false) override; }; extern SimRadio *simRadio; \ No newline at end of file diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index 5a1f8ed7e..b31d2dc2e 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -413,7 +413,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["from"] = new JSONValue((unsigned int)mp->from); jsonObj["channel"] = new JSONValue((unsigned int)mp->channel); jsonObj["type"] = new JSONValue(msgType.c_str()); - jsonObj["sender"] = new JSONValue(owner.id); + jsonObj["sender"] = new JSONValue(nodeDB->getNodeId().c_str()); if (mp->rx_rssi != 0) jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi); if (mp->rx_snr != 0) diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index e0daa1a88..353c710a1 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -353,7 +353,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["from"] = (unsigned int)mp->from; jsonObj["channel"] = (unsigned int)mp->channel; jsonObj["type"] = msgType.c_str(); - jsonObj["sender"] = owner.id; + jsonObj["sender"] = nodeDB->getNodeId().c_str(); if (mp->rx_rssi != 0) jsonObj["rssi"] = (int)mp->rx_rssi; if (mp->rx_snr != 0) diff --git a/src/sleep.cpp b/src/sleep.cpp index ec560cb90..756582c74 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -244,6 +244,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); #endif +#ifdef RAK_WISMESH_TAP_V2 + digitalWrite(SDCARD_CS, LOW); +#endif + #ifdef TRACKER_T1000_E #ifdef GNSS_AIROHA digitalWrite(GPS_VRTC_EN, LOW); @@ -553,7 +557,7 @@ void enableLoraInterrupt() gpio_pullup_en((gpio_num_t)LORA_CS); #endif -#ifdef HELTEC_V4 +#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); diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp index ede3d22b7..1c2f0642a 100644 --- a/test/test_mqtt/MQTT.cpp +++ b/test/test_mqtt/MQTT.cpp @@ -332,7 +332,7 @@ void setUp(void) }; channelFile.channels_count = 1; owner = meshtastic_User{.id = "!12345678"}; - myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 10}; + myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 0x12345678}; // Match the expected gateway ID in topic localPosition = meshtastic_Position{.has_latitude_i = true, .latitude_i = 7 * 1e7, .has_longitude_i = true, .longitude_i = 3 * 1e7}; @@ -591,7 +591,7 @@ void test_receiveEncryptedPKITopicToUs(void) // Should ignore messages published to MQTT by this gateway. void test_receiveIgnoresOwnPublishedMessages(void) { - unitTest->publish(&decoded, owner.id); + unitTest->publish(&decoded, nodeDB->getNodeId().c_str()); TEST_ASSERT_TRUE(mockRouter->packets_.empty()); TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty()); @@ -603,7 +603,7 @@ void test_receiveAcksOwnSentMessages(void) meshtastic_MeshPacket p = decoded; p.from = myNodeInfo.my_node_num; - unitTest->publish(&p, owner.id); + unitTest->publish(&p, nodeDB->getNodeId().c_str()); TEST_ASSERT_TRUE(mockRouter->packets_.empty()); TEST_ASSERT_EQUAL(1, mockRoutingModule->ackNacks_.size()); diff --git a/variants/esp32/diy/hydra/variant.h b/variants/esp32/diy/hydra/variant.h index 0d64c1b5e..e5c10e26b 100644 --- a/variants/esp32/diy/hydra/variant.h +++ b/variants/esp32/diy/hydra/variant.h @@ -6,7 +6,6 @@ #define GPS_TX_PIN 15 #define GPS_RX_PIN 12 #define PIN_GPS_EN 4 -#define GPS_POWER_TOGGLE // Moved definition from platformio.ini to here #define BUTTON_PIN 39 // The middle button GPIO on the T-Beam // Note: On the ESP32 base version, gpio34-39 are input-only, and do not have internal pull-ups. diff --git a/variants/esp32/heltec_v2.1/platformio.ini b/variants/esp32/heltec_v2.1/platformio.ini index 763f9764c..4dcd9e583 100644 --- a/variants/esp32/heltec_v2.1/platformio.ini +++ b/variants/esp32/heltec_v2.1/platformio.ini @@ -7,4 +7,3 @@ build_flags = ${esp32_base.build_flags} -D HELTEC_V2_1 -I variants/esp32/heltec_v2.1 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/esp32/heltec_wsl_v2.1/platformio.ini b/variants/esp32/heltec_wsl_v2.1/platformio.ini index eb44c88d2..6a77cf11b 100644 --- a/variants/esp32/heltec_wsl_v2.1/platformio.ini +++ b/variants/esp32/heltec_wsl_v2.1/platformio.ini @@ -6,4 +6,3 @@ build_flags = ${esp32_base.build_flags} -D PRIVATE_HW -I variants/esp32/heltec_wsl_v2.1 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini index ea17751c6..e53f22d30 100644 --- a/variants/esp32/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -10,7 +10,6 @@ build_flags = ${esp32_base.build_flags} -D TBEAM_V10 -I variants/esp32/tbeam - -DGPS_POWER_TOGGLE ; comment this line to disable double press function on the user button to turn off gps entirely. -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue upload_speed = 921600 diff --git a/variants/esp32/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini index bd85aa847..8d5bdab9e 100644 --- a/variants/esp32/tlora_v2_1_16/platformio.ini +++ b/variants/esp32/tlora_v2_1_16/platformio.ini @@ -4,5 +4,13 @@ board = ttgo-lora32-v21 board_check = true build_flags = ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. upload_speed = 115200 + +[env:sugarcube] +extends = env:tlora-v2-1-1_6 +board_level = extra +build_flags = + ${env:tlora-v2-1-1_6.build_flags} + -DBUTTON_PIN=0 + -DPIN_BUZZER=25 + -DLED_PIN=-1 \ No newline at end of file diff --git a/variants/esp32/tlora_v2_1_16/variant.h b/variants/esp32/tlora_v2_1_16/variant.h index 48c069ab7..9584dd68b 100644 --- a/variants/esp32/tlora_v2_1_16/variant.h +++ b/variants/esp32/tlora_v2_1_16/variant.h @@ -8,7 +8,11 @@ #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 +#if defined(LED_PIN) && LED_PIN == -1 +#undef LED_PIN +#else #define LED_PIN 25 // If defined we will blink this LED +#endif #define USE_RF95 #define LORA_DIO0 26 // a No connect on the SX1262 module diff --git a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini index 9404faa02..a6b9d2254 100644 --- a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini +++ b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini @@ -6,6 +6,5 @@ build_flags = ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D LORA_TCXO_GPIO=33 upload_speed = 115200 \ No newline at end of file diff --git a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini index f1110386e..1258fd8b7 100644 --- a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini +++ b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini @@ -5,6 +5,5 @@ build_flags = ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D LORA_TCXO_GPIO=12 -D BUTTON_PIN=0 \ No newline at end of file diff --git a/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini b/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini index dbd420f04..3fcfbf281 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini +++ b/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini @@ -5,4 +5,3 @@ build_flags = ${esp32s3_base.build_flags} -D CDEBYTE_EORA_S3 -I variants/esp32s3/CDEBYTE_EoRa-S3 - -D GPS_POWER_TOGGLE diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini index 49e84bf4f..eed21a412 100644 --- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini +++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini @@ -16,7 +16,6 @@ build_flags = -I variants/esp32s3/crowpanel-esp32s3-5-epaper -D PRIVATE_HW -DBOARD_HAS_PSRAM - -DGPS_POWER_TOGGLE -DEINK_DISPLAY_MODEL=GxEPD2_579_GDEY0579T93 ;https://www.good-display.com/product/439.html -DEINK_WIDTH=792 -DEINK_HEIGHT=272 @@ -46,7 +45,6 @@ build_flags = -I variants/esp32s3/crowpanel-esp32s3-5-epaper -D PRIVATE_HW -DBOARD_HAS_PSRAM - -DGPS_POWER_TOGGLE -DEINK_DISPLAY_MODEL=GxEPD2_420_GYE042A87 ; similar Panel: GDEY042T81 : https://www.good-display.com/product/386.html -DEINK_WIDTH=400 -DEINK_HEIGHT=300 @@ -76,7 +74,6 @@ build_flags = -I variants/esp32s3/crowpanel-esp32s3-5-epaper -D PRIVATE_HW -DBOARD_HAS_PSRAM - -DGPS_POWER_TOGGLE -DEINK_DISPLAY_MODEL=GxEPD2_290_GDEY029T94 ;https://www.good-display.com/product/389.html -DEINK_WIDTH=296 -DEINK_HEIGHT=128 diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini index d43ffd0df..0bb21581a 100644 --- a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini +++ b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini @@ -6,5 +6,4 @@ board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_capsule_sensor_v3 -D HELTEC_CAPSULE_SENSOR_V3 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output diff --git a/variants/esp32s3/heltec_v3/platformio.ini b/variants/esp32s3/heltec_v3/platformio.ini index b521e11ca..af0854e49 100644 --- a/variants/esp32s3/heltec_v3/platformio.ini +++ b/variants/esp32s3/heltec_v3/platformio.ini @@ -8,4 +8,3 @@ build_flags = ${esp32s3_base.build_flags} -D HELTEC_V3 -I variants/esp32s3/heltec_v3 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index d0a250ad3..7057f9646 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -7,4 +7,3 @@ build_flags = ${esp32s3_base.build_flags} -D HELTEC_V4 -I variants/esp32s3/heltec_v4 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h index 2b6b7af3d..1c9516e39 100644 --- a/variants/esp32s3/heltec_v4/variant.h +++ b/variants/esp32s3/heltec_v4/variant.h @@ -37,6 +37,7 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define USE_GC1109_PA // We have a GC1109 power amplifier+attenuator #define LORA_PA_POWER 7 // power en #define LORA_PA_EN 2 #define LORA_PA_TX_EN 46 // enable tx diff --git a/variants/esp32s3/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini index 2faba45a8..3a373bf4f 100644 --- a/variants/esp32s3/heltec_wireless_tracker/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker/platformio.ini @@ -8,7 +8,6 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_wireless_tracker -D HELTEC_TRACKER_V1_1 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output lib_deps = diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini index 89fe4b385..cd961533d 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini @@ -8,7 +8,6 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_wireless_tracker_V1_0 -D HELTEC_TRACKER_V1_0 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output lib_deps = ${esp32s3_base.lib_deps} diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h new file mode 100644 index 000000000..61c319109 --- /dev/null +++ b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h @@ -0,0 +1,71 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "soc/soc_caps.h" +#include + +#define DISPLAY_HEIGHT 80 +#define DISPLAY_WIDTH 160 + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +static const uint8_t LED_BUILTIN = 18; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +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 SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 3; +static const uint8_t LED = 18; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini new file mode 100644 index 000000000..0f9265f91 --- /dev/null +++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini @@ -0,0 +1,13 @@ +[env:heltec-wireless-tracker-v2] +extends = esp32s3_base +board = heltec_wireless_tracker_v2 +board_build.partitions = default_8MB.csv +upload_protocol = esptool + +build_flags = + ${esp32s3_base.build_flags} + -I variants/esp32s3/heltec_wireless_tracker_v2 + -D HELTEC_WIRELESS_TRACKER_V2 +lib_deps = + ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@^1.2.0 diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h new file mode 100644 index 000000000..9ac064ea2 --- /dev/null +++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h @@ -0,0 +1,78 @@ +#define LED_PIN 18 + +#define _VARIANT_HELTEC_WIRELESS_TRACKER + +// I2C +#define I2C_SDA SDA +#define I2C_SCL SCL + +// ST7735S TFT LCD +#define ST7735S 1 // there are different (sub-)versions of ST7735 +#define ST7735_CS 38 +#define ST7735_RS 40 // DC +#define ST7735_SDA 42 // MOSI +#define ST7735_SCK 41 +#define ST7735_RESET 39 +#define ST7735_MISO -1 +#define ST7735_BUSY -1 +#define TFT_BL 21 +#define ST7735_SPI_HOST SPI3_HOST +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define SCREEN_ROTATE +#define TFT_HEIGHT DISPLAY_WIDTH +#define TFT_WIDTH DISPLAY_HEIGHT +#define TFT_OFFSET_X 24 +#define TFT_OFFSET_Y 0 +#define TFT_INVERT false +#define SCREEN_TRANSITION_FRAMERATE 3 // fps +#define DISPLAY_FORCE_SMALL_FONTS + +#define VEXT_ENABLE 3 // active HIGH - powers the GPS, GPS LNA and OLED +#define VEXT_ON_VALUE HIGH +#define BUTTON_PIN 0 + +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider +#define ADC_MULTIPLIER 4.9 * 1.045 +#define ADC_CTRL 2 // active HIGH, powers the voltage divider. +#define ADC_USE_PULLUP // Use internal pullup/pulldown instead of actively driving the output + +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 33 +#define GPS_TX_PIN 34 +#define PIN_GPS_RESET 35 +#define PIN_GPS_PPS 36 +// #define PIN_GPS_EN 3 // Uncomment to power off the GPS with triple-click on Tracker v2, though we'll also lose the +// display. + +#define GPS_RESET_MODE LOW +#define GPS_UC6580 +#define GPS_BAUDRATE 115200 + +#define USE_SX1262 +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TCXO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#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 USE_GC1109_PA // We have a GC1109 power amplifier+attenuator +#define LORA_PA_POWER 7 // power en +#define LORA_PA_EN 4 +#define LORA_PA_TX_EN 46 // enable tx \ No newline at end of file diff --git a/variants/esp32s3/heltec_wsl_v3/platformio.ini b/variants/esp32s3/heltec_wsl_v3/platformio.ini index 06cde2304..c038a463e 100644 --- a/variants/esp32s3/heltec_wsl_v3/platformio.ini +++ b/variants/esp32s3/heltec_wsl_v3/platformio.ini @@ -7,4 +7,3 @@ build_flags = ${esp32s3_base.build_flags} -D HELTEC_WSL_V3 -I variants/esp32s3/heltec_wsl_v3 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/esp32s3/link32_s3_v1/platformio.ini b/variants/esp32s3/link32_s3_v1/platformio.ini index c1b71b3b5..8d88075c4 100644 --- a/variants/esp32s3/link32_s3_v1/platformio.ini +++ b/variants/esp32s3/link32_s3_v1/platformio.ini @@ -5,7 +5,6 @@ build_flags = ${esp32_base.build_flags} -D LINK_32 -I variants/esp32s3/link32_s3_v1 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DARDUINO_USB_CDC_ON_BOOT -DARDUINO_USB_MODE=1 -DRADIOLIB_EXCLUDE_SX128X=1 diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini index 45c3ae4ea..0b3c7843a 100644 --- a/variants/esp32s3/t-deck-pro/platformio.ini +++ b/variants/esp32s3/t-deck-pro/platformio.ini @@ -7,7 +7,6 @@ upload_protocol = esptool build_flags = ${esp32_base.build_flags} -I variants/esp32s3/t-deck-pro -D T_DECK_PRO - -D GPS_POWER_TOGGLE -D USE_EINK -D EINK_DISPLAY_MODEL=GxEPD2_310_GDEQ031T10 -D EINK_WIDTH=240 diff --git a/variants/esp32s3/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini index 7c8070c3e..9ab0756d1 100644 --- a/variants/esp32s3/t-deck/platformio.ini +++ b/variants/esp32s3/t-deck/platformio.ini @@ -9,7 +9,6 @@ upload_protocol = esptool build_flags = ${esp32s3_base.build_flags} -D T_DECK -D BOARD_HAS_PSRAM - -D GPS_POWER_TOGGLE -I variants/esp32s3/t-deck lib_deps = ${esp32s3_base.lib_deps} diff --git a/variants/esp32s3/t-eth-elite/platformio.ini b/variants/esp32s3/t-eth-elite/platformio.ini index 6107185ce..1a5823bc3 100644 --- a/variants/esp32s3/t-eth-elite/platformio.ini +++ b/variants/esp32s3/t-eth-elite/platformio.ini @@ -9,7 +9,6 @@ build_flags = -D T_ETH_ELITE -D HAS_UDP_MULTICAST=1 -I variants/esp32s3/t-eth-elite - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. lib_ignore = Ethernet diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 9800161bb..d63537904 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -10,7 +10,6 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/tlora-pager -D T_LORA_PAGER -D BOARD_HAS_PSRAM - -D GPS_POWER_TOGGLE -D HAS_SDCARD -D SDCARD_USE_SPI1 -D ENABLE_ROTARY_PULLUP diff --git a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini index 71644ee77..eca052f57 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini +++ b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini @@ -8,7 +8,6 @@ build_flags = ${esp32_base.build_flags} -D TLORA_T3S3_EPAPER -I variants/esp32s3/tlora_t3s3_epaper - -DGPS_POWER_TOGGLE -DUSE_EINK -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 diff --git a/variants/esp32s3/tlora_t3s3_v1/platformio.ini b/variants/esp32s3/tlora_t3s3_v1/platformio.ini index d9624f043..56ece0d62 100644 --- a/variants/esp32s3/tlora_t3s3_v1/platformio.ini +++ b/variants/esp32s3/tlora_t3s3_v1/platformio.ini @@ -6,4 +6,3 @@ upload_protocol = esptool build_flags = ${esp32_base.build_flags} -D TLORA_T3S3_V1 -I variants/esp32s3/tlora_t3s3_v1 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/esp32s3/tracksenger/platformio.ini b/variants/esp32s3/tracksenger/platformio.ini index 0e9f08541..213a917b1 100644 --- a/variants/esp32s3/tracksenger/platformio.ini +++ b/variants/esp32s3/tracksenger/platformio.ini @@ -8,7 +8,6 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/tracksenger/internal -D HELTEC_TRACKER_V1_1 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output lib_deps = @@ -25,7 +24,6 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/tracksenger/lcd -D HELTEC_TRACKER_V1_1 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output lib_deps = @@ -42,5 +40,4 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/tracksenger/oled -D HELTEC_TRACKER_V1_1 - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output diff --git a/variants/native/portduino-buildroot/platformio.ini b/variants/native/portduino-buildroot/platformio.ini index d1bd39e10..a3d0f4639 100644 --- a/variants/native/portduino-buildroot/platformio.ini +++ b/variants/native/portduino-buildroot/platformio.ini @@ -4,5 +4,6 @@ extends = portduino_base ; environment variable in the buildroot environment. build_flags = ${portduino_base.build_flags} -O0 -I variants/native/portduino-buildroot board = buildroot +board_level = extra lib_deps = ${portduino_base.lib_deps} build_src_filter = ${portduino_base.build_src_filter} \ No newline at end of file diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini index c47ab8bf1..49a8a71c7 100644 --- a/variants/native/portduino/platformio.ini +++ b/variants/native/portduino/platformio.ini @@ -3,6 +3,7 @@ extends = portduino_base build_flags = ${portduino_base.build_flags} -I variants/native/portduino -I /usr/include board = cross_platform +board_level = extra lib_deps = ${portduino_base.lib_deps} melopero/Melopero RV3028@^1.1.0 @@ -16,6 +17,7 @@ extends = native_base build_flags = ${native_base.build_flags} !pkg-config --libs libulfius --silence-errors || : !pkg-config --libs openssl --silence-errors || : + !pkg-config --cflags --libs sdl2 --silence-errors || : [env:native-tft] extends = native_base @@ -40,35 +42,16 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio -D VIEW_320x240 !pkg-config --libs libulfius --silence-errors || : !pkg-config --libs openssl --silence-errors || : + !pkg-config --cflags --libs sdl2 --silence-errors || : build_src_filter = ${native_base.build_src_filter} -[env:native-sdl] -extends = native_base -build_type = release -lib_deps = - ${env.lib_deps} - ${networking_base.lib_deps} - ${radiolib_base.lib_deps} - ${environmental_base.lib_deps} - # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto - rweather/Crypto@0.4.0 - # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main - https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip - # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library - adafruit/Adafruit seesaw Library@1.7.9 - https://github.com/jp-bennett/LovyanGFX/archive/7458f84a126c1f8fdc7b038074f71be903f6e4c0.zip -build_flags = ${native_base.build_flags} - !pkg-config --cflags --libs sdl2 --silence-errors || : - -D LGFX_SDL=1 - [env:native-fb] extends = native_base build_type = release lib_deps = ${native_base.lib_deps} ${device-ui_base.lib_deps} -board_level = extra build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -D RAM_SIZE=8192 -D USE_FRAMEBUFFER=1 @@ -97,7 +80,6 @@ build_type = debug lib_deps = ${native_base.lib_deps} ${device-ui_base.lib_deps} -board_level = extra build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -lxkbcommon -D DEBUG_HEAP -D RAM_SIZE=16384 @@ -126,3 +108,7 @@ build_src_filter = ${env:native-tft.build_src_filter} [env:coverage] extends = env:native build_flags = -lgcov --coverage -fprofile-abs-path -fsanitize=address ${env:native.build_flags} +; https://docs.platformio.org/en/latest/projectconf/sections/env/options/test/test_testing_command.html +test_testing_command = + ${platformio.build_dir}/${this.__env__}/program + -s diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini index 0578bcfe8..f89b05d1f 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini @@ -9,7 +9,6 @@ debug_tool = jlink build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/ELECROW-ThinkNode-M1 -DELECROW_ThinkNode_M1 - -DGPS_POWER_TOGGLE -DUSE_EINK -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 -DEINK_WIDTH=200 diff --git a/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini index 89a45694c..1279f12c6 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini +++ b/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini @@ -8,7 +8,6 @@ build_flags = ${nrf52840_base.build_flags} -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS01-4Y10TD> lib_deps = diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini index ad5867bd5..f8d6da008 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini @@ -8,7 +8,6 @@ build_flags = ${nrf52840_base.build_flags} -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_420_GDEY042T81 -DEINK_WIDTH=400 -DEINK_HEIGHT=300 diff --git a/variants/nrf52840/MS24SF1/platformio.ini b/variants/nrf52840/MS24SF1/platformio.ini index f162cbd60..df15b5605 100644 --- a/variants/nrf52840/MS24SF1/platformio.ini +++ b/variants/nrf52840/MS24SF1/platformio.ini @@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/MS24SF1 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/MS24SF1> lib_deps = diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h index e93442c7e..87342a02f 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h @@ -94,7 +94,6 @@ NRF52 PRO MICRO PIN ASSIGNMENT #define PIN_GPS_RX (0 + 20) // P0.20 #define PIN_GPS_EN (0 + 24) // P0.24 -#define GPS_POWER_TOGGLE #define GPS_UBLOX // define GPS_DEBUG diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h index 7aafab7da..6e208e79f 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h @@ -93,7 +93,6 @@ NRF52 PRO MICRO PIN ASSIGNMENT #define PIN_GPS_RX (0 + 20) // P0.20 #define PIN_GPS_EN (0 + 24) // P0.24 -#define GPS_POWER_TOGGLE #define GPS_UBLOX // define GPS_DEBUG diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini index 5c1047aae..c6cd23314 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini +++ b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini @@ -6,8 +6,8 @@ board = gat562_mesh_trial_tracker board_check = true build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/gat562_mesh_trial_tracker - -D GAT562_MESH_TRIAL_TRACKER - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + ;-D GAT562_MESH_TRIAL_TRACKER + -D PRIVATE_HW -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 diff --git a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini index c7b30b339..c49dadd56 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini +++ b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini @@ -8,7 +8,6 @@ debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/heltec_mesh_node_t114 - -DGPS_POWER_TOGGLE -DHELTEC_T114 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_node_t114> diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini index 625dd1d43..36a7904d6 100644 --- a/variants/nrf52840/heltec_mesh_solar/platformio.ini +++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini @@ -8,7 +8,6 @@ debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/heltec_mesh_solar - -DGPS_POWER_TOGGLE -DHELTEC_MESH_SOLAR build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_solar> diff --git a/variants/nrf52840/meshlink/platformio.ini b/variants/nrf52840/meshlink/platformio.ini index 466362242..2a4e27fe8 100644 --- a/variants/nrf52840/meshlink/platformio.ini +++ b/variants/nrf52840/meshlink/platformio.ini @@ -9,7 +9,6 @@ board_level = extra build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/meshlink -D MESHLINK - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D EINK_DISPLAY_MODEL=GxEPD2_213_B74 -D EINK_WIDTH=250 -D EINK_HEIGHT=122 diff --git a/variants/nrf52840/meshlink_eink/platformio.ini b/variants/nrf52840/meshlink_eink/platformio.ini index af5a0040e..c0c0cb1dd 100644 --- a/variants/nrf52840/meshlink_eink/platformio.ini +++ b/variants/nrf52840/meshlink_eink/platformio.ini @@ -9,7 +9,6 @@ board_level = extra build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/meshlink_eink -D MESHLINK - -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D EINK_DISPLAY_MODEL=GxEPD2_213_B74 -D EINK_WIDTH=250 -D EINK_HEIGHT=122 diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index d1139b3be..55aabe930 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -19,7 +19,9 @@ #ifndef _VARIANT_MESHTINY_ #define _VARIANT_MESHTINY_ +#ifndef MESHTINY #define MESHTINY +#endif /** Master clock frequency */ #define VARIANT_MCK (64000000ul) diff --git a/variants/nrf52840/r1-neo/platformio.ini b/variants/nrf52840/r1-neo/platformio.ini index 6feb55dc9..60f1f6ae1 100644 --- a/variants/nrf52840/r1-neo/platformio.ini +++ b/variants/nrf52840/r1-neo/platformio.ini @@ -6,7 +6,6 @@ board_check = true build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/r1-neo -D R1_NEO - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 diff --git a/variants/nrf52840/rak2560/platformio.ini b/variants/nrf52840/rak2560/platformio.ini index edc648b9b..021e6d03b 100644 --- a/variants/nrf52840/rak2560/platformio.ini +++ b/variants/nrf52840/rak2560/platformio.ini @@ -6,7 +6,6 @@ board_check = true build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/rak2560 -D RAK_4631 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 diff --git a/variants/nrf52840/rak3401_1watt/platformio.ini b/variants/nrf52840/rak3401_1watt/platformio.ini new file mode 100644 index 000000000..1a915a6b3 --- /dev/null +++ b/variants/nrf52840/rak3401_1watt/platformio.ini @@ -0,0 +1,31 @@ +; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +[env:rak3401-1watt] +extends = nrf52840_base +board = wiscore_rak4631 +board_check = true +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/rak3401_1watt + -D RAK_4631 +; -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -D RAK3401 + -D RAK13302 ; RAK 1Watt Power Amplifier + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak3401_1watt> + +lib_deps = + ${nrf52840_base.lib_deps} + ${networking_base.lib_deps} + melopero/Melopero RV3028@^1.1.0 + rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 + beegee-tokyo/RAK12035_SoilMoisture@^1.0.4 + https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip + +; 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 + +; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!) +; programming time is about the same as the bootloader version. +; For information on this see the meshtastic developers documentation for "Development on the NRF52" + diff --git a/variants/nrf52840/rak3401_1watt/variant.cpp b/variants/nrf52840/rak3401_1watt/variant.cpp new file mode 100644 index 000000000..e84b60b3b --- /dev/null +++ b/variants/nrf52840/rak3401_1watt/variant.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h new file mode 100644 index 000000000..d4bb1a175 --- /dev/null +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -0,0 +1,226 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_RAK3401_ +#define _VARIANT_RAK3401_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Analog pins + */ +#define PIN_A0 (5) +#define PIN_A1 (31) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define WB_I2C1_SDA (13) // SENSOR_SLOT IO_SLOT +#define WB_I2C1_SCL (14) // SENSOR_SLOT IO_SLOT + +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define WB_IO5 PIN_NFC1 +#define WB_IO4 (4) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +#define PIN_SPI1_MISO (29) // (0 + 29) +#define PIN_SPI1_MOSI (30) // (0 + 30) +#define PIN_SPI1_SCK (3) // (0 + 3) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * eink display pins + */ +#define PIN_EINK_CS (0 + 26) +#define PIN_EINK_BUSY (0 + 4) +#define PIN_EINK_DC (0 + 17) +#define PIN_EINK_RES (-1) +#define PIN_EINK_SCLK (0 + 3) +#define PIN_EINK_MOSI (0 + 30) // also called SDI + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (WB_I2C1_SDA) +#define PIN_WIRE_SCL (WB_I2C1_SCL) + +// QSPI Pins +#define PIN_QSPI_SCK 3 +#define PIN_QSPI_CS 26 +#define PIN_QSPI_IO0 30 +#define PIN_QSPI_IO1 29 +#define PIN_QSPI_IO2 28 +#define PIN_QSPI_IO3 2 + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES IS25LP080D +#define EXTERNAL_FLASH_USE_QSPI + +// 1watt sx1262 RAK13302 +#define HW_SPI1_DEVICE 1 + +#define LORA_SCK PIN_SPI1_SCK +#define LORA_MISO PIN_SPI1_MISO +#define LORA_MOSI PIN_SPI1_MOSI +#define LORA_CS 26 + +#define USE_SX1262 +#define SX126X_CS (26) +#define SX126X_DIO1 (10) +#define SX126X_BUSY (9) +#define SX126X_RESET (4) + +#define SX126X_POWER_EN (21) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Testing USB detection +#define NRF_APM +// If using a power chip like the INA3221 you can override the default battery voltage channel below +// and comment out NRF_APM to use the INA3221 instead of the USB detection for charging +// #define INA3221_BAT_CH INA3221_CH2 +// #define INA3221_ENV_CH INA3221_CH1 + +// enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings +#define PIN_3V3_EN (34) +#define WB_IO2 PIN_3V3_EN + +// RAK1910 GPS module +// If using the wisblock GPS module and pluged into Port A on WisBlock base +// IO1 is hooked to PPS (pin 12 on header) = gpio 17 +// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). +// Therefore must be 1 to keep peripherals powered +// Power is on the controllable 3V3_S rail +// #define PIN_GPS_RESET (34) +// #define PIN_GPS_EN PIN_3V3_EN +#define PIN_GPS_PPS (17) // Pulse per second input from the GPS + +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press + +// RAK12002 RTC Module +#define RV3028_RTC (uint8_t)0b1010010 + +// RAK18001 Buzzer in Slot C +// #define PIN_BUZZER 21 // IO3 is PWM2 +// NEW: set this via protobuf instead! + +// Battery +// The battery sense is hooked to pin A0 (5) +#define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 + +#define HAS_RTC 1 + +#define RAK_4631 1 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif diff --git a/variants/nrf52840/rak4631/platformio.ini b/variants/nrf52840/rak4631/platformio.ini index 6bf5f44cb..205966529 100644 --- a/variants/nrf52840/rak4631/platformio.ini +++ b/variants/nrf52840/rak4631/platformio.ini @@ -7,7 +7,6 @@ board_check = true build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/rak4631 -D RAK_4631 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 diff --git a/variants/nrf52840/rak4631_eth_gw/platformio.ini b/variants/nrf52840/rak4631_eth_gw/platformio.ini index 4be8843a2..3c61e3498 100644 --- a/variants/nrf52840/rak4631_eth_gw/platformio.ini +++ b/variants/nrf52840/rak4631_eth_gw/platformio.ini @@ -6,7 +6,6 @@ board_check = true build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/rak4631_eth_gw -D RAK_4631 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DHAS_UDP_MULTICAST=1 -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini index e94eef1ee..d7dab2678 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini @@ -6,7 +6,6 @@ board_check = true build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/rak4631_nomadstar_meteor_pro -D NOMADSTAR_METEOR_PRO - ;-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 diff --git a/variants/nrf52840/rak_wismeshtag/platformio.ini b/variants/nrf52840/rak_wismeshtag/platformio.ini index 08e723302..f04d1f186 100644 --- a/variants/nrf52840/rak_wismeshtag/platformio.ini +++ b/variants/nrf52840/rak_wismeshtag/platformio.ini @@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/rak_wismeshtag -D WISMESH_TAG -D RAK_4631 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 diff --git a/variants/nrf52840/rak_wismeshtap/platformio.ini b/variants/nrf52840/rak_wismeshtap/platformio.ini index adf301537..3369f9c77 100644 --- a/variants/nrf52840/rak_wismeshtap/platformio.ini +++ b/variants/nrf52840/rak_wismeshtap/platformio.ini @@ -6,7 +6,6 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/rak_wismeshtap -DWISMESH_TAP -DRAK_4631 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 diff --git a/variants/nrf52840/t-echo-lite/platformio.ini b/variants/nrf52840/t-echo-lite/platformio.ini index 68ae59dcb..90e6487a7 100644 --- a/variants/nrf52840/t-echo-lite/platformio.ini +++ b/variants/nrf52840/t-echo-lite/platformio.ini @@ -9,7 +9,6 @@ debug_tool = jlink build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/t-echo-lite -D T_ECHO_LITE - -D GPS_POWER_TOGGLE -D EINK_DISPLAY_MODEL=GxEPD2_122_T61 -D EINK_WIDTH=192 -D EINK_HEIGHT=176 diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 2e2cdce72..0748f6d48 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -140,11 +140,12 @@ static const uint8_t A0 = PIN_A0; #define HAS_GPS 1 // #define PIN_GPS_REINIT (32 + 5) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K -#define PIN_GPS_STANDBY (32 + 10) // An output to wake GPS, low means allow sleep, high means force wake -// Seems to be missing on this new board -#define PIN_GPS_PPS (0 + 29) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 15) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 13) // This is for bits going TOWARDS the GPS +#define PIN_GPS_EN (32 + 11) // GPS power +#define GPS_EN_ACTIVE 1 +#define PIN_GPS_STANDBY (32 + 13) // wakeup pin +#define PIN_GPS_PPS (32 + 15) +#define GPS_TX_PIN (32 + 10) // L76K module RX PIN +#define GPS_RX_PIN (0 + 29) // L76K module TX PIN #define GPS_THREAD_INTERVAL 50 @@ -204,4 +205,4 @@ static const uint8_t A0 = PIN_A0; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/t-echo/platformio.ini b/variants/nrf52840/t-echo/platformio.ini index 6541c9796..051fb3099 100644 --- a/variants/nrf52840/t-echo/platformio.ini +++ b/variants/nrf52840/t-echo/platformio.ini @@ -9,7 +9,6 @@ debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/t-echo - -DGPS_POWER_TOGGLE -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 -DEINK_WIDTH=200 -DEINK_HEIGHT=200 diff --git a/variants/nrf52840/tracker-t1000-e/platformio.ini b/variants/nrf52840/tracker-t1000-e/platformio.ini index c6c3f269c..905d751fd 100644 --- a/variants/nrf52840/tracker-t1000-e/platformio.ini +++ b/variants/nrf52840/tracker-t1000-e/platformio.ini @@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags} -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DTRACKER_T1000_E - -DGPS_POWER_TOGGLE -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL=1 -DMESHTASTIC_EXCLUDE_CANNEDMESSAGES=1 -DMESHTASTIC_EXCLUDE_SCREEN=1 diff --git a/variants/nrf52840/wio-sdk-wm1110/platformio.ini b/variants/nrf52840/wio-sdk-wm1110/platformio.ini index 2c65246b8..028129783 100644 --- a/variants/nrf52840/wio-sdk-wm1110/platformio.ini +++ b/variants/nrf52840/wio-sdk-wm1110/platformio.ini @@ -14,7 +14,6 @@ build_flags = ${nrf52840_base.build_flags} -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DCFG_TUD_CDC=0 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/wio-sdk-wm1110> diff --git a/variants/nrf52840/wio-t1000-s/platformio.ini b/variants/nrf52840/wio-t1000-s/platformio.ini index 3594bcf07..c6b61fc8a 100644 --- a/variants/nrf52840/wio-t1000-s/platformio.ini +++ b/variants/nrf52840/wio-t1000-s/platformio.ini @@ -8,7 +8,6 @@ build_flags = ${nrf52840_base.build_flags} -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/wio-t1000-s> lib_deps = diff --git a/variants/nrf52840/wio-tracker-wm1110/platformio.ini b/variants/nrf52840/wio-tracker-wm1110/platformio.ini index b383043bb..73b7dedd4 100644 --- a/variants/nrf52840/wio-tracker-wm1110/platformio.ini +++ b/variants/nrf52840/wio-tracker-wm1110/platformio.ini @@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags} -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/wio-tracker-wm1110> lib_deps = diff --git a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini index 290982405..c5af9a4a4 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini +++ b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini @@ -6,9 +6,12 @@ board_level = extra build_flags = ${stm32_base.build_flags} -Ivariants/stm32/CDEBYTE_E77-MBL - -DSERIAL_UART_INSTANCE=1 + -DSERIAL_UART_INSTANCE=2 -DPIN_SERIAL_RX=PA3 -DPIN_SERIAL_TX=PA2 + -DENABLE_HWSERIAL1 + -DPIN_SERIAL1_RX=PB7 + -DPIN_SERIAL1_TX=PB6 -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 -DMESHTASTIC_EXCLUDE_I2C=1 -DMESHTASTIC_EXCLUDE_GPS=1 diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h index 317f44489..e3d111a33 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/variant.h +++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h @@ -18,4 +18,6 @@ Do not expect a working Meshtastic device with this target. #define LED_PIN PB4 // LED1 // #define LED_PIN PB3 // LED2 #define LED_STATE_ON 1 + +#define EBYTE_E77_MBL #endif diff --git a/variants/stm32/rak3172/platformio.ini b/variants/stm32/rak3172/platformio.ini index 7fc6c7cba..b9a4b8a04 100644 --- a/variants/stm32/rak3172/platformio.ini +++ b/variants/stm32/rak3172/platformio.ini @@ -6,7 +6,6 @@ board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem build_flags = ${stm32_base.build_flags} -Ivariants/stm32/rak3172 - -DRAK3172 -DENABLE_HWSERIAL1 -DPIN_SERIAL1_RX=PB7 -DPIN_SERIAL1_TX=PB6 diff --git a/variants/stm32/rak3172/variant.h b/variants/stm32/rak3172/variant.h index 45752b481..30d2b57b4 100644 --- a/variants/stm32/rak3172/variant.h +++ b/variants/stm32/rak3172/variant.h @@ -16,4 +16,6 @@ Do not expect a working Meshtastic device with this target. #define LED_PIN PA0 // Green LED #define LED_STATE_ON 1 +#define RAK3172 + #endif diff --git a/version.properties b/version.properties index 5c84a8e65..f33f0f1cb 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 12 +build = 13