diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml new file mode 100644 index 000000000..f5352b3c4 --- /dev/null +++ b/.github/workflows/build_one_arch.yml @@ -0,0 +1,500 @@ +name: Build One Arch + +on: + workflow_dispatch: + inputs: + arch: + type: choice + options: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + - native + +jobs: + setup: + strategy: + fail-fast: false + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + cache: pip + - run: pip install -U platformio + - name: Generate matrix + id: jsonStep + run: | + if [[ "$GITHUB_HEAD_REF" == "" ]]; then + TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra) + else + TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr) + fi + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" + echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + outputs: + esp32: ${{ steps.jsonStep.outputs.esp32 }} + esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} + esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} + esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} + nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} + rp2040: ${{ steps.jsonStep.outputs.rp2040 }} + rp2350: ${{ steps.jsonStep.outputs.rp2350 }} + stm32: ${{ steps.jsonStep.outputs.stm32 }} + check: ${{ steps.jsonStep.outputs.check }} + + version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Get release version string + run: | + echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT + id: version + env: + BUILD_LOCATION: local + outputs: + long: ${{ steps.version.outputs.long }} + deb: ${{ steps.version.outputs.deb }} + + build-esp32: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}} + needs: [setup, version] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32 + + build-esp32s3: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}} + needs: [setup, version] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32s3 + + build-esp32c3: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}} + needs: [setup, version] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32c3 + + build-esp32c6: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}} + needs: [setup, version] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32c6 + + build-nrf52840: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}} + needs: [setup, version] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: nrf52840 + + build-rp2040: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}} + needs: [setup, version] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: rp2040 + + build-rp2350: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}} + needs: [setup, version] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: rp2350 + + build-stm32: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }} + needs: [setup, version] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.stm32) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: stm32 + + build-debian-src: + if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} + uses: ./.github/workflows/build_debian_src.yml + with: + series: UNRELEASED + build_location: local + secrets: inherit + + package-pio-deps-native-tft: + if: ${{ inputs.arch == 'native' }} + uses: ./.github/workflows/package_pio_deps.yml + with: + pio_env: native-tft + secrets: inherit + + test-native: + if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }} + uses: ./.github/workflows/test_native.yml + + docker-deb-amd64: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + + docker-deb-amd64-tft: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + pio_env: native-tft + + docker-alp-amd64: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: alpine + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + + docker-alp-amd64-tft: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: alpine + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + pio_env: native-tft + + docker-deb-arm64: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/arm64 + runs-on: ubuntu-24.04-arm + push: false + + docker-deb-armv7: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/arm/v7 + runs-on: ubuntu-24.04-arm + push: false + + gather-artifacts: + permissions: + contents: write + pull-requests: write + strategy: + fail-fast: false + matrix: + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + runs-on: ubuntu-latest + needs: + [ + version, + build-esp32, + build-esp32s3, + build-esp32c3, + build-esp32c6, + build-nrf52840, + build-rp2040, + build-rp2350, + build-stm32, + ] + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - uses: actions/download-artifact@v5 + with: + path: ./ + pattern: firmware-${{inputs.arch}}-* + merge-multiple: true + + - name: Display structure of downloaded files + run: ls -R + + - name: Move files up + run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat + + - name: Repackage in single firmware zip + uses: actions/upload-artifact@v4 + with: + name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} + overwrite: true + path: | + ./firmware-*.bin + ./firmware-*.uf2 + ./firmware-*.hex + ./firmware-*-ota.zip + ./device-*.sh + ./device-*.bat + ./littlefs-*.bin + ./bleota*bin + ./Meshtastic_nRF52_factory_erase*.uf2 + retention-days: 30 + + - uses: actions/download-artifact@v5 + with: + name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./output + + # For diagnostics + - name: Show artifacts + run: ls -lR + + - name: Device scripts permissions + run: | + chmod +x ./output/device-install.sh + chmod +x ./output/device-update.sh + + - name: Zip firmware + run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output + + - name: Repackage in single elfs zip + uses: actions/upload-artifact@v4 + with: + name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip + overwrite: true + path: ./*.elf + retention-days: 30 + + - uses: scruplelesswizard/comment-artifact@main + if: ${{ github.event_name == 'pull_request' }} + with: + name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} + description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" + github-token: ${{ secrets.GITHUB_TOKEN }} + + release-artifacts: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' }} + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + needs: + - version + - gather-artifacts + - build-debian-src + - package-pio-deps-native-tft + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: 3.x + + - name: Create release + uses: softprops/action-gh-release@v2 + id: create_release + with: + draft: true + prerelease: true + name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha + tag_name: v${{ needs.version.outputs.long }} + body: | + Autogenerated by github action, developer should edit as required before publishing... + + - name: Download source deb + uses: actions/download-artifact@v5 + with: + pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src + merge-multiple: true + path: ./output/debian-src + + - name: Download `native-tft` pio deps + uses: actions/download-artifact@v5 + with: + pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./output/pio-deps-native-tft + + - name: Zip Linux sources + working-directory: output + run: | + zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src + zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft + + # For diagnostics + - name: Display structure of downloaded files + run: ls -lR + + - name: Add Linux sources to GtiHub Release + # Only run when targeting master branch with workflow_dispatch + if: ${{ github.ref_name == 'master' }} + run: | + gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip + gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + release-firmware: + strategy: + fail-fast: false + matrix: + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' }} + needs: [release-artifacts, version] + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: 3.x + + - uses: actions/download-artifact@v5 + with: + pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./output + + - name: Display structure of downloaded files + run: ls -lR + + - name: Device scripts permissions + run: | + chmod +x ./output/device-install.sh + chmod +x ./output/device-update.sh + + - name: Zip firmware + run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output + + - uses: actions/download-artifact@v5 + with: + name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip + merge-multiple: true + path: ./elfs + + - name: Zip debug elfs + run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs + + # For diagnostics + - name: Display structure of downloaded files + run: ls -lR + + - name: Add bins and debug elfs to GitHub Release + # Only run when targeting master branch with workflow_dispatch + if: ${{ github.ref_name == 'master' }} + run: | + gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip + gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-firmware: + runs-on: ubuntu-24.04 + if: ${{ github.event_name == 'workflow_dispatch' }} + needs: [release-firmware, version] + env: + targets: |- + esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: 3.x + + - uses: actions/download-artifact@v5 + with: + pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./publish + + - name: Publish firmware to meshtastic.github.io + uses: peaceiris/actions-gh-pages@v4 + env: + # On event/* branches, use the event name as the destination prefix + DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }} + with: + deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }} + external_repository: meshtastic/meshtastic.github.io + publish_branch: master + publish_dir: ./publish + destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }} + keep_files: true + user_name: github-actions[bot] + user_email: github-actions[bot]@users.noreply.github.com + commit_message: ${{ needs.version.outputs.long }} + enable_jekyll: true diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml new file mode 100644 index 000000000..3c83ce960 --- /dev/null +++ b/.github/workflows/build_one_target.yml @@ -0,0 +1,395 @@ +name: Build One Target + +on: + workflow_dispatch: + inputs: + arch: + type: choice + options: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + - native + target: + type: string + required: false + description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets. + + # find-target: + # type: boolean + # default: true + # description: 'Find the available targets' +jobs: + find-targets: + if: ${{ inputs.target == '' }} + strategy: + fail-fast: false + matrix: + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + cache: pip + - run: pip install -U platformio + - name: Generate matrix + id: jsonStep + run: | + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} 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 + + version: + if: ${{ inputs.target != '' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Get release version string + run: | + echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT + id: version + env: + BUILD_LOCATION: local + outputs: + long: ${{ steps.version.outputs.long }} + deb: ${{ steps.version.outputs.deb }} + + build-arch: + if: ${{ inputs.target != '' && inputs.arch != 'native' }} + needs: [version] + strategy: + fail-fast: false + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ inputs.target }} + platform: ${{ inputs.arch }} + + build-debian-src: + if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }} + uses: ./.github/workflows/build_debian_src.yml + with: + series: UNRELEASED + build_location: local + secrets: inherit + + package-pio-deps-native-tft: + if: ${{ inputs.arch == 'native' }} + uses: ./.github/workflows/package_pio_deps.yml + with: + pio_env: native-tft + secrets: inherit + + test-native: + if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }} + uses: ./.github/workflows/test_native.yml + + docker-deb-amd64: + if: ${{ inputs.target != '' && inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + + docker-deb-amd64-tft: + if: ${{ inputs.target != '' && inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + pio_env: native-tft + + docker-alp-amd64: + if: ${{ inputs.target != '' && inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: alpine + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + + docker-alp-amd64-tft: + if: ${{ inputs.target != '' && inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: alpine + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + pio_env: native-tft + + docker-deb-arm64: + if: ${{ inputs.target != '' && inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/arm64 + runs-on: ubuntu-24.04-arm + push: false + + docker-deb-armv7: + if: ${{ inputs.target != '' && inputs.arch == 'native' }} + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/arm/v7 + runs-on: ubuntu-24.04-arm + push: false + + gather-artifacts: + permissions: + contents: write + pull-requests: write + strategy: + fail-fast: false + runs-on: ubuntu-latest + needs: [version, build-arch] + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - uses: actions/download-artifact@v5 + with: + path: ./ + pattern: firmware-*-* + merge-multiple: true + + - name: Display structure of downloaded files + run: ls -R + + - name: Move files up + run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat + + - name: Repackage in single firmware zip + uses: actions/upload-artifact@v4 + with: + name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }} + overwrite: true + path: | + ./firmware-*.bin + ./firmware-*.uf2 + ./firmware-*.hex + ./firmware-*-ota.zip + ./device-*.sh + ./device-*.bat + ./littlefs-*.bin + ./bleota*bin + ./Meshtastic_nRF52_factory_erase*.uf2 + retention-days: 30 + + - uses: actions/download-artifact@v5 + with: + pattern: firmware-*-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./output + + # For diagnostics + - name: Show artifacts + run: ls -lR + + - name: Device scripts permissions + run: | + chmod +x ./output/device-install.sh + chmod +x ./output/device-update.sh + + - name: Zip firmware + run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output + + - name: Repackage in single elfs zip + uses: actions/upload-artifact@v4 + with: + name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip + overwrite: true + path: ./*.elf + retention-days: 30 + + - uses: scruplelesswizard/comment-artifact@main + if: ${{ github.event_name == 'pull_request' }} + with: + name: firmware-${{inputs.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/daily_packaging.yml b/.github/workflows/daily_packaging.yml index df5ed27d5..392faeb8a 100644 --- a/.github/workflows/daily_packaging.yml +++ b/.github/workflows/daily_packaging.yml @@ -21,12 +21,14 @@ permissions: jobs: docker-multiarch: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/docker_manifest.yml with: release_channel: daily secrets: inherit package-ppa: + if: github.repository == 'meshtastic/firmware' strategy: fail-fast: false matrix: @@ -42,6 +44,7 @@ jobs: secrets: inherit package-obs: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/package_obs.yml with: obs_project: network:Meshtastic:daily @@ -49,6 +52,7 @@ jobs: secrets: inherit hook-copr: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/hook_copr.yml with: copr_project: daily diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 6ff12221b..f61e314a7 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -3,7 +3,7 @@ concurrency: group: ci-${{ github.head_ref || github.run_id }} cancel-in-progress: true on: - # # Triggers the workflow on push but only for the master branch + # # Triggers the workflow on push but only for the main branches push: branches: - master @@ -27,6 +27,7 @@ on: jobs: setup: + if: github.repository == 'meshtastic/firmware' strategy: fail-fast: false matrix: @@ -74,6 +75,7 @@ jobs: check: ${{ steps.jsonStep.outputs.check }} version: + if: github.repository == 'meshtastic/firmware' runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -95,7 +97,7 @@ jobs: matrix: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest - if: ${{ github.event_name != 'workflow_dispatch' }} + if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }} steps: - uses: actions/checkout@v5 - name: Build base @@ -208,10 +210,11 @@ jobs: secrets: inherit test-native: - if: ${{ !contains(github.ref_name, 'event/') }} + if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }} uses: ./.github/workflows/test_native.yml docker-deb-amd64: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/docker_build.yml with: distro: debian @@ -220,6 +223,7 @@ jobs: push: false docker-deb-amd64-tft: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/docker_build.yml with: distro: debian @@ -229,6 +233,7 @@ jobs: pio_env: native-tft docker-alp-amd64: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/docker_build.yml with: distro: alpine @@ -237,6 +242,7 @@ jobs: push: false docker-alp-amd64-tft: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/docker_build.yml with: distro: alpine @@ -246,6 +252,7 @@ jobs: pio_env: native-tft docker-deb-arm64: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/docker_build.yml with: distro: debian @@ -254,6 +261,7 @@ jobs: push: false docker-deb-armv7: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/docker_build.yml with: distro: debian @@ -262,6 +270,8 @@ jobs: push: false gather-artifacts: + # trunk-ignore(checkov/CKV2_GHA_1) + if: github.repository == 'meshtastic/firmware' permissions: contents: write pull-requests: write @@ -361,7 +371,7 @@ jobs: release-artifacts: runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} + if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }} outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} needs: @@ -436,7 +446,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} + if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware'}} needs: [release-artifacts, version] steps: - name: Checkout diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml new file mode 100644 index 000000000..7f397ce18 --- /dev/null +++ b/.github/workflows/merge_queue.yml @@ -0,0 +1,508 @@ +name: Merge Queue +# Not sure how concurrency works in merge_queue, removing for now. +# concurrency: +# group: merge-queue-${{ github.head_ref || github.run_id }} +# cancel-in-progress: true +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 + - check + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + cache: pip + - run: pip install -U platformio + - name: Generate matrix + id: jsonStep + run: | + if [[ "$GITHUB_HEAD_REF" == "" ]]; then + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) + else + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} 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 + outputs: + esp32: ${{ steps.jsonStep.outputs.esp32 }} + esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} + esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} + esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} + nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} + rp2040: ${{ steps.jsonStep.outputs.rp2040 }} + rp2350: ${{ steps.jsonStep.outputs.rp2350 }} + stm32: ${{ steps.jsonStep.outputs.stm32 }} + check: ${{ steps.jsonStep.outputs.check }} + + version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Get release version string + run: | + echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT + id: version + env: + BUILD_LOCATION: local + outputs: + long: ${{ steps.version.outputs.long }} + deb: ${{ steps.version.outputs.deb }} + + check: + needs: setup + strategy: + fail-fast: true + matrix: ${{ fromJson(needs.setup.outputs.check) }} + + runs-on: ubuntu-latest + if: ${{ github.event_name != 'workflow_dispatch' }} + steps: + - uses: actions/checkout@v5 + - name: Build base + id: base + uses: ./.github/actions/setup-base + - name: Check ${{ matrix.board }} + run: bin/check-all.sh ${{ matrix.board }} + + build-esp32: + needs: [setup, version] + strategy: + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} + matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32 + + build-esp32s3: + 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 + + build-debian-src: + if: github.repository == 'meshtastic/firmware' + uses: ./.github/workflows/build_debian_src.yml + with: + series: UNRELEASED + build_location: local + secrets: inherit + + package-pio-deps-native-tft: + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/package_pio_deps.yml + with: + pio_env: native-tft + secrets: inherit + + test-native: + if: ${{ !contains(github.ref_name, 'event/') }} + uses: ./.github/workflows/test_native.yml + + docker-deb-amd64: + 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 + push: false + + gather-artifacts: + # trunk-ignore(checkov/CKV2_GHA_1) + permissions: + contents: write + pull-requests: write + strategy: + fail-fast: false + matrix: + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + runs-on: ubuntu-latest + needs: + [ + version, + build-esp32, + build-esp32s3, + build-esp32c3, + build-esp32c6, + build-nrf52840, + build-rp2040, + build-rp2350, + build-stm32, + ] + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - uses: actions/download-artifact@v5 + with: + path: ./ + pattern: firmware-${{matrix.arch}}-* + merge-multiple: true + + - name: Display structure of downloaded files + run: ls -R + + - name: Move files up + run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat + + - name: Repackage in single firmware zip + uses: actions/upload-artifact@v4 + with: + name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} + overwrite: true + path: | + ./firmware-*.bin + ./firmware-*.uf2 + ./firmware-*.hex + ./firmware-*-ota.zip + ./device-*.sh + ./device-*.bat + ./littlefs-*.bin + ./bleota*bin + ./Meshtastic_nRF52_factory_erase*.uf2 + retention-days: 30 + + - uses: actions/download-artifact@v5 + with: + name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./output + + # For diagnostics + - name: Show artifacts + run: ls -lR + + - name: Device scripts permissions + run: | + chmod +x ./output/device-install.sh + chmod +x ./output/device-update.sh + + - name: Zip firmware + run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output + + - name: Repackage in single elfs zip + uses: actions/upload-artifact@v4 + with: + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + overwrite: true + path: ./*.elf + retention-days: 30 + + - uses: scruplelesswizard/comment-artifact@main + if: ${{ github.event_name == 'pull_request' }} + with: + name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} + description: "Download firmware-${{matrix.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-${{matrix.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-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output + + - uses: actions/download-artifact@v5 + with: + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + merge-multiple: true + path: ./elfs + + - name: Zip debug elfs + run: zip -j -9 -r ./debug-elfs-${{matrix.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-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{matrix.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/.trunk/trunk.yaml b/.trunk/trunk.yaml index e10e20a04..f23c41810 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,15 +8,15 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.469 - - renovate@41.94.0 + - checkov@3.2.471 + - renovate@41.115.6 - prettier@3.6.2 - - trufflehog@3.90.5 + - trufflehog@3.90.6 - yamllint@1.37.1 - bandit@1.8.6 - trivy@0.66.0 - taplo@0.10.0 - - ruff@0.12.11 + - ruff@0.13.0 - isort@6.0.1 - markdownlint@0.45.0 - oxipng@9.1.5 diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index 108ca4910..090c141fa 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.10 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9 diff --git a/debian/changelog b/debian/changelog index 286349dd2..76e390001 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,109 +1,49 @@ -meshtasticd (2.7.9.0) unstable; urgency=medium - - * Version 2.7.9 - - -- GitHub Actions Wed, 03 Sep 2025 23:39:17 +0000 - -meshtasticd (2.7.8.0) unstable; urgency=medium - - * Version 2.7.8 - - -- GitHub Actions Sat, 30 Aug 2025 00:26:04 +0000 - -meshtasticd (2.7.7.0) unstable; urgency=medium - - * Version 2.7.7 - - -- GitHub Actions Thu, 28 Aug 2025 10:33:25 +0000 - -meshtasticd (2.7.6.0) unstable; urgency=medium - - * Version 2.7.6 - - -- GitHub Actions Tue, 12 Aug 2025 23:48:48 +0000 - -meshtasticd (2.7.5.0) unstable; urgency=medium - - * Version 2.7.5 - - -- GitHub Actions Sat, 09 Aug 2025 12:46:53 +0000 - -meshtasticd (2.7.4.0) unstable; urgency=medium - - * Version 2.7.4 - - -- GitHub Actions Sat, 19 Jul 2025 11:36:55 +0000 - -meshtasticd (2.7.3.0) unstable; urgency=medium - - * Version 2.7.3 - - -- GitHub Actions Thu, 10 Jul 2025 16:29:27 +0000 - -meshtasticd (2.7.2.0) unstable; urgency=medium - - * Version 2.7.2 - - -- GitHub Actions Fri, 04 Jul 2025 11:58:01 +0000 - -meshtasticd (2.7.1.0) unstable; urgency=medium - - * Version 2.7.1 - - -- GitHub Actions Fri, 27 Jun 2025 20:12:21 +0000 - -meshtasticd (2.6.13) unstable; urgency=medium - - * Version 2.6.13 - - -- GitHub Actions Mon, 16 Jun 2025 02:10:49 +0000 - -meshtasticd (2.6.11) unstable; urgency=medium - - * Version 2.6.11 - - -- GitHub Actions Mon, 02 Jun 2025 20:00:55 +0000 - -meshtasticd (2.6.10) unstable; urgency=medium - - * Version 2.6.10 - - -- GitHub Actions Sun, 25 May 2025 20:46:49 +0000 - -meshtasticd (2.6.9) unstable; urgency=medium - - * Version 2.6.9 - * Run as non-root user, 'meshtasticd' - - -- GitHub Actions Thu, 15 May 2025 11:13:30 +0000 - -meshtasticd (2.6.8) unstable; urgency=medium - - * Version 2.6.8 - - -- GitHub Actions Tue, 06 May 2025 01:32:49 +0000 - -meshtasticd (2.5.22) unstable; urgency=medium - - * Version 2.5.22 - - -- GitHub Actions Wed, 05 Feb 2025 01:10:33 +0000 - -meshtasticd (2.5.21) unstable; urgency=medium - - * Version 2.5.21 - - -- GitHub Actions Sat, 25 Jan 2025 01:39:16 +0000 - -meshtasticd (2.5.20) unstable; urgency=medium - - * Version 2.5.20 - - -- GitHub Actions Mon, 13 Jan 2025 19:24:14 +0000 - -meshtasticd (2.5.19) unstable; urgency=medium +meshtasticd (2.7.10.0) UNRELEASED; urgency=medium * Initial packaging * Version 2.5.19 - -- Austin Lane Thu, 02 Jan 2025 12:00:00 +0000 + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ Ubuntu ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + -- Thu, 18 Sep 2025 22:11:37 +0000 diff --git a/platformio.ini b/platformio.ini index 47b5f823d..941e33beb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -55,6 +55,7 @@ build_flags = -Wno-missing-field-initializers -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now #-D OLED_PL=1 + #-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs monitor_speed = 115200 monitor_filters = direct @@ -118,13 +119,13 @@ 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/3677476c8a823ee85056b5fb1d146a3e193f8276.zip + https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip ; Common libs for environmental measurements in telemetry module [environmental_base] lib_deps = # renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO - adafruit/Adafruit BusIO@1.17.2 + adafruit/Adafruit BusIO@1.17.3 # renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor adafruit/Adafruit Unified Sensor@1.1.15 # renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library diff --git a/protobufs b/protobufs index 8caf42396..46b81e822 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 8caf42396438f0d8a0305143485fd671c1fc7126 +Subproject commit 46b81e822af1b8e408f437092337f129dee693e6 diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index e4ef3b443..947b1e054 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -183,9 +183,9 @@ class AmbientLightingThread : public concurrency::OSThread #endif #endif pixels.show(); - LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d", - moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + // LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d", + // moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, + // moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); #endif #ifdef RGBLED_CA analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red); diff --git a/src/DisplayFormatters.h b/src/DisplayFormatters.h index ad193e966..981010b33 100644 --- a/src/DisplayFormatters.h +++ b/src/DisplayFormatters.h @@ -6,7 +6,5 @@ class DisplayFormatters public: static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName, bool usePreset); - - public: static const char *getDeviceRole(meshtastic_Config_DeviceConfig_Role role); }; diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index efab84399..9624a4593 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -4,6 +4,7 @@ #include "concurrency/OSThread.h" #include "configuration.h" #include "main.h" +#include "memGet.h" #include "mesh/generated/meshtastic/mesh.pb.h" #include #include @@ -166,6 +167,16 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, print(thread->ThreadName); print("] "); } + +#ifdef DEBUG_HEAP + // Add heap free space bytes prefix before every log message +#ifdef ARCH_PORTDUINO + ::printf("[heap %u] ", memGet.getFreeHeap()); +#else + printf("[heap %u] ", memGet.getFreeHeap()); +#endif +#endif // DEBUG_HEAP + r += vprintf(logLevel, format, arg); } diff --git a/src/configuration.h b/src/configuration.h index d5adba028..1b386ec17 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -262,6 +262,13 @@ along with this program. If not, see . #define VEXT_ON_VALUE LOW #endif +// ----------------------------------------------------------------------------- +// Rotary encoder +// ----------------------------------------------------------------------------- +#ifndef ROTARY_DELAY +#define ROTARY_DELAY 5 +#endif + // ----------------------------------------------------------------------------- // GPS // ----------------------------------------------------------------------------- @@ -431,7 +438,6 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_SERIAL 1 #define MESHTASTIC_EXCLUDE_POWERSTRESS 1 #define MESHTASTIC_EXCLUDE_ADMIN 1 -#define MESHTASTIC_EXCLUDE_AMBIENTLIGHTING 1 #endif // // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 5cb4fca32..01a630b52 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -294,6 +294,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) type = AHT10; break; #endif +#if !defined(M5STACK_UNITC6L) case INA_ADDR: case INA_ADDR_ALTERNATE: case INA_ADDR_WAVESHARE_UPS: @@ -340,6 +341,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) // else: probably a RAK12500/UBLOX GPS on I2C } break; +#endif case MCP9808_ADDR: // We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some // weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips. diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 14ed91a1e..689c550d3 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -355,6 +355,14 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#elif defined(USE_SPISSD1306) + dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48); + if (!dispdev->init()) { + LOG_DEBUG("Error: SSD1306 not detected!"); + } else { + static_cast(dispdev)->setHorizontalOffset(32); + LOG_INFO("SSD1306 init success"); + } #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) dispdev = new TFTDisplay(address.address, -1, -1, geometry, @@ -545,7 +553,7 @@ void Screen::setup() // === Apply loaded brightness === #if defined(ST7789_CS) static_cast(dispdev)->setDisplayBrightness(brightness); -#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) +#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306) dispdev->setBrightness(brightness); #endif LOG_INFO("Applied screen brightness: %d", brightness); @@ -592,7 +600,7 @@ void Screen::setup() static_cast(dispdev)->flipScreenVertically(); #elif defined(USE_ST7789) static_cast(dispdev)->flipScreenVertically(); -#else +#elif !defined(M5STACK_UNITC6L) dispdev->flipScreenVertically(); #endif } @@ -730,7 +738,11 @@ int32_t Screen::runOnce() #ifndef DISABLE_WELCOME_UNSET if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { +#if defined(M5STACK_UNITC6L) + menuHandler::LoraRegionPicker(); +#else menuHandler::OnboardMessage(); +#endif } #endif if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) { @@ -942,682 +954,708 @@ void Screen::setFrames(FrameFocus focus) #if defined(DISPLAY_CLOCK_FRAME) if (!hiddenFrames.clock) { - fsi.positions.clock = numframes; - normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame - : graphics::ClockRenderer::drawDigitalClockFrame; - indicatorIcons.push_back(digital_icon_clock); - } + if (!hiddenFrames.clock) { + fsi.positions.clock = numframes; +#if defined(M5STACK_UNITC6L) + normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame; +#else + normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame + : graphics::ClockRenderer::drawDigitalClockFrame; +#endif + indicatorIcons.push_back(digital_icon_clock); #endif - // Declare this early so it’s available in FOCUS_PRESERVE block - bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message); + // Declare this early so it’s available in FOCUS_PRESERVE block + bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message); - if (!hiddenFrames.home) { - fsi.positions.home = numframes; - normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused; - indicatorIcons.push_back(icon_home); - } + if (!hiddenFrames.home) { + fsi.positions.home = numframes; + normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused; + indicatorIcons.push_back(icon_home); + } - fsi.positions.textMessage = numframes; - normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame; - indicatorIcons.push_back(icon_mail); + fsi.positions.textMessage = numframes; + normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame; + indicatorIcons.push_back(icon_mail); #ifndef USE_EINK - if (!hiddenFrames.nodelist) { - fsi.positions.nodelist = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen; - indicatorIcons.push_back(icon_nodes); - } + if (!hiddenFrames.nodelist) { + fsi.positions.nodelist = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen; + indicatorIcons.push_back(icon_nodes); + } #endif // Show detailed node views only on E-Ink builds #ifdef USE_EINK - if (!hiddenFrames.nodelist_lastheard) { - fsi.positions.nodelist_lastheard = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen; - indicatorIcons.push_back(icon_nodes); - } - if (!hiddenFrames.nodelist_hopsignal) { - fsi.positions.nodelist_hopsignal = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen; - indicatorIcons.push_back(icon_signal); - } - if (!hiddenFrames.nodelist_distance) { - fsi.positions.nodelist_distance = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen; - indicatorIcons.push_back(icon_distance); - } + if (!hiddenFrames.nodelist_lastheard) { + fsi.positions.nodelist_lastheard = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen; + indicatorIcons.push_back(icon_nodes); + } + if (!hiddenFrames.nodelist_hopsignal) { + fsi.positions.nodelist_hopsignal = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen; + indicatorIcons.push_back(icon_signal); + } + if (!hiddenFrames.nodelist_distance) { + fsi.positions.nodelist_distance = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen; + indicatorIcons.push_back(icon_distance); + } #endif #if HAS_GPS - if (!hiddenFrames.nodelist_bearings) { - fsi.positions.nodelist_bearings = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses; - indicatorIcons.push_back(icon_list); - } - if (!hiddenFrames.gps) { - fsi.positions.gps = numframes; - normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen; - indicatorIcons.push_back(icon_compass); - } + if (!hiddenFrames.nodelist_bearings) { + fsi.positions.nodelist_bearings = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses; + indicatorIcons.push_back(icon_list); + } + if (!hiddenFrames.gps) { + fsi.positions.gps = numframes; + normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen; + indicatorIcons.push_back(icon_compass); + } #endif - if (RadioLibInterface::instance && !hiddenFrames.lora) { - fsi.positions.lora = numframes; - normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused; - indicatorIcons.push_back(icon_radio); - } - if (!hiddenFrames.system) { - fsi.positions.system = numframes; - normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen; - indicatorIcons.push_back(icon_system); - } + if (RadioLibInterface::instance && !hiddenFrames.lora) { + fsi.positions.lora = numframes; + normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused; + indicatorIcons.push_back(icon_radio); + } + if (!hiddenFrames.system) { + fsi.positions.system = numframes; + normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen; + indicatorIcons.push_back(icon_system); + } #if !defined(DISPLAY_CLOCK_FRAME) - if (!hiddenFrames.clock) { - fsi.positions.clock = numframes; - normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame - : graphics::ClockRenderer::drawDigitalClockFrame; - indicatorIcons.push_back(digital_icon_clock); - } + if (!hiddenFrames.clock) { + fsi.positions.clock = numframes; + normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame + : graphics::ClockRenderer::drawDigitalClockFrame; + indicatorIcons.push_back(digital_icon_clock); + } #endif - if (!hiddenFrames.chirpy) { - fsi.positions.chirpy = numframes; - normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy; - indicatorIcons.push_back(small_chirpy); - } + if (!hiddenFrames.chirpy) { + fsi.positions.chirpy = numframes; + normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy; + indicatorIcons.push_back(small_chirpy); + } #if HAS_WIFI && !defined(ARCH_PORTDUINO) - if (!hiddenFrames.wifi && isWifiAvailable()) { - fsi.positions.wifi = numframes; - normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline; - indicatorIcons.push_back(icon_wifi); - } + if (!hiddenFrames.wifi && isWifiAvailable()) { + fsi.positions.wifi = numframes; + normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline; + indicatorIcons.push_back(icon_wifi); + } #endif - // Beware of what changes you make in this code! - // We pass numframes into GetMeshModulesWithUIFrames() which is highly important! - // Inside of that callback, goes over to MeshModule.cpp and we run - // modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr - // entries until we're ready to start building the matching entries. - // We are doing our best to keep the normalFrames vector - // and the moduleFrames vector in lock step. - moduleFrames = MeshModule::GetMeshModulesWithUIFrames(numframes); - LOG_DEBUG("Show %d module frames", moduleFrames.size()); + // Beware of what changes you make in this code! + // We pass numframes into GetMeshModulesWithUIFrames() which is highly important! + // Inside of that callback, goes over to MeshModule.cpp and we run + // modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr + // entries until we're ready to start building the matching entries. + // We are doing our best to keep the normalFrames vector + // and the moduleFrames vector in lock step. + moduleFrames = MeshModule::GetMeshModulesWithUIFrames(numframes); + LOG_DEBUG("Show %d module frames", moduleFrames.size()); - for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) { - // Draw the module frame, using the hack described above - if (*i != nullptr) { - normalFrames[numframes] = drawModuleFrame; + for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) { + // Draw the module frame, using the hack described above + if (*i != nullptr) { + normalFrames[numframes] = drawModuleFrame; - // Check if the module being drawn has requested focus - // We will honor this request later, if setFrames was triggered by a UIFrameEvent - MeshModule *m = *i; - if (m && m->isRequestingFocus()) - fsi.positions.focusedModule = numframes; - if (m && m == waypointModule) - fsi.positions.waypoint = numframes; + // Check if the module being drawn has requested focus + // We will honor this request later, if setFrames was triggered by a UIFrameEvent + MeshModule *m = *i; + if (m && m->isRequestingFocus()) + fsi.positions.focusedModule = numframes; + if (m && m == waypointModule) + fsi.positions.waypoint = numframes; - indicatorIcons.push_back(icon_module); - numframes++; + indicatorIcons.push_back(icon_module); + numframes++; + } + } + + LOG_DEBUG("Added modules. numframes: %d", numframes); + + // We don't show the node info of our node (if we have it yet - we should) + size_t numMeshNodes = nodeDB->getNumMeshNodes(); + if (numMeshNodes > 0) + numMeshNodes--; + + if (!hiddenFrames.show_favorites) { + // Temporary array to hold favorite node frames + std::vector favoriteFrames; + + for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { + const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); + if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) { + favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo); + } + } + + // Insert favorite frames *after* collecting them all + if (!favoriteFrames.empty()) { + fsi.positions.firstFavorite = numframes; + for (const auto &f : favoriteFrames) { + normalFrames[numframes++] = f; + indicatorIcons.push_back(icon_node); + } + fsi.positions.lastFavorite = numframes - 1; + } else { + fsi.positions.firstFavorite = 255; + fsi.positions.lastFavorite = 255; + } + } + + fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE + this->frameCount = numframes; // ✅ Save frame count for use in custom overlay + LOG_DEBUG("Finished build frames. numframes: %d", numframes); + + ui->setFrames(normalFrames, numframes); + ui->disableAllIndicators(); + + // Add overlays: frame icons and alert banner) + static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, + NotificationRenderer::drawBannercallback}; + ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); + + prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed) + + // Focus on a specific frame, in the frame set we just created + switch (focus) { + case FOCUS_DEFAULT: + ui->switchToFrame(fsi.positions.deviceFocused); + break; + case FOCUS_FAULT: + ui->switchToFrame(fsi.positions.fault); + break; + case FOCUS_TEXTMESSAGE: + hasUnreadMessage = false; // ✅ Clear when message is *viewed* + ui->switchToFrame(fsi.positions.textMessage); + break; + case FOCUS_MODULE: + // Whichever frame was marked by MeshModule::requestFocus(), if any + // If no module requested focus, will show the first frame instead + ui->switchToFrame(fsi.positions.focusedModule); + break; + case FOCUS_CLOCK: + // Whichever frame was marked by MeshModule::requestFocus(), if any + // If no module requested focus, will show the first frame instead + ui->switchToFrame(fsi.positions.clock); + break; + case FOCUS_SYSTEM: + ui->switchToFrame(fsi.positions.system); + break; + + case FOCUS_PRESERVE: + // No more adjustment — force stay on same index + if (previousFrameCount > fsi.frameCount) { + ui->switchToFrame(originalPosition - 1); + } else if (previousFrameCount < fsi.frameCount) { + ui->switchToFrame(originalPosition + 1); + } else { + ui->switchToFrame(originalPosition); + } + break; + } + + // Store the info about this frameset, for future setFrames calls + this->framesetInfo = fsi; + + setFastFramerate(); // Draw ASAP } - } - LOG_DEBUG("Added modules. numframes: %d", numframes); + void Screen::setFrameImmediateDraw(FrameCallback * drawFrames) + { + ui->disableAllIndicators(); + ui->setFrames(drawFrames, 1); + setFastFramerate(); + } - // We don't show the node info of our node (if we have it yet - we should) - size_t numMeshNodes = nodeDB->getNumMeshNodes(); - if (numMeshNodes > 0) - numMeshNodes--; - - if (!hiddenFrames.show_favorites) { - // Temporary array to hold favorite node frames - std::vector favoriteFrames; - - for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { - const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); - if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) { - favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo); + void Screen::toggleFrameVisibility(const std::string &frameName) + { +#ifndef USE_EINK + if (frameName == "nodelist") { + hiddenFrames.nodelist = !hiddenFrames.nodelist; + } +#endif +#ifdef USE_EINK + if (frameName == "nodelist_lastheard") { + hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard; + } + if (frameName == "nodelist_hopsignal") { + hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal; + } + if (frameName == "nodelist_distance") { + hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance; + } +#endif +#if HAS_GPS + if (frameName == "nodelist_bearings") { + hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings; + } + if (frameName == "gps") { + hiddenFrames.gps = !hiddenFrames.gps; + } +#endif + if (frameName == "lora") { + hiddenFrames.lora = !hiddenFrames.lora; + } + if (frameName == "clock") { + hiddenFrames.clock = !hiddenFrames.clock; + } + if (frameName == "show_favorites") { + hiddenFrames.show_favorites = !hiddenFrames.show_favorites; + } + if (frameName == "chirpy") { + hiddenFrames.chirpy = !hiddenFrames.chirpy; } } - // Insert favorite frames *after* collecting them all - if (!favoriteFrames.empty()) { - fsi.positions.firstFavorite = numframes; - for (const auto &f : favoriteFrames) { - normalFrames[numframes++] = f; - indicatorIcons.push_back(icon_node); + bool Screen::isFrameHidden(const std::string &frameName) const + { +#ifndef USE_EINK + if (frameName == "nodelist") + return hiddenFrames.nodelist; +#endif +#ifdef USE_EINK + if (frameName == "nodelist_lastheard") + return hiddenFrames.nodelist_lastheard; + if (frameName == "nodelist_hopsignal") + return hiddenFrames.nodelist_hopsignal; + if (frameName == "nodelist_distance") + return hiddenFrames.nodelist_distance; +#endif +#if HAS_GPS + if (frameName == "nodelist_bearings") + return hiddenFrames.nodelist_bearings; + if (frameName == "gps") + return hiddenFrames.gps; +#endif + if (frameName == "lora") + return hiddenFrames.lora; + if (frameName == "clock") + return hiddenFrames.clock; + if (frameName == "show_favorites") + return hiddenFrames.show_favorites; + if (frameName == "chirpy") + return hiddenFrames.chirpy; + + return false; + } + + // Dismisses the currently displayed screen frame, if possible + // Relevant for text message, waypoint, others in future? + // Triggered with a CardKB keycombo + void Screen::hideCurrentFrame() + { + uint8_t currentFrame = ui->getUiState()->currentFrame; + bool dismissed = false; + if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) { + LOG_INFO("Hide Text Message"); + devicestate.has_rx_text_message = false; + memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); + } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) { + LOG_DEBUG("Hide Waypoint"); + devicestate.has_rx_waypoint = false; + hiddenFrames.waypoint = true; + dismissed = true; + } else if (currentFrame == framesetInfo.positions.wifi) { + LOG_DEBUG("Hide WiFi Screen"); + hiddenFrames.wifi = true; + dismissed = true; + } else if (currentFrame == framesetInfo.positions.lora) { + LOG_INFO("Hide LoRa"); + hiddenFrames.lora = true; + dismissed = true; + } + + if (dismissed) { + setFrames(FOCUS_DEFAULT); // You could also use FOCUS_PRESERVE } - fsi.positions.lastFavorite = numframes - 1; - } else { - fsi.positions.firstFavorite = 255; - fsi.positions.lastFavorite = 255; } - } - fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE - this->frameCount = numframes; // ✅ Save frame count for use in custom overlay - LOG_DEBUG("Finished build frames. numframes: %d", numframes); + void Screen::handleStartFirmwareUpdateScreen() + { + LOG_DEBUG("Show firmware screen"); + showingNormalScreen = false; + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - ui->setFrames(normalFrames, numframes); - ui->disableAllIndicators(); - - // Add overlays: frame icons and alert banner) - static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; - ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); - - prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed) - - // Focus on a specific frame, in the frame set we just created - switch (focus) { - case FOCUS_DEFAULT: - ui->switchToFrame(fsi.positions.deviceFocused); - break; - case FOCUS_FAULT: - ui->switchToFrame(fsi.positions.fault); - break; - case FOCUS_TEXTMESSAGE: - hasUnreadMessage = false; // ✅ Clear when message is *viewed* - ui->switchToFrame(fsi.positions.textMessage); - break; - case FOCUS_MODULE: - // Whichever frame was marked by MeshModule::requestFocus(), if any - // If no module requested focus, will show the first frame instead - ui->switchToFrame(fsi.positions.focusedModule); - break; - case FOCUS_CLOCK: - // Whichever frame was marked by MeshModule::requestFocus(), if any - // If no module requested focus, will show the first frame instead - ui->switchToFrame(fsi.positions.clock); - break; - case FOCUS_SYSTEM: - ui->switchToFrame(fsi.positions.system); - break; - - case FOCUS_PRESERVE: - // No more adjustment — force stay on same index - if (previousFrameCount > fsi.frameCount) { - ui->switchToFrame(originalPosition - 1); - } else if (previousFrameCount < fsi.frameCount) { - ui->switchToFrame(originalPosition + 1); - } else { - ui->switchToFrame(originalPosition); + static FrameCallback frames[] = {graphics::NotificationRenderer::drawFrameFirmware}; + setFrameImmediateDraw(frames); } - break; - } - // Store the info about this frameset, for future setFrames calls - this->framesetInfo = fsi; + void Screen::blink() + { + setFastFramerate(); + uint8_t count = 10; + dispdev->setBrightness(254); + while (count > 0) { + dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight()); + dispdev->display(); + delay(50); + dispdev->clear(); + dispdev->display(); + delay(50); + count = count - 1; + } + // The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in + // OLEDDisplay. + dispdev->setBrightness(brightness); + } - setFastFramerate(); // Draw ASAP -} - -void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) -{ - ui->disableAllIndicators(); - ui->setFrames(drawFrames, 1); - setFastFramerate(); -} - -void Screen::toggleFrameVisibility(const std::string &frameName) -{ -#ifndef USE_EINK - if (frameName == "nodelist") { - hiddenFrames.nodelist = !hiddenFrames.nodelist; - } -#endif -#ifdef USE_EINK - if (frameName == "nodelist_lastheard") { - hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard; - } - if (frameName == "nodelist_hopsignal") { - hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal; - } - if (frameName == "nodelist_distance") { - hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance; - } -#endif -#if HAS_GPS - if (frameName == "nodelist_bearings") { - hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings; - } - if (frameName == "gps") { - hiddenFrames.gps = !hiddenFrames.gps; - } -#endif - if (frameName == "lora") { - hiddenFrames.lora = !hiddenFrames.lora; - } - if (frameName == "clock") { - hiddenFrames.clock = !hiddenFrames.clock; - } - if (frameName == "show_favorites") { - hiddenFrames.show_favorites = !hiddenFrames.show_favorites; - } - if (frameName == "chirpy") { - hiddenFrames.chirpy = !hiddenFrames.chirpy; - } -} - -bool Screen::isFrameHidden(const std::string &frameName) const -{ -#ifndef USE_EINK - if (frameName == "nodelist") - return hiddenFrames.nodelist; -#endif -#ifdef USE_EINK - if (frameName == "nodelist_lastheard") - return hiddenFrames.nodelist_lastheard; - if (frameName == "nodelist_hopsignal") - return hiddenFrames.nodelist_hopsignal; - if (frameName == "nodelist_distance") - return hiddenFrames.nodelist_distance; -#endif -#if HAS_GPS - if (frameName == "nodelist_bearings") - return hiddenFrames.nodelist_bearings; - if (frameName == "gps") - return hiddenFrames.gps; -#endif - if (frameName == "lora") - return hiddenFrames.lora; - if (frameName == "clock") - return hiddenFrames.clock; - if (frameName == "show_favorites") - return hiddenFrames.show_favorites; - if (frameName == "chirpy") - return hiddenFrames.chirpy; - - return false; -} - -// Dismisses the currently displayed screen frame, if possible -// Relevant for text message, waypoint, others in future? -// Triggered with a CardKB keycombo -void Screen::hideCurrentFrame() -{ - uint8_t currentFrame = ui->getUiState()->currentFrame; - bool dismissed = false; - if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) { - LOG_INFO("Hide Text Message"); - devicestate.has_rx_text_message = false; - memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); - } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) { - LOG_DEBUG("Hide Waypoint"); - devicestate.has_rx_waypoint = false; - hiddenFrames.waypoint = true; - dismissed = true; - } else if (currentFrame == framesetInfo.positions.wifi) { - LOG_DEBUG("Hide WiFi Screen"); - hiddenFrames.wifi = true; - dismissed = true; - } else if (currentFrame == framesetInfo.positions.lora) { - LOG_INFO("Hide LoRa"); - hiddenFrames.lora = true; - dismissed = true; - } - - if (dismissed) { - setFrames(FOCUS_DEFAULT); // You could also use FOCUS_PRESERVE - } -} - -void Screen::handleStartFirmwareUpdateScreen() -{ - LOG_DEBUG("Show firmware screen"); - showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame - - static FrameCallback frames[] = {graphics::NotificationRenderer::drawFrameFirmware}; - setFrameImmediateDraw(frames); -} - -void Screen::blink() -{ - setFastFramerate(); - uint8_t count = 10; - dispdev->setBrightness(254); - while (count > 0) { - dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight()); - dispdev->display(); - delay(50); - dispdev->clear(); - dispdev->display(); - delay(50); - count = count - 1; - } - // The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay. - dispdev->setBrightness(brightness); -} - -void Screen::increaseBrightness() -{ - brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62); + void Screen::increaseBrightness() + { + brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62); #if defined(ST7789_CS) - // run the setDisplayBrightness function. This works on t-decks - static_cast(dispdev)->setDisplayBrightness(brightness); + // run the setDisplayBrightness function. This works on t-decks + static_cast(dispdev)->setDisplayBrightness(brightness); #endif - /* TO DO: add little popup in center of screen saying what brightness level it is set to*/ -} + /* TO DO: add little popup in center of screen saying what brightness level it is set to*/ + } -void Screen::decreaseBrightness() -{ - brightness = (brightness < 70) ? brightness : (brightness - 62); + void Screen::decreaseBrightness() + { + brightness = (brightness < 70) ? brightness : (brightness - 62); #if defined(ST7789_CS) - static_cast(dispdev)->setDisplayBrightness(brightness); + static_cast(dispdev)->setDisplayBrightness(brightness); #endif - /* TO DO: add little popup in center of screen saying what brightness level it is set to*/ -} - -void Screen::setFunctionSymbol(std::string sym) -{ - if (std::find(functionSymbol.begin(), functionSymbol.end(), sym) == functionSymbol.end()) { - functionSymbol.push_back(sym); - functionSymbolString = ""; - for (auto symbol : functionSymbol) { - functionSymbolString = symbol + " " + functionSymbolString; + /* TO DO: add little popup in center of screen saying what brightness level it is set to*/ } - setFastFramerate(); - } -} -void Screen::removeFunctionSymbol(std::string sym) -{ - functionSymbol.erase(std::remove(functionSymbol.begin(), functionSymbol.end(), sym), functionSymbol.end()); - functionSymbolString = ""; - for (auto symbol : functionSymbol) { - functionSymbolString = symbol + " " + functionSymbolString; - } - setFastFramerate(); -} + void Screen::setFunctionSymbol(std::string sym) + { + if (std::find(functionSymbol.begin(), functionSymbol.end(), sym) == functionSymbol.end()) { + functionSymbol.push_back(sym); + functionSymbolString = ""; + for (auto symbol : functionSymbol) { + functionSymbolString = symbol + " " + functionSymbolString; + } + setFastFramerate(); + } + } -void Screen::handleOnPress() -{ - // If screen was off, just wake it, otherwise advance to next frame - // If we are in a transition, the press must have bounced, drop it. - if (ui->getUiState()->frameState == FIXED) { - ui->nextFrame(); - lastScreenTransition = millis(); - setFastFramerate(); - } -} + void Screen::removeFunctionSymbol(std::string sym) + { + functionSymbol.erase(std::remove(functionSymbol.begin(), functionSymbol.end(), sym), functionSymbol.end()); + functionSymbolString = ""; + for (auto symbol : functionSymbol) { + functionSymbolString = symbol + " " + functionSymbolString; + } + setFastFramerate(); + } -void Screen::handleShowPrevFrame() -{ - // If screen was off, just wake it, otherwise go back to previous frame - // If we are in a transition, the press must have bounced, drop it. - if (ui->getUiState()->frameState == FIXED) { - ui->previousFrame(); - lastScreenTransition = millis(); - setFastFramerate(); - } -} + void Screen::handleOnPress() + { + // If screen was off, just wake it, otherwise advance to next frame + // If we are in a transition, the press must have bounced, drop it. + if (ui->getUiState()->frameState == FIXED) { + ui->nextFrame(); + lastScreenTransition = millis(); + setFastFramerate(); + } + } -void Screen::handleShowNextFrame() -{ - // If screen was off, just wake it, otherwise advance to next frame - // If we are in a transition, the press must have bounced, drop it. - if (ui->getUiState()->frameState == FIXED) { - ui->nextFrame(); - lastScreenTransition = millis(); - setFastFramerate(); - } -} + void Screen::handleShowPrevFrame() + { + // If screen was off, just wake it, otherwise go back to previous frame + // If we are in a transition, the press must have bounced, drop it. + if (ui->getUiState()->frameState == FIXED) { + ui->previousFrame(); + lastScreenTransition = millis(); + setFastFramerate(); + } + } + + void Screen::handleShowNextFrame() + { + // If screen was off, just wake it, otherwise advance to next frame + // If we are in a transition, the press must have bounced, drop it. + if (ui->getUiState()->frameState == FIXED) { + ui->nextFrame(); + lastScreenTransition = millis(); + setFastFramerate(); + } + } #ifndef SCREEN_TRANSITION_FRAMERATE #define SCREEN_TRANSITION_FRAMERATE 30 // fps #endif -void Screen::setFastFramerate() -{ - // We are about to start a transition so speed up fps - targetFramerate = SCREEN_TRANSITION_FRAMERATE; + void Screen::setFastFramerate() + { +#if defined(M5STACK_UNITC6L) + dispdev->clear(); + dispdev->display(); +#endif + // We are about to start a transition so speed up fps + targetFramerate = SCREEN_TRANSITION_FRAMERATE; - ui->setTargetFPS(targetFramerate); - setInterval(0); // redraw ASAP - runASAP = true; -} - -int Screen::handleStatusUpdate(const meshtastic::Status *arg) -{ - // LOG_DEBUG("Screen got status update %d", arg->getStatusType()); - switch (arg->getStatusType()) { - case STATUS_TYPE_NODE: - if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { - setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible) + ui->setTargetFPS(targetFramerate); + setInterval(0); // redraw ASAP + runASAP = true; } - nodeDB->updateGUI = false; - break; - } - return 0; -} - -// Handles when message is received; will jump to text message frame. -int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) -{ - if (showingNormalScreen) { - if (packet->from == 0) { - // Outgoing message (likely sent from phone) - devicestate.has_rx_text_message = false; - memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); - hiddenFrames.textMessage = true; - hasUnreadMessage = false; // Clear unread state when user replies - - setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list - } else { - // Incoming message - devicestate.has_rx_text_message = true; // Needed to include the message frame - hasUnreadMessage = true; // Enables mail icon in the header - setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view - - // Only wake/force display if the configuration allows it - if (shouldWakeOnReceivedMessage()) { - setOn(true); // Wake up the screen first - forceDisplay(); // Forces screen redraw - } - // === Prepare banner content === - const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); - const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; - - const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes); - - char banner[256]; - - // Check for bell character in message to determine alert type - bool isAlert = false; - for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) { - if (msgRaw[i] == '\x07') { - isAlert = true; - break; + int Screen::handleStatusUpdate(const meshtastic::Status *arg) + { + // LOG_DEBUG("Screen got status update %d", arg->getStatusType()); + switch (arg->getStatusType()) { + case STATUS_TYPE_NODE: + if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { + setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible) } + nodeDB->updateGUI = false; + break; } - if (isAlert) { - if (longName && longName[0]) { - snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); + return 0; + } + + // Handles when message is received; will jump to text message frame. + int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) + { + if (showingNormalScreen) { + if (packet->from == 0) { + // Outgoing message (likely sent from phone) + devicestate.has_rx_text_message = false; + memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); + hiddenFrames.textMessage = true; + hasUnreadMessage = false; // Clear unread state when user replies + + setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list } else { - strcpy(banner, "Alert Received"); - } - } else { - if (longName && longName[0]) { + // Incoming message + devicestate.has_rx_text_message = true; // Needed to include the message frame + hasUnreadMessage = true; // Enables mail icon in the header + setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view + + // Only wake/force display if the configuration allows it + if (shouldWakeOnReceivedMessage()) { + setOn(true); // Wake up the screen first + forceDisplay(); // Forces screen redraw + } + // === Prepare banner content === + const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); + const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; + + const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes); + + char banner[256]; + + // Check for bell character in message to determine alert type + bool isAlert = false; + for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) { + if (msgRaw[i] == '\x07') { + isAlert = true; + break; + } + } + + if (isAlert) { + if (longName && longName[0]) { + snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); + } else { + strcpy(banner, "Alert Received"); + } + } else { + if (longName && longName[0]) { +#if defined(M5STACK_UNITC6L) + strcpy(banner, "New Message"); +#else snprintf(banner, sizeof(banner), "New Message from\n%s", longName); - } else { - strcpy(banner, "New Message"); +#endif + + } else { + strcpy(banner, "New Message"); + } + } +#if defined(M5STACK_UNITC6L) + screen->setOn(true); + screen->showSimpleBanner(banner, 1500); + playLongBeep(); +#else + screen->showSimpleBanner(banner, 3000); +#endif } } - screen->showSimpleBanner(banner, 3000); + return 0; } - } - return 0; -} + // Triggered by MeshModules + int Screen::handleUIFrameEvent(const UIFrameEvent *event) + { + // Block UI frame events when virtual keyboard is active + if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) { + return 0; + } -// Triggered by MeshModules -int Screen::handleUIFrameEvent(const UIFrameEvent *event) -{ - // Block UI frame events when virtual keyboard is active - if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) { - return 0; - } + if (showingNormalScreen) { + // Regenerate the frameset, potentially honoring a module's internal requestFocus() call + if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET) + setFrames(FOCUS_MODULE); - if (showingNormalScreen) { - // Regenerate the frameset, potentially honoring a module's internal requestFocus() call - if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET) - setFrames(FOCUS_MODULE); + // Regenerate the frameset, while Attempt to maintain focus on the current frame + else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND) + setFrames(FOCUS_PRESERVE); - // Regenerate the frameset, while Attempt to maintain focus on the current frame - else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND) - setFrames(FOCUS_PRESERVE); + // Don't regenerate the frameset, just re-draw whatever is on screen ASAP + else if (event->action == UIFrameEvent::Action::REDRAW_ONLY) + setFastFramerate(); + } - // Don't regenerate the frameset, just re-draw whatever is on screen ASAP - else if (event->action == UIFrameEvent::Action::REDRAW_ONLY) - setFastFramerate(); - } + return 0; + } - return 0; -} + int Screen::handleInputEvent(const InputEvent *event) + { + if (!screenOn) + return 0; -int Screen::handleInputEvent(const InputEvent *event) -{ - if (!screenOn) - return 0; - - // Handle text input notifications specially - pass input to virtual keyboard - if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) { - NotificationRenderer::inEvent = *event; - static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; - ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); - setFastFramerate(); // Draw ASAP - ui->update(); - return 0; - } + // Handle text input notifications specially - pass input to virtual keyboard + if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) { + NotificationRenderer::inEvent = *event; + static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, + NotificationRenderer::drawBannercallback}; + ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); + setFastFramerate(); // Draw ASAP + ui->update(); + return 0; + } #ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw. - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please - EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update - handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) - setFastFramerate(); // Draw ASAP + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please + EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update + handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) + setFastFramerate(); // Draw ASAP #endif - if (NotificationRenderer::isOverlayBannerShowing()) { - NotificationRenderer::inEvent = *event; - static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; - ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); - setFastFramerate(); // Draw ASAP - ui->update(); + if (NotificationRenderer::isOverlayBannerShowing()) { + NotificationRenderer::inEvent = *event; + static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, + NotificationRenderer::drawBannercallback}; + ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); + setFastFramerate(); // Draw ASAP + ui->update(); - menuHandler::handleMenuSwitch(dispdev); - return 0; - } - - // Use left or right input from a keyboard to move between frames, - // so long as a mesh module isn't using these events for some other purpose - if (showingNormalScreen) { - - // Ask any MeshModules if they're handling keyboard input right now - bool inputIntercepted = false; - for (MeshModule *module : moduleFrames) { - if (module && module->interceptingKeyboardInput()) - inputIntercepted = true; - } - - // If no modules are using the input, move between frames - if (!inputIntercepted) { - if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS) { - showPrevFrame(); - } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) { - showNextFrame(); - } else if (event->inputEvent == INPUT_BROKER_SELECT) { - if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) { - menuHandler::homeBaseMenu(); - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) { - menuHandler::systemBaseMenu(); -#if HAS_GPS - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) { - menuHandler::positionBaseMenu(); -#endif - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) { - menuHandler::clockMenu(); - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) { - menuHandler::loraMenu(); - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) { - if (devicestate.rx_text_message.from) { - menuHandler::messageResponseMenu(); - } else { - menuHandler::textMessageBaseMenu(); - } - } else if (framesetInfo.positions.firstFavorite != 255 && - this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite && - this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) { - menuHandler::favoriteBaseMenu(); - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist || - this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard || - this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal || - this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance || - this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal || - this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) { - menuHandler::nodeListMenu(); - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.wifi) { - menuHandler::wifiBaseMenu(); - } - } else if (event->inputEvent == INPUT_BROKER_BACK) { - showPrevFrame(); - } else if (event->inputEvent == INPUT_BROKER_CANCEL) { - setOn(false); + menuHandler::handleMenuSwitch(dispdev); + return 0; } + + // Use left or right input from a keyboard to move between frames, + // so long as a mesh module isn't using these events for some other purpose + if (showingNormalScreen) { + + // Ask any MeshModules if they're handling keyboard input right now + bool inputIntercepted = false; + for (MeshModule *module : moduleFrames) { + if (module && module->interceptingKeyboardInput()) + inputIntercepted = true; + } + + // If no modules are using the input, move between frames + if (!inputIntercepted) { + if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS) { + showPrevFrame(); + } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) { + showNextFrame(); + } else if (event->inputEvent == INPUT_BROKER_SELECT) { + if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) { + menuHandler::homeBaseMenu(); + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) { + menuHandler::systemBaseMenu(); +#if HAS_GPS + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) { + menuHandler::positionBaseMenu(); +#endif + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) { + menuHandler::clockMenu(); + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) { + menuHandler::loraMenu(); + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) { + if (devicestate.rx_text_message.from) { + menuHandler::messageResponseMenu(); + } else { +#if defined(M5STACK_UNITC6L) + menuHandler::textMessageMenu(); +#else + menuHandler::textMessageBaseMenu(); +#endif + } + } else if (framesetInfo.positions.firstFavorite != 255 && + this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite && + this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) { + menuHandler::favoriteBaseMenu(); + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist || + this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard || + this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal || + this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance || + this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal || + this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) { + menuHandler::nodeListMenu(); + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.wifi) { + menuHandler::wifiBaseMenu(); + } + } else if (event->inputEvent == INPUT_BROKER_BACK) { + showPrevFrame(); + } else if (event->inputEvent == INPUT_BROKER_CANCEL) { + setOn(false); + } + } + } + + return 0; } - } - return 0; -} + int Screen::handleAdminMessage(AdminModule_ObserverData * arg) + { + switch (arg->request->which_payload_variant) { + // Node removed manually (i.e. via app) + case meshtastic_AdminMessage_remove_by_nodenum_tag: + setFrames(FOCUS_PRESERVE); + *arg->result = AdminMessageHandleResult::HANDLED; + break; -int Screen::handleAdminMessage(AdminModule_ObserverData *arg) -{ - switch (arg->request->which_payload_variant) { - // Node removed manually (i.e. via app) - case meshtastic_AdminMessage_remove_by_nodenum_tag: - setFrames(FOCUS_PRESERVE); - *arg->result = AdminMessageHandleResult::HANDLED; - break; + // Default no-op, in case the admin message observable gets used by other classes in future + default: + break; + } + return 0; + } - // Default no-op, in case the admin message observable gets used by other classes in future - default: - break; - } - return 0; -} + bool Screen::isOverlayBannerShowing() + { + return NotificationRenderer::isOverlayBannerShowing(); + } -bool Screen::isOverlayBannerShowing() -{ - return NotificationRenderer::isOverlayBannerShowing(); -} - -} // namespace graphics + } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} #endif // HAS_SCREEN -bool shouldWakeOnReceivedMessage() -{ - /* - The goal here is to determine when we do NOT wake up the screen on message received: - - Any ext. notifications are turned on - - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE - - If the battery level is very low - */ - if (moduleConfig.external_notification.enabled) { - return false; + bool shouldWakeOnReceivedMessage() + { + /* + The goal here is to determine when we do NOT wake up the screen on message received: + - Any ext. notifications are turned on + - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE + - If the battery level is very low + */ + if (moduleConfig.external_notification.enabled) { + return false; + } + if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT, + meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN, + meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) { + return false; + } + if (powerStatus && powerStatus->getBatteryChargePercent() < 10) { + return false; + } + return true; } - if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT, - meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN, - meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) { - return false; - } - if (powerStatus && powerStatus->getBatteryChargePercent() < 10) { - return false; - } - return true; -} diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 55ce20052..0dd9e3784 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -81,6 +81,8 @@ class Screen #include #elif defined(USE_ST7789) #include +#elif defined(USE_SPISSD1306) +#include #else // the SH1106/SSD1306 variant is auto-detected #include diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index a25417b05..c497a27b2 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -79,6 +79,10 @@ #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 #define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28 #define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 +#elif defined(M5STACK_UNITC6L) +#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13 +#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13 +#define FONT_LARGE FONT_SMALL_LOCAL // Height: 13 #else #define FONT_SMALL FONT_SMALL_LOCAL // Height: 13 #define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19 diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 3cf84f415..3937bcf50 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -129,7 +129,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti int batteryX = 1; int batteryY = HEADER_OFFSET_Y + 1; - +#if !defined(M5STACK_UNITC6L) // === Battery Icons === if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging batteryX += 1; @@ -368,7 +368,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti } } } - +#endif display->setColor(WHITE); // Reset for other UI } diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp index bb8cdd561..751db8d88 100644 --- a/src/graphics/draw/ClockRenderer.cpp +++ b/src/graphics/draw/ClockRenderer.cpp @@ -2,11 +2,13 @@ #if HAS_SCREEN #include "ClockRenderer.h" #include "NodeDB.h" +#include "UIRenderer.h" #include "configuration.h" #include "gps/GeoCoord.h" #include "gps/RTC.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" +#include "graphics/draw/UIRenderer.h" #include "graphics/emotes.h" #include "graphics/images.h" #include "main.h" @@ -190,6 +192,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 const char *titleStr = ""; // === Header === graphics::drawCommonHeader(display, x, y, titleStr, true, true); + int line = 0; #ifdef T_WATCH_S3 if (nimbleBluetooth && nimbleBluetooth->isConnected()) { @@ -314,6 +317,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 const char *titleStr = ""; // === Header === graphics::drawCommonHeader(display, x, y, titleStr, true, true); + int line = 0; #ifdef T_WATCH_S3 if (nimbleBluetooth && nimbleBluetooth->isConnected()) { diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index c93ef578c..60abd661e 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -277,12 +277,13 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds); // Line 1 (Still) +#if !defined(M5STACK_UNITC6L) display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str()); if (config.display.heading_bold) display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str()); display->setColor(WHITE); - +#endif // Setup string to assemble analogClock string std::string analogClock = ""; @@ -329,8 +330,7 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t #if HAS_GPS if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { // Line 3 - if (config.display.gps_format != - meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude + if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude UIRenderer::drawGpsAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); // Line 4 @@ -386,7 +386,11 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, char shortnameble[35]; getMacAddr(dmac); snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]); +#if defined(M5STACK_UNITC6L) + snprintf(shortnameble, sizeof(shortnameble), "%s", screen->ourId); +#else snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId); +#endif int textWidth = display->getStringWidth(shortnameble); int nameX = (SCREEN_WIDTH - textWidth); display->drawString(nameX, getTextPositions(display)[line++], shortnameble); @@ -405,7 +409,11 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, char regionradiopreset[25]; const char *region = myRegion ? myRegion->name : NULL; if (region != nullptr) { - snprintf(regionradiopreset, sizeof(regionradiopreset), "Reg: %s/%s", region, mode); +#if defined(M5STACK_UNITC6L) + snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region); +#else + snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode); +#endif } textWidth = display->getStringWidth(regionradiopreset); nameX = (SCREEN_WIDTH - textWidth) / 2; @@ -417,9 +425,17 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, float freq = RadioLibInterface::instance->getFreq(); snprintf(freqStr, sizeof(freqStr), "%.3f", freq); if (config.lora.channel_num == 0) { +#if defined(M5STACK_UNITC6L) + snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr); +#else snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr); +#endif } else { +#if defined(M5STACK_UNITC6L) + snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num); +#else snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num); +#endif } size_t len = strlen(frequencyslot); if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) { @@ -429,6 +445,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], frequencyslot); +#if !defined(M5STACK_UNITC6L) // === Fifth Row: Channel Utilization === const char *chUtil = "ChUtil:"; char chUtilPercentage[10]; @@ -485,6 +502,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++], chUtilPercentage); +#endif } // **************************** @@ -510,8 +528,11 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x #ifdef USE_EINK barsOffset -= 12; #endif +#if defined(M5STACK_UNITC6L) + const int barX = x + 45 + barsOffset; +#else const int barX = x + 40 + barsOffset; - +#endif auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) { if (total == 0) return; @@ -536,7 +557,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x // Label display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawString(labelX, getTextPositions(display)[line], label); - +#if !defined(M5STACK_UNITC6L) // Bar int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2; display->setColor(WHITE); @@ -544,7 +565,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x display->fillRect(barX, barY, fillWidth, barHeight); display->setColor(WHITE); - +#endif // Value string display->setTextAlignment(TEXT_ALIGN_RIGHT); display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr); @@ -597,10 +618,16 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x line += 1; } line += 1; + char appversionstr[35]; snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION)); char appversionstr_formatted[40]; char *lastDot = strrchr(appversionstr, '.'); +#if defined(M5STACK_UNITC6L) + if (lastDot != nullptr) { + *lastDot = '\0'; // truncate string + } +#else if (lastDot) { size_t prefixLen = lastDot - appversionstr; strncpy(appversionstr_formatted, appversionstr, prefixLen); @@ -611,10 +638,12 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1); appversionstr[sizeof(appversionstr) - 1] = '\0'; } +#endif int textWidth = display->getStringWidth(appversionstr); int nameX = (SCREEN_WIDTH - textWidth) / 2; - display->drawString(nameX, getTextPositions(display)[line], appversionstr); + display->drawString(nameX, getTextPositions(display)[line], appversionstr); +#if !defined(M5STACK_UNITC6L) if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it line += 1; char uptimeStr[32] = ""; @@ -633,6 +662,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line], uptimeStr); } +#endif } // **************************** @@ -661,6 +691,7 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1 textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("World!") / 2); display->drawString(textX, getTextPositions(display)[line++], "World!"); } + } // namespace DebugRenderer } // namespace graphics #endif \ No newline at end of file diff --git a/src/graphics/draw/DebugRenderer.h b/src/graphics/draw/DebugRenderer.h index 563a6c1ce..65fa74ca6 100644 --- a/src/graphics/draw/DebugRenderer.h +++ b/src/graphics/draw/DebugRenderer.h @@ -33,7 +33,6 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, // System screen display void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); - // Chirpy screen display void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); } // namespace DebugRenderer diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 73381a92b..9be0e0b02 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -102,7 +102,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration) "NP_865", "BR_902"}; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "LoRa Region"; +#else bannerOptions.message = "Set the LoRa region"; +#endif bannerOptions.durationMs = duration; bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 27; @@ -317,7 +321,11 @@ void menuHandler::TZPicker() void menuHandler::clockMenu() { +#if defined(M5STACK_UNITC6L) + static const char *optionsArray[] = {"Back", "Time Format", "Timezone"}; +#else static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"}; +#endif enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 }; BannerOverlayOptions bannerOptions; bannerOptions.message = "Clock Action"; @@ -341,8 +349,11 @@ void menuHandler::clockMenu() void menuHandler::messageResponseMenu() { enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 }; - +#if defined(M5STACK_UNITC6L) + static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply Preset"}; +#else static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"}; +#endif static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset}; int options = 3; @@ -356,7 +367,11 @@ void menuHandler::messageResponseMenu() optionsEnumArray[options++] = Aloud; #endif BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Message"; +#else bannerOptions.message = "Message Action"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsCount = options; @@ -409,7 +424,11 @@ void menuHandler::homeBaseMenu() optionsArray[options] = "Send Node Info"; } optionsEnumArray[options++] = Position; +#if defined(M5STACK_UNITC6L) + optionsArray[options] = "New Preset"; +#else optionsArray[options] = "New Preset Msg"; +#endif optionsEnumArray[options++] = Preset; if (kb_found) { optionsArray[options] = "New Freetext Msg"; @@ -417,7 +436,11 @@ void menuHandler::homeBaseMenu() } BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Home"; +#else bannerOptions.message = "Home Action"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsCount = options; @@ -456,6 +479,11 @@ void menuHandler::homeBaseMenu() screen->showOverlayBanner(bannerOptions); } +void menuHandler::textMessageMenu() +{ + cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST); +} + void menuHandler::textMessageBaseMenu() { enum optionsNumbers { Back, Preset, Freetext, enumEnd }; @@ -502,11 +530,17 @@ void menuHandler::systemBaseMenu() optionsArray[options] = "Frame Visiblity Toggle"; optionsEnumArray[options++] = FrameToggles; - +#if defined(M5STACK_UNITC6L) + optionsArray[options] = "Bluetooth"; +#else optionsArray[options] = "Bluetooth Toggle"; +#endif optionsEnumArray[options++] = Bluetooth; - +#if defined(M5STACK_UNITC6L) + optionsArray[options] = "Power"; +#else optionsArray[options] = "Reboot/Shutdown"; +#endif optionsEnumArray[options++] = PowerMenu; if (test_enabled) { @@ -515,7 +549,11 @@ void menuHandler::systemBaseMenu() } BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "System"; +#else bannerOptions.message = "System Action"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = options; bannerOptions.optionsEnumPtr = optionsEnumArray; @@ -551,7 +589,11 @@ void menuHandler::systemBaseMenu() void menuHandler::favoriteBaseMenu() { enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd }; +#if defined(M5STACK_UNITC6L) + static const char *optionsArray[enumEnd] = {"Back", "New Preset"}; +#else static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"}; +#endif static int optionsEnumArray[enumEnd] = {Back, Preset}; int options = 2; @@ -559,13 +601,19 @@ void menuHandler::favoriteBaseMenu() optionsArray[options] = "New Freetext Msg"; optionsEnumArray[options++] = Freetext; } +#if !defined(M5STACK_UNITC6L) optionsArray[options] = "Trace Route"; optionsEnumArray[options++] = TraceRoute; +#endif optionsArray[options] = "Remove Favorite"; optionsEnumArray[options++] = Remove; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Favorites"; +#else bannerOptions.message = "Favorites Action"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsCount = options; @@ -624,11 +672,19 @@ void menuHandler::positionBaseMenu() void menuHandler::nodeListMenu() { enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd }; +#if defined(M5STACK_UNITC6L) + static const char *optionsArray[] = {"Back", "Add Favorite", "Reset Node"}; +#else static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"}; +#endif BannerOverlayOptions bannerOptions; bannerOptions.message = "Node Action"; bannerOptions.optionsArrayPtr = optionsArray; +#if defined(M5STACK_UNITC6L) + bannerOptions.optionsCount = 3; +#else bannerOptions.optionsCount = 5; +#endif bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Favorite) { menuQueue = add_favorite; @@ -738,36 +794,40 @@ void menuHandler::GPSFormatMenu() isHighResolution ? "Universal Transverse Mercator" : "UTM", isHighResolution ? "Military Grid Reference System" : "MGRS", isHighResolution ? "Open Location Code" : "OLC", - isHighResolution ? "Ordnance Survey Grid Ref" : "OSGR"}; + isHighResolution ? "Ordnance Survey Grid Ref" : "OSGR", + isHighResolution ? "Maidenhead Locator" : "MLS"}; BannerOverlayOptions bannerOptions; bannerOptions.message = "GPS Format"; bannerOptions.optionsArrayPtr = optionsArray; - bannerOptions.optionsCount = 7; + bannerOptions.optionsCount = 8; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == 1) { - config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC; + uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC; service->reloadConfig(SEGMENT_CONFIG); } else if (selected == 2) { - config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS; + uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS; service->reloadConfig(SEGMENT_CONFIG); } else if (selected == 3) { - config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM; + uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM; service->reloadConfig(SEGMENT_CONFIG); } else if (selected == 4) { - config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS; + uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS; service->reloadConfig(SEGMENT_CONFIG); } else if (selected == 5) { - config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC; + uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC; service->reloadConfig(SEGMENT_CONFIG); } else if (selected == 6) { - config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR; + uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR; + service->reloadConfig(SEGMENT_CONFIG); + } else if (selected == 7) { + uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS; service->reloadConfig(SEGMENT_CONFIG); } else { menuQueue = position_base_menu; screen->runNow(); } }; - bannerOptions.InitialSelected = config.display.gps_format + 1; + bannerOptions.InitialSelected = uiconfig.gps_format + 1; screen->showOverlayBanner(bannerOptions); } #endif @@ -776,7 +836,11 @@ void menuHandler::BluetoothToggleMenu() { static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Bluetooth"; +#else bannerOptions.message = "Toggle Bluetooth"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 3; bannerOptions.bannerCallback = [](int selected) -> void { @@ -968,7 +1032,11 @@ void menuHandler::rebootMenu() { static const char *optionsArray[] = {"Back", "Confirm"}; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Reboot"; +#else bannerOptions.message = "Reboot Device?"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 2; bannerOptions.bannerCallback = [](int selected) -> void { @@ -988,7 +1056,11 @@ void menuHandler::shutdownMenu() { static const char *optionsArray[] = {"Back", "Confirm"}; BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Shutdown"; +#else bannerOptions.message = "Shutdown Device?"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 2; bannerOptions.bannerCallback = [](int selected) -> void { @@ -1005,7 +1077,12 @@ void menuHandler::shutdownMenu() void menuHandler::addFavoriteMenu() { +#if defined(M5STACK_UNITC6L) + screen->showNodePicker("Node Favorite", 30000, [](uint32_t nodenum) -> void { +#else screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void { + +#endif LOG_WARN("Nodenum: %u", nodenum); nodeDB->set_favorite(true, nodenum); screen->setFrames(graphics::Screen::FOCUS_PRESERVE); @@ -1218,7 +1295,11 @@ void menuHandler::powerMenu() #endif BannerOverlayOptions bannerOptions; +#if defined(M5STACK_UNITC6L) + bannerOptions.message = "Power"; +#else bannerOptions.message = "Reboot / Shutdown"; +#endif bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = options; bannerOptions.optionsEnumPtr = optionsEnumArray; diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index e8a2904a0..56477258f 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -84,6 +84,7 @@ class menuHandler static void screenOptionsMenu(); static void powerMenu(); static void FrameToggles_menu(); + static void textMessageMenu(); private: static void saveUIConfig(); diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 117829167..6971826de 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -181,12 +181,19 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 display->clear(); display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); - +#if defined(M5STACK_UNITC6L) + const int fixedTopHeight = 24; + const int windowX = 0; + const int windowY = fixedTopHeight; + const int windowWidth = 64; + const int windowHeight = SCREEN_HEIGHT - fixedTopHeight; +#else const int navHeight = FONT_HEIGHT_SMALL; const int scrollBottom = SCREEN_HEIGHT - navHeight; const int usableHeight = scrollBottom; const int textWidth = SCREEN_WIDTH; +#endif bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); bool isBold = config.display.heading_bold; @@ -201,7 +208,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 graphics::drawCommonHeader(display, x, y, titleStr); const char *messageString = "No messages"; int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2); +#if defined(M5STACK_UNITC6L) + display->drawString(center_text, windowY + (windowHeight / 2) - (FONT_HEIGHT_SMALL / 2) - 5, messageString); +#else display->drawString(center_text, getTextPositions(display)[2], messageString); +#endif return; } @@ -209,6 +220,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); char headerStr[80]; const char *sender = "???"; +#if defined(M5STACK_UNITC6L) + if (node && node->has_user) + sender = node->user.short_name; +#else if (node && node->has_user) { if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) { sender = node->user.long_name; @@ -216,6 +231,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 sender = node->user.short_name; } } +#endif uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24; uint8_t timestampHours, timestampMinutes; int32_t daysAgo; @@ -235,10 +251,61 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 sender); } } else { +#if defined(M5STACK_UNITC6L) + snprintf(headerStr, sizeof(headerStr), "%s from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(), + sender); +#else snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(), sender); +#endif } +#if defined(M5STACK_UNITC6L) + graphics::drawCommonHeader(display, x, y, titleStr); + int headerY = getTextPositions(display)[1]; + display->drawString(x, headerY, headerStr); + for (int separatorX = 0; separatorX < SCREEN_WIDTH; separatorX += 2) { + display->setPixel(separatorX, fixedTopHeight - 1); + } + cachedLines.clear(); + std::string fullMsg(messageBuf); + std::string currentLine; + for (size_t i = 0; i < fullMsg.size();) { + unsigned char c = fullMsg[i]; + size_t charLen = 1; + if ((c & 0xE0) == 0xC0) + charLen = 2; + else if ((c & 0xF0) == 0xE0) + charLen = 3; + else if ((c & 0xF8) == 0xF0) + charLen = 4; + std::string nextChar = fullMsg.substr(i, charLen); + std::string testLine = currentLine + nextChar; + if (display->getStringWidth(testLine.c_str()) > windowWidth) { + cachedLines.push_back(currentLine); + currentLine = nextChar; + } else { + currentLine = testLine; + } + i += charLen; + } + if (!currentLine.empty()) + cachedLines.push_back(currentLine); + cachedHeights = calculateLineHeights(cachedLines, emotes); + int yOffset = windowY; + int linesDrawn = 0; + for (size_t i = 0; i < cachedLines.size(); ++i) { + if (linesDrawn >= 2) + break; + int lineHeight = cachedHeights[i]; + if (yOffset + lineHeight > windowY + windowHeight) + break; + drawStringWithEmotes(display, windowX, yOffset, cachedLines[i], emotes, numEmotes); + yOffset += lineHeight; + linesDrawn++; + } + screen->forceDisplay(); +#else uint32_t now = millis(); #ifndef EXCLUDE_EMOJI // === Bounce animation setup === @@ -355,6 +422,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Draw header at the end to sort out overlapping elements graphics::drawCommonHeader(display, x, y, titleStr); +#endif } std::vector generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth) diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index d8746fb69..7d6a38dd3 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -21,6 +21,10 @@ extern bool haveGlyphs(const char *str); // Global screen instance extern graphics::Screen *screen; +#if defined(M5STACK_UNITC6L) +static uint32_t lastSwitchTime = 0; +#else +#endif namespace graphics { namespace NodeListRenderer @@ -393,9 +397,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t { const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1; const int rowYOffset = FONT_HEIGHT_SMALL - 3; - +#if defined(M5STACK_UNITC6L) + int columnWidth = display->getWidth(); +#else int columnWidth = display->getWidth() / 2; - +#endif display->clear(); // Draw the battery/time header @@ -408,8 +414,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t int totalRowsAvailable = (display->getHeight() - y) / rowYOffset; int visibleNodeRows = totalRowsAvailable; +#if defined(M5STACK_UNITC6L) + int totalColumns = 1; +#else int totalColumns = 2; - +#endif int startIndex = scrollIndex * visibleNodeRows * totalColumns; if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) { startIndex++; // skip own node @@ -445,12 +454,14 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t } } +#if !defined(M5STACK_UNITC6L) // Draw column separator if (shownCount > 0) { const int firstNodeY = y + 3; drawColumnSeparator(display, x, firstNodeY, lastNodeY); } +#endif const int scrollStartY = y + 3; drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY); } @@ -468,6 +479,13 @@ void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, unsigned long now = millis(); +#if defined(M5STACK_UNITC6L) + display->clear(); + if (now - lastSwitchTime >= 3000) { + display->display(); + lastSwitchTime = now; + } +#endif // On very first call (on boot or state enter) if (lastRenderedMode == MODE_COUNT) { currentMode = MODE_LAST_HEARD; @@ -522,6 +540,14 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, double lat = DegD(ourNode->position.latitude_i); double lon = DegD(ourNode->position.longitude_i); +#if defined(M5STACK_UNITC6L) + display->clear(); + uint32_t now = millis(); + if (now - lastSwitchTime >= 2000) { + display->display(); + lastSwitchTime = now; + } +#endif if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { #if HAS_GPS if (screen->hasHeading()) { diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index b53cd2f3f..26bfe8447 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -250,9 +250,11 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta } // Handle input - if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { + if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT || + inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { curSelected--; - } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { + } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT || + inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { curSelected++; } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { alertBannerCallback(selectedNodenum); @@ -365,9 +367,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp // Handle input if (alertBannerOptions > 0) { - if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { + if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT || + inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { curSelected--; - } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { + } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT || + inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { curSelected++; } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { if (optionsEnumPtr != nullptr) { @@ -491,6 +495,135 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay // count lines uint16_t boxWidth = hPadding * 2 + maxWidth; +#if defined(M5STACK_UNITC6L) + if (needs_bell) { + if (isHighResolution && boxWidth <= 150) + boxWidth += 26; + if (!isHighResolution && boxWidth <= 100) + boxWidth += 20; + } + + uint16_t screenHeight = display->height(); + uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3; + uint8_t visibleTotalLines = std::min(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight); + uint16_t contentHeight = visibleTotalLines * effectiveLineHeight; + uint16_t boxHeight = contentHeight + vPadding * 2; + if (visibleTotalLines == 1) + boxHeight += (isHighResolution ? 4 : 3); + + int16_t boxLeft = (display->width() / 2) - (boxWidth / 2); + if (totalLines > visibleTotalLines) + boxWidth += (isHighResolution ? 4 : 2); + int16_t boxTop = (display->height() / 2) - (boxHeight / 2); + + if (visibleTotalLines == 1) { + boxTop += 25; + } + if (alertBannerOptions < 3) { + int missingLines = 3 - alertBannerOptions; + int moveUp = missingLines * (effectiveLineHeight / 2); + boxTop -= moveUp; + if (boxTop < 0) + boxTop = 0; + } + + // === Draw Box === + display->setColor(BLACK); + display->fillRect(boxLeft, boxTop, boxWidth, boxHeight); + display->setColor(WHITE); + display->drawRect(boxLeft, boxTop, boxWidth, boxHeight); + display->fillRect(boxLeft, boxTop - 2, boxWidth, 1); + display->fillRect(boxLeft - 2, boxTop, 1, boxHeight); + display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight); + display->setColor(BLACK); + display->fillRect(boxLeft, boxTop, 1, 1); + display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1); + display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1); + display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1); + display->setColor(WHITE); + int16_t lineY = boxTop + vPadding; + int swingRange = 8; + static int swingOffset = 0; + static bool swingRight = true; + static unsigned long lastSwingTime = 0; + unsigned long now = millis(); + int swingSpeedMs = 10 / (swingRange * 2); + if (now - lastSwingTime >= (unsigned long)swingSpeedMs) { + lastSwingTime = now; + if (swingRight) { + swingOffset++; + if (swingOffset >= swingRange) + swingRight = false; + } else { + swingOffset--; + if (swingOffset <= 0) + swingRight = true; + } + } + for (int i = 0; i < lineCount; i++) { + bool isTitle = (i == 0); + int globalOptionIndex = (i - 1) + firstOptionToShow; + bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected); + + uint16_t visibleWidth = 64 - hPadding * 2; + if (totalLines > visibleTotalLines) + visibleWidth -= 6; + char lineBuffer[lineLengths[i] + 1]; + strncpy(lineBuffer, lines[i], lineLengths[i]); + lineBuffer[lineLengths[i]] = '\0'; + + if (isTitle) { + if (visibleTotalLines == 1) { + display->setColor(BLACK); + display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight); + display->setColor(WHITE); + display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer); + } else { + display->setColor(WHITE); + display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight); + display->setColor(BLACK); + display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer); + display->setColor(WHITE); + if (needs_bell) { + int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2; + display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert); + display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert); + } + } + lineY = boxTop + effectiveLineHeight + 1; + } else if (isSelectedOption) { + display->setColor(WHITE); + display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight); + display->setColor(BLACK); + if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) { + int textX = boxLeft + hPadding + swingOffset; + display->drawString(textX, lineY - 1, lineBuffer); + } else { + display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer); + } + display->setColor(WHITE); + lineY += effectiveLineHeight; + } else { + display->setColor(BLACK); + display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight); + display->setColor(WHITE); + display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer); + lineY += effectiveLineHeight; + } + } + if (totalLines > visibleTotalLines) { + const uint8_t scrollBarWidth = 5; + int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2; + int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight; + uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight; + float ratio = (float)visibleTotalLines / totalLines; + uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4); + float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines); + uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight); + display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight); + display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight); + } +#else if (needs_bell) { if (isHighResolution && boxWidth <= 150) boxWidth += 26; @@ -579,6 +712,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight); display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight); } +#endif } /// Draw the last text message we received diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 2a7f3aeee..202a28835 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -20,7 +20,7 @@ // External variables extern graphics::Screen *screen; - +static uint32_t lastSwitchTime = 0; namespace graphics { NodeNum UIRenderer::currentFavoriteNodeNum = 0; @@ -119,7 +119,7 @@ void UIRenderer::drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, con void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps, const char *mode) { - auto gpsFormat = config.display.gps_format; + auto gpsFormat = uiconfig.gps_format; char displayLine[32]; if (!gps->getIsConnected() && !config.position.fixed_position) { @@ -132,25 +132,25 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); - if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) { + if (gpsFormat != meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS) { char coordinateLine_1[22]; char coordinateLine_2[22]; - if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees + if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %f", geoCoord.getLatitude() * 1e-7); snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %f", geoCoord.getLongitude() * 1e-7); - } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator + } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %06u E", geoCoord.getUTMZone(), geoCoord.getUTMBand(), geoCoord.getUTMEasting()); snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%07u N", geoCoord.getUTMNorthing()); - } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System + } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %1c%1c", geoCoord.getMGRSZone(), geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k()); snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing()); - } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { // Open Location Code + } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC) { // Open Location Code geoCoord.getOLCCode(coordinateLine_1); coordinateLine_2[0] = '\0'; - } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference + } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') { // OSGR is only valid around the UK region snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%s", "Out of Boundary"); coordinateLine_2[0] = '\0'; @@ -160,6 +160,48 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing()); } + } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) { // Maidenhead Locator System + double lat = geoCoord.getLatitude() * 1e-7; + double lon = geoCoord.getLongitude() * 1e-7; + + // Normalize + if (lat > 90.0) + lat = 90.0; + if (lat < -90.0) + lat = -90.0; + while (lon < -180.0) + lon += 360.0; + while (lon >= 180.0) + lon -= 360.0; + + double adjLon = lon + 180.0; + double adjLat = lat + 90.0; + + char maiden[10]; // enough for 8-char + null + + // Field (2 letters) + int lonField = int(adjLon / 20.0); + int latField = int(adjLat / 10.0); + adjLon -= lonField * 20.0; + adjLat -= latField * 10.0; + + // Square (2 digits) + int lonSquare = int(adjLon / 2.0); + int latSquare = int(adjLat / 1.0); + adjLon -= lonSquare * 2.0; + adjLat -= latSquare * 1.0; + + // Subsquare (2 letters) + double lonUnit = 2.0 / 24.0; + double latUnit = 1.0 / 24.0; + int lonSub = int(adjLon / lonUnit); + int latSub = int(adjLat / latUnit); + + snprintf(maiden, sizeof(maiden), "%c%c%c%c%c%c", 'A' + lonField, 'A' + latField, '0' + lonSquare, '0' + latSquare, + 'A' + lonSub, 'A' + latSub); + + snprintf(coordinateLine_1, sizeof(coordinateLine_1), "MH: %s", maiden); + coordinateLine_2[0] = '\0'; // only need one line } if (strcmp(mode, "line1") == 0) { @@ -232,7 +274,6 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes // ********************** void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y) { - if (favoritedNodes.empty()) return; @@ -244,8 +285,15 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex]; if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite) return; - + uint32_t now = millis(); display->clear(); +#if defined(M5STACK_UNITC6L) + if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒 + { + display->display(); + lastSwitchTime = now; + } +#endif currentFavoriteNodeNum = node->num; // === Create the shortName and title string === const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node"; @@ -264,9 +312,13 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st // List of available macro Y positions in order, from top to bottom. int line = 1; // which slot to use next std::string usernameStr; - // === 1. Long Name (always try to show first) === +#if defined(M5STACK_UNITC6L) + const char *username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr; +#else const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr; +#endif + if (username) { usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case // Print node's long name (e.g. "Backpack Node") @@ -321,7 +373,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st if (seenStr[0] && line < 5) { display->drawString(x, getTextPositions(display)[line++], seenStr); } - +#if !defined(M5STACK_UNITC6L) // === 4. Uptime (only show if metric is present) === char uptimeStr[32] = ""; if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) { @@ -493,6 +545,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st } // else show nothing } +#endif } // **************************** @@ -506,7 +559,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta int line = 1; // === Header === +#if defined(M5STACK_UNITC6L) + graphics::drawCommonHeader(display, x, y, "Home"); +#else graphics::drawCommonHeader(display, x, y, ""); +#endif // === Content below header === @@ -521,20 +578,25 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta config.display.heading_bold = false; // Display Region and Channel Utilization +#if defined(M5STACK_UNITC6L) + drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online"); +#else drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online"); - +#endif char uptimeStr[32] = ""; uint32_t uptime = millis() / 1000; uint32_t days = uptime / 86400; uint32_t hours = (uptime % 86400) / 3600; uint32_t mins = (uptime % 3600) / 60; // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m" +#if !defined(M5STACK_UNITC6L) if (days) snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours); else if (hours) snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins); else snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins); +#endif display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr); // === Second Row: Satellites and Voltage === @@ -563,6 +625,21 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta } #endif +#if defined(M5STACK_UNITC6L) + line += 1; + + // === Node Identity === + int textWidth = 0; + int nameX = 0; + char shortnameble[35]; + snprintf(shortnameble, sizeof(shortnameble), "%s", + graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : ""); + + // === ShortName Centered === + textWidth = display->getStringWidth(shortnameble); + nameX = (SCREEN_WIDTH - textWidth) / 2; + display->drawString(nameX, getTextPositions(display)[line++], shortnameble); +#else if (powerStatus->getHasBattery()) { char batStr[20]; int batV = powerStatus->getBatteryVoltageMv() / 1000; @@ -688,6 +765,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], shortnameble); } +#endif } // Start Functions to write date/time to the screen @@ -846,6 +924,28 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED // needs to be drawn relative to x and y // draw centered icon left to right and centered above the one line of app text +#if defined(M5STACK_UNITC6L) + display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits); + display->setFont(FONT_MEDIUM); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + // Draw region in upper left + if (upperMsg) { + int msgWidth = display->getStringWidth(upperMsg); + int msgX = x + (SCREEN_WIDTH - msgWidth) / 2; + int msgY = y; + display->drawString(msgX, msgY, upperMsg); + } + // Draw version and short name in bottom middle + char buf[25]; + snprintf(buf, sizeof(buf), "%s %s", xstr(APP_VERSION_SHORT), + graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : ""); + + display->drawString(x + getStringCenteredX(buf), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, buf); + screen->forceDisplay(); + + display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code +#else display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2, icon_width, icon_height, icon_bits); @@ -854,7 +954,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED const char *title = "meshtastic.org"; display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); display->setFont(FONT_SMALL); - // Draw region in upper left if (upperMsg) display->drawString(x + 0, y + 0, upperMsg); @@ -869,6 +968,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED screen->forceDisplay(); display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code +#endif } // **************************** @@ -968,11 +1068,11 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU #if defined(USE_EINK) // E-Ink: skip seconds, show only days/hours/mins if (days > 0) { - snprintf(buf, sizeof(buf), " Last: %ud %uh", days, hours); + snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours); } else if (hours > 0) { - snprintf(buf, sizeof(buf), " Last: %uh %um", hours, mins); + snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins); } else { - snprintf(buf, sizeof(buf), " Last: %um", mins); + snprintf(buf, sizeof(buf), "Last: %um", mins); } #else // Non E-Ink: include seconds where useful @@ -995,24 +1095,13 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU // === Third Row: Line 1 GPS Info === UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line1"); - if (config.display.gps_format != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { + if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC && + uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) { // === Fourth Row: Line 2 GPS Info === UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2"); } - - // === Fourth/Fifth Row: Altitude === - char DisplayLineTwo[32] = {0}; - int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode)) - ? ourNode->position.altitude - : geoCoord.getAltitude(); - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { - snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET); - } else { - snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0im", geoCoord.getAltitude()); - } - display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo); } - +#if !defined(M5STACK_UNITC6L) // === Draw Compass if heading is valid === if (validHeading) { // --- Compass Rendering: landscape (wide) screens use original side-aligned logic --- @@ -1095,6 +1184,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU } } #endif +#endif // HAS_GPS } #ifdef USERPREFS_OEM_TEXT @@ -1251,7 +1341,6 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta display->setColor(WHITE); } } - // Knock the corners off the square display->setColor(BLACK); display->drawRect(rectX, y - 2, 1, 1); diff --git a/src/graphics/images.h b/src/graphics/images.h index 168a9b716..72dda7886 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -287,7 +287,9 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101 #define analog_icon_clock_height 8 const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000, 0b00100100, 0b01000010, 0b01000010, 0b11111111}; - +#ifdef M5STACK_UNITC6L +#include "img/icon_small.xbm" +#else #define chirpy_width 38 #define chirpy_height 50 static unsigned char chirpy[] = { @@ -361,4 +363,5 @@ static unsigned char chirpy_hirez[] = { static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f}; #include "img/icon.xbm" +#endif static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning"); \ No newline at end of file diff --git a/src/graphics/img/icon_small.xbm b/src/graphics/img/icon_small.xbm new file mode 100644 index 000000000..97884edad --- /dev/null +++ b/src/graphics/img/icon_small.xbm @@ -0,0 +1,116 @@ +#ifndef USERPREFS_HAS_SPLASH +#define icon_width 50 +#define icon_height 20 +static uint8_t icon_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x80, + 0x07, 0xc0, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x0f, + 0xc0, 0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xe0, + 0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x1f, + 0x00, 0x00, 0x00, 0xf0, 0x03, 0xf0, 0x3f, 0x00, + 0x00, 0x00, 0xf8, 0x03, 0xf8, 0x7f, 0x00, 0x00, + 0x00, 0xf8, 0x01, 0xfc, 0x7e, 0x00, 0x00, 0x00, + 0xfc, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0xfe, + 0x00, 0x7e, 0xf8, 0x00, 0x00, 0x00, 0x7e, 0x00, + 0x3f, 0xf8, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x1f, + 0xf0, 0x01, 0x00, 0x00, 0x1f, 0x80, 0x1f, 0xe0, + 0x03, 0x00, 0x80, 0x1f, 0xc0, 0x0f, 0xe0, 0x03, + 0x00, 0x80, 0x0f, 0xc0, 0x07, 0xc0, 0x07, 0x00, + 0xc0, 0x0f, 0xe0, 0x07, 0x80, 0x0f, 0x00, 0xe0, + 0x07, 0xf0, 0x03, 0x80, 0x1f, 0x00, 0xe0, 0x03, + 0xf8, 0x03, 0x00, 0x1f, 0x00, 0xf0, 0x03, 0xf8, + 0x01, 0x00, 0x3f, 0x00, 0xf8, 0x01, 0xfc, 0x00, + 0x00, 0x7e, 0x00, 0xfc, 0x00, 0xfe, 0x00, 0x00, + 0x7e, 0x00, 0xfc, 0x00, 0x7e, 0x00, 0x00, 0xfc, + 0x00, 0x7e, 0x00, 0x3f, 0x00, 0x00, 0xf8, 0x00, + 0x7e, 0x00, 0x3e, 0x00, 0x00, 0xf8, 0x00, 0x38, + 0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; +#endif + +// Chirpy image definitions for M5STACK_UNITC6L compatibility +#define chirpy_width 38 +#define chirpy_height 50 +static unsigned char chirpy[] = { + 0xfe, 0xff, 0xff, 0xff, 0xdf, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01, + 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01, 0x00, + 0x00, 0x00, 0xe0, 0x81, 0xff, 0xff, 0x7f, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xcf, 0x7f, + 0xfe, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, + 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, + 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, + 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0xcf, 0x7f, 0xfe, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0x81, 0xff, + 0xff, 0x7f, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xc3, 0x00, 0xe0, 0x01, 0x00, 0xc3, + 0x00, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x30, 0x03, 0xe0, 0x01, 0xc0, 0x30, 0x03, + 0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0, + 0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01, + 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xfe, 0xff, 0xff, 0xff, 0xdf}; + +#define chirpy_width_hirez 76 +#define chirpy_height_hirez 100 +static unsigned char chirpy_hirez[] = { + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, + 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, + 0x03, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, + 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, + 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, + 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, + 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, + 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, + 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, + 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, + 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, + 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, + 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, + 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, + 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, + 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, + 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, + 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, + 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, + 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, + 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, + 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, + 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, + 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, + 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x1f, + 0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00, + 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00, + 0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00, + 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc, + 0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00, + 0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00, + 0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, + 0x03, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07, + 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, + 0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc, + 0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03, + 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x80, 0x07, + 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0xc0, 0x03, 0x00, + 0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0f, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3 +}; + +#define chirpy_small_image_width 8 +#define chirpy_small_image_height 8 +static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f}; \ No newline at end of file diff --git a/src/input/ExpressLRSFiveWay.cpp b/src/input/ExpressLRSFiveWay.cpp index 776b9001d..01712ad2a 100644 --- a/src/input/ExpressLRSFiveWay.cpp +++ b/src/input/ExpressLRSFiveWay.cpp @@ -188,7 +188,7 @@ void ExpressLRSFiveWay::determineAction(KeyType key, PressLength length) // Feed input to the canned messages module void ExpressLRSFiveWay::sendKey(input_broker_event key) { - InputEvent e; + InputEvent e = {}; e.source = inputSourceName; e.inputEvent = key; notifyObservers(&e); diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp index 1f80fd5d3..fee7c8ded 100644 --- a/src/input/LinuxInput.cpp +++ b/src/input/LinuxInput.cpp @@ -73,7 +73,7 @@ int32_t LinuxInput::runOnce() int rd = read(events[i].data.fd, ev, sizeof(ev)); assert(rd > ((signed int)sizeof(struct input_event))); for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) { - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; e.source = this->_originName; e.kbchar = 0; diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp index 204a0fbf0..980057ce4 100644 --- a/src/input/RotaryEncoderInterruptBase.cpp +++ b/src/input/RotaryEncoderInterruptBase.cpp @@ -45,7 +45,7 @@ void RotaryEncoderInterruptBase::init( int32_t RotaryEncoderInterruptBase::runOnce() { - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; e.source = this->_originName; unsigned long now = millis(); @@ -151,7 +151,7 @@ RotaryEncoderInterruptBaseStateType RotaryEncoderInterruptBase::intHandler(bool // Logic to prevent bouncing. newState = ROTARY_EVENT_CLEARED; } - setIntervalFromNow(50); // TODO: this modifies a non-volatile variable! + setIntervalFromNow(ROTARY_DELAY); // TODO: this modifies a non-volatile variable! return newState; } diff --git a/src/input/SeesawRotary.cpp b/src/input/SeesawRotary.cpp index c212773c4..0a6e6e974 100644 --- a/src/input/SeesawRotary.cpp +++ b/src/input/SeesawRotary.cpp @@ -49,7 +49,7 @@ bool SeesawRotary::init() int32_t SeesawRotary::runOnce() { - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; bool currentlyPressed = !ss.digitalRead(SS_SWITCH); diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index 63501bda5..2df1ace70 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -29,7 +29,7 @@ SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name) void SerialKeyboard::erase() { - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_BACK; e.kbchar = 0x08; e.source = this->_originName; @@ -80,7 +80,7 @@ int32_t SerialKeyboard::runOnce() if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but // shouldn't be a limitation - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; e.source = this->_originName; // SELECT OR SEND OR CANCEL EVENT diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp index c0e220941..69dcab04e 100644 --- a/src/input/TouchScreenImpl1.cpp +++ b/src/input/TouchScreenImpl1.cpp @@ -47,7 +47,7 @@ bool TouchScreenImpl1::getTouch(int16_t &x, int16_t &y) */ void TouchScreenImpl1::onEvent(const TouchEvent &event) { - InputEvent e; + InputEvent e = {}; e.source = event.source; e.kbchar = 0; e.touchX = event.x; diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp index 4c8ce6409..4ddaf7064 100644 --- a/src/input/TrackballInterruptBase.cpp +++ b/src/input/TrackballInterruptBase.cpp @@ -51,7 +51,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef int32_t TrackballInterruptBase::runOnce() { - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; // Handle long press detection for press button diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp index 0bf0f5cd4..d597c8d8f 100644 --- a/src/input/UpDownInterruptBase.cpp +++ b/src/input/UpDownInterruptBase.cpp @@ -48,7 +48,7 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, int32_t UpDownInterruptBase::runOnce() { - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; unsigned long now = millis(); diff --git a/src/input/i2cButton.cpp b/src/input/i2cButton.cpp new file mode 100644 index 000000000..d874146cd --- /dev/null +++ b/src/input/i2cButton.cpp @@ -0,0 +1,95 @@ +#include "i2cButton.h" +#include "meshUtils.h" + +#include "configuration.h" +#if defined(M5STACK_UNITC6L) + +#include "MeshService.h" +#include "RadioLibInterface.h" +#include "buzz.h" +#include "input/InputBroker.h" +#include "main.h" +#include "modules/CannedMessageModule.h" +#include "modules/ExternalNotificationModule.h" +#include "power.h" +#include "sleep.h" +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#endif + +i2cButtonThread *i2cButton; + +using namespace concurrency; + +extern void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value); + +extern void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value); + +#define PI4IO_M_ADDR 0x43 +#define getbit(x, y) ((x) >> (y)&0x01) +#define PI4IO_REG_IRQ_STA 0x13 +#define PI4IO_REG_IN_STA 0x0F +#define PI4IO_REG_CHIP_RESET 0x01 + +i2cButtonThread::i2cButtonThread(const char *name) : OSThread(name) +{ + _originName = name; + if (inputBroker) + inputBroker->registerSource(this); +} + +int32_t i2cButtonThread::runOnce() +{ + static bool btn1_pressed = false; + static uint32_t press_start_time = 0; + const uint32_t LONG_PRESS_TIME = 1000; + static bool long_press_triggered = false; + + uint8_t in_data; + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, in_data); + if (getbit(in_data, 0)) { + uint8_t input_state; + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IN_STA, &input_state); + + if (!getbit(input_state, 0)) { + if (!btn1_pressed) { + btn1_pressed = true; + press_start_time = millis(); + long_press_triggered = false; + } + } else { + if (btn1_pressed) { + btn1_pressed = false; + uint32_t press_duration = millis() - press_start_time; + if (long_press_triggered) { + long_press_triggered = false; + return 50; + } + + if (press_duration < LONG_PRESS_TIME) { + InputEvent evt; + evt.source = "UserButton"; + evt.inputEvent = INPUT_BROKER_USER_PRESS; + evt.kbchar = 0; + evt.touchX = 0; + evt.touchY = 0; + this->notifyObservers(&evt); + } + } + } + } + + if (btn1_pressed && !long_press_triggered && (millis() - press_start_time >= LONG_PRESS_TIME)) { + long_press_triggered = true; + InputEvent evt; + evt.source = "UserButton"; + evt.inputEvent = INPUT_BROKER_SELECT; + evt.kbchar = 0; + evt.touchX = 0; + evt.touchY = 0; + this->notifyObservers(&evt); + } + return 50; +} +#endif \ No newline at end of file diff --git a/src/input/i2cButton.h b/src/input/i2cButton.h new file mode 100644 index 000000000..1ad908606 --- /dev/null +++ b/src/input/i2cButton.h @@ -0,0 +1,18 @@ +#pragma once + +#include "InputBroker.h" +#include "OneButton.h" +#include "concurrency/OSThread.h" +#include "configuration.h" +#if defined(M5STACK_UNITC6L) + +class i2cButtonThread : public Observable, public concurrency::OSThread +{ + public: + const char *_originName; + explicit i2cButtonThread(const char *name); + int32_t runOnce() override; +}; + +extern i2cButtonThread *i2cButton; +#endif diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 5db1e39a9..3f5d4d184 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -90,7 +90,7 @@ int32_t KbI2cBase::runOnce() while (keyCount--) { const BBQ10Keyboard::KeyEvent key = Q10keyboard.keyEvent(); if ((key.key != 0x00) && (key.state == BBQ10Keyboard::StateRelease)) { - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; e.source = this->_originName; switch (key.key) { @@ -187,7 +187,7 @@ int32_t KbI2cBase::runOnce() } case 0x37: { // MPR121 MPRkeyboard.trigger(); - InputEvent e; + InputEvent e = {}; while (MPRkeyboard.hasEvent()) { char nextEvent = MPRkeyboard.dequeueEvent(); @@ -250,7 +250,7 @@ int32_t KbI2cBase::runOnce() } case 0x84: { // Adafruit TCA8418 TCAKeyboard.trigger(); - InputEvent e; + InputEvent e = {}; while (TCAKeyboard.hasEvent()) { char nextEvent = TCAKeyboard.dequeueEvent(); e.inputEvent = INPUT_BROKER_ANYKEY; @@ -350,7 +350,7 @@ int32_t KbI2cBase::runOnce() } if (PrintDataBuf != 0) { LOG_DEBUG("RAK14004 key 0x%x pressed", PrintDataBuf); - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_MATRIXKEY; e.source = this->_originName; e.kbchar = PrintDataBuf; @@ -365,7 +365,7 @@ int32_t KbI2cBase::runOnce() if (i2cBus->available()) { char c = i2cBus->read(); - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; e.source = this->_originName; switch (c) { diff --git a/src/input/kbMatrixBase.cpp b/src/input/kbMatrixBase.cpp index 05f4d8177..18243f3bf 100644 --- a/src/input/kbMatrixBase.cpp +++ b/src/input/kbMatrixBase.cpp @@ -72,7 +72,7 @@ int32_t KbMatrixBase::runOnce() if (key != 0) { LOG_DEBUG("Key 0x%x pressed", key); // reset shift now that we have a keypress - InputEvent e; + InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; e.source = this->_originName; switch (key) { diff --git a/src/main.cpp b/src/main.cpp index 510d2b898..a4335187c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -387,7 +387,6 @@ void setup() io.digitalWrite(EXPANDS_GPIO_EN, HIGH); io.pinMode(EXPANDS_SD_PULLEN, INPUT); #endif - concurrency::hasBeenSetup = true; #if ARCH_PORTDUINO SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); @@ -546,6 +545,12 @@ void setup() #endif #endif +#if defined(M5STACK_UNITC6L) + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, 1); + c6l_init(); +#endif + #ifdef PIN_LCD_RESET // FIXME - move this someplace better, LCD is at address 0x3F pinMode(PIN_LCD_RESET, OUTPUT); @@ -879,7 +884,8 @@ void setup() if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_SPISSD1306) screen = new graphics::Screen(screen_found, screen_model, screen_geometry); #elif defined(ARCH_PORTDUINO) if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) && @@ -1142,7 +1148,8 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_SPISSD1306) if (screen) screen->setup(); #elif defined(ARCH_PORTDUINO) @@ -1506,9 +1513,6 @@ extern meshtastic_DeviceMetadata getDeviceMetadata() deviceMetadata.hw_model = HW_VENDOR; deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled; deviceMetadata.excluded_modules = meshtastic_ExcludedModules_EXCLUDED_NONE; -#if MESHTASTIC_EXCLUDE_MQTT - deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_MQTT_CONFIG; -#endif #if MESHTASTIC_EXCLUDE_REMOTEHARDWARE deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_REMOTEHARDWARE_CONFIG; #endif @@ -1531,21 +1535,10 @@ extern meshtastic_DeviceMetadata getDeviceMetadata() #if NO_EXT_GPIO && NO_GPS || MESHTASTIC_EXCLUDE_SERIAL deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_SERIAL_CONFIG; #endif -#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_PAXCOUNTER - // PAXCOUNTER is only supported on ESP32 due to memory constraints -#else +#ifndef ARCH_ESP32 deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_PAXCOUNTER_CONFIG; #endif -#if MESHTASTIC_EXCLUDE_STOREFORWARD - deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_STOREFORWARD_CONFIG; -#endif -#if MESHTASTIC_EXCLUDE_RANGETEST - deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_RANGETEST_CONFIG; -#endif -#if MESHTASTIC_EXCLUDE_NEIGHBORINFO - deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_NEIGHBORINFO_CONFIG; -#endif -#if (!defined(HAS_RGB_LED) && !defined(RAK_4631)) || defined(MESHTASTIC_EXCLUDE_AMBIENTLIGHTING) +#if !defined(HAS_RGB_LED) && !RAK_4631 deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AMBIENTLIGHTING_CONFIG; #endif diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 4ef41ddfb..aec112a3e 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -423,6 +423,33 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash) } } +bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash) +{ + // Iterate all known presets + for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX; + ++preset) { + const char *name = + DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false, false); + if (!name) + continue; + if (strcmp(name, "Invalid") == 0) + continue; // skip invalid placeholder + uint8_t h = xorHash((const uint8_t *)name, strlen(name)); + // Expand default PSK alias 1 to actual bytes and xor into hash + uint8_t tmp = h ^ xorHash(defaultpsk, sizeof(defaultpsk)); + if (tmp == channelHash) { + // Set crypto to defaultpsk and report success + CryptoKey k; + memcpy(k.bytes, defaultpsk, sizeof(defaultpsk)); + k.length = sizeof(defaultpsk); + crypto->setKey(k); + LOG_INFO("Matched default preset '%s' for hash 0x%x; set default PSK", name, channelHash); + return true; + } + } + return false; +} + /** Given a channel index setup crypto for encoding that channel (or the primary channel if that channel is unsecured) * * This method is called before encoding outbound packets diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index 7873a306a..b53f552fa 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -94,6 +94,8 @@ class Channels bool ensureLicensedOperation(); + bool setDefaultPresetCryptoForHash(ChannelHash channelHash); + private: /** Given a channel index, change to use the crypto key specified by that index * diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 1859ca27b..237b4286c 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.bluetooth.fixed_pin = defaultBLEPin; #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) bool hasScreen = true; #ifdef HELTEC_MESH_NODE_T114 uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET); @@ -775,9 +775,7 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.version = DEVICESTATE_CUR_VER; moduleConfig.has_mqtt = true; -#if !MESHTASTIC_EXCLUDE_RANGETEST moduleConfig.has_range_test = true; -#endif moduleConfig.has_serial = true; moduleConfig.has_store_forward = true; moduleConfig.has_telemetry = true; @@ -843,12 +841,6 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; #endif moduleConfig.has_canned_message = true; -#if !MESHTASTIC_EXCLUDE_AUDIO - moduleConfig.has_audio = true; -#endif -#if !MESHTASTIC_EXCLUDE_PAXCOUNTER - moduleConfig.has_paxcounter = true; -#endif #if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT moduleConfig.mqtt.enabled = true; #endif @@ -891,14 +883,12 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.detection_sensor.detection_trigger_type = meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH; moduleConfig.detection_sensor.minimum_broadcast_secs = 45; -#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING moduleConfig.has_ambient_lighting = true; moduleConfig.ambient_lighting.current = 10; // Default to a color based on our node number moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16; moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; -#endif initModuleConfigIntervals(); } @@ -1438,25 +1428,15 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) moduleConfig.has_canned_message = true; moduleConfig.has_external_notification = true; moduleConfig.has_mqtt = true; -#if !MESHTASTIC_EXCLUDE_RANGETEST moduleConfig.has_range_test = true; -#endif moduleConfig.has_serial = true; -#if !MESHTASTIC_EXCLUDE_STOREFORWARD moduleConfig.has_store_forward = true; -#endif moduleConfig.has_telemetry = true; moduleConfig.has_neighbor_info = true; moduleConfig.has_detection_sensor = true; -#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING moduleConfig.has_ambient_lighting = true; -#endif -#if !MESHTASTIC_EXCLUDE_AUDIO moduleConfig.has_audio = true; -#endif -#if !MESHTASTIC_EXCLUDE_PAXCOUNTER moduleConfig.has_paxcounter = true; -#endif success &= saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index c6c3415bb..9fb1b589f 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -34,21 +34,6 @@ // Flag to indicate a heartbeat was received and we should send queue status bool heartbeatReceived = false; -// Helper function to skip excluded module configs and advance state -size_t PhoneAPI::skipExcludedModuleConfig(uint8_t *buf) -{ - config_state++; - if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) { - if (config_nonce == SPECIAL_NONCE_ONLY_CONFIG) { - state = STATE_SEND_FILEMANIFEST; - } else { - state = STATE_SEND_OTHER_NODEINFOS; - } - config_state = 0; - } - return getFromRadio(buf); -} - PhoneAPI::PhoneAPI() { lastContactMsec = millis(); @@ -370,35 +355,20 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.moduleConfig.payload_variant.serial = moduleConfig.serial; break; case meshtastic_ModuleConfig_external_notification_tag: -#if !(NO_EXT_GPIO || MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION) LOG_DEBUG("Send module config: ext notification"); fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_external_notification_tag; fromRadioScratch.moduleConfig.payload_variant.external_notification = moduleConfig.external_notification; break; -#else - LOG_DEBUG("External Notification module excluded from build, skipping"); - return skipExcludedModuleConfig(buf); -#endif case meshtastic_ModuleConfig_store_forward_tag: -#if !MESHTASTIC_EXCLUDE_STOREFORWARD LOG_DEBUG("Send module config: store forward"); fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_store_forward_tag; fromRadioScratch.moduleConfig.payload_variant.store_forward = moduleConfig.store_forward; break; -#else - LOG_DEBUG("Store & Forward module excluded from build, skipping"); - return skipExcludedModuleConfig(buf); -#endif case meshtastic_ModuleConfig_range_test_tag: -#if !MESHTASTIC_EXCLUDE_RANGETEST LOG_DEBUG("Send module config: range test"); fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_range_test_tag; fromRadioScratch.moduleConfig.payload_variant.range_test = moduleConfig.range_test; break; -#else - LOG_DEBUG("Range Test module excluded from build, skipping"); - return skipExcludedModuleConfig(buf); -#endif case meshtastic_ModuleConfig_telemetry_tag: LOG_DEBUG("Send module config: telemetry"); fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_telemetry_tag; @@ -410,15 +380,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.moduleConfig.payload_variant.canned_message = moduleConfig.canned_message; break; case meshtastic_ModuleConfig_audio_tag: -#if !MESHTASTIC_EXCLUDE_AUDIO LOG_DEBUG("Send module config: audio"); fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_audio_tag; fromRadioScratch.moduleConfig.payload_variant.audio = moduleConfig.audio; break; -#else - LOG_DEBUG("Audio module excluded from build, skipping"); - return skipExcludedModuleConfig(buf); -#endif case meshtastic_ModuleConfig_remote_hardware_tag: LOG_DEBUG("Send module config: remote hardware"); fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_remote_hardware_tag; @@ -435,25 +400,15 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.moduleConfig.payload_variant.detection_sensor = moduleConfig.detection_sensor; break; case meshtastic_ModuleConfig_ambient_lighting_tag: -#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING LOG_DEBUG("Send module config: ambient lighting"); fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_ambient_lighting_tag; fromRadioScratch.moduleConfig.payload_variant.ambient_lighting = moduleConfig.ambient_lighting; break; -#else - LOG_DEBUG("Ambient Lighting module excluded from build, skipping"); - return skipExcludedModuleConfig(buf); -#endif case meshtastic_ModuleConfig_paxcounter_tag: -#if !MESHTASTIC_EXCLUDE_PAXCOUNTER LOG_DEBUG("Send module config: paxcounter"); fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag; fromRadioScratch.moduleConfig.payload_variant.paxcounter = moduleConfig.paxcounter; break; -#else - LOG_DEBUG("Paxcounter module excluded from build, skipping"); - return skipExcludedModuleConfig(buf); -#endif default: LOG_ERROR("Unknown module config type %d", config_state); } diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 6b4bb6fc1..0d7772d17 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -172,7 +172,4 @@ class PhoneAPI /// If the mesh service tells us fromNum has changed, tell the phone virtual int onNotify(uint32_t newValue) override; - - /// Helper function to skip excluded module configs and advance state - size_t skipExcludedModuleConfig(uint8_t *buf); }; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 37b70dab6..09fb079c5 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -430,6 +430,36 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p) } } } + +#if HAS_UDP_MULTICAST + // Fallback: for UDP multicast, try default preset names with default PSK if normal channel match failed + if (!decrypted && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP) { + if (channels.setDefaultPresetCryptoForHash(p->channel)) { + memcpy(bytes, p->encrypted.bytes, rawSize); + crypto->decrypt(p->from, p->id, rawSize, bytes); + + meshtastic_Data decodedtmp; + memset(&decodedtmp, 0, sizeof(decodedtmp)); + if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) && + decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) { + p->decoded = decodedtmp; + p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; + // Map to our local default channel index (name+PSK default), not necessarily primary + ChannelIndex defaultIndex = channels.getPrimaryIndex(); + for (ChannelIndex i = 0; i < channels.getNumChannels(); ++i) { + if (channels.isDefaultChannel(i)) { + defaultIndex = i; + break; + } + } + chIndex = defaultIndex; + decrypted = true; + } else { + LOG_WARN("UDP fallback decode attempted but failed for hash 0x%x", p->channel); + } + } + } +#endif if (decrypted) { // parsing was successful p->channel = chIndex; // change to store the index instead of the hash diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 547dd0175..4ca2c197f 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -50,15 +50,15 @@ class StreamAPI : public PhoneAPI * phone. */ virtual int32_t runOncePart(); - virtual int32_t runOncePart(char *buf,uint16_t bufLen); + virtual int32_t runOncePart(char *buf, uint16_t bufLen); private: /** * Read any rx chars from the link and call handleToRadio */ int32_t readStream(); - int32_t readStream(char *buf,uint16_t bufLen); - int32_t handleRecStream(char *buf,uint16_t bufLen); + int32_t readStream(char *buf, uint16_t bufLen); + int32_t handleRecStream(char *buf, uint16_t bufLen); /** * call getFromRadio() and deliver encapsulated packets to the Stream diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 59e55db3f..0453ecad2 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -173,28 +173,10 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags { meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1 } meshtastic_Config_NetworkConfig_ProtocolFlags; -/* How the GPS coordinates are displayed on the OLED screen. */ -typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat { - /* GPS coordinates are displayed in the normal decimal degrees format: - DD.DDDDDD DDD.DDDDDD */ - meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC = 0, - /* GPS coordinates are displayed in the degrees minutes seconds format: - DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */ - meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS = 1, - /* Universal Transverse Mercator format: - ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */ - meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM = 2, - /* Military Grid Reference System format: - ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square, - E is easting, N is northing */ - meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS = 3, - /* Open Location Code (aka Plus Codes). */ - meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC = 4, - /* Ordnance Survey Grid Reference (the National Grid System of the UK). - Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square, - E is the easting, N is the northing */ - meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR = 5 -} meshtastic_Config_DisplayConfig_GpsCoordinateFormat; +/* Deprecated in 2.7.4: Unused */ +typedef enum _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat { + meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED = 0 +} meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat; /* Unit display preference */ typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits { @@ -491,7 +473,7 @@ typedef struct _meshtastic_Config_DisplayConfig { uint32_t screen_on_secs; /* Deprecated in 2.7.4: Unused How the GPS coordinates are formatted on the OLED screen. */ - meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format; + meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat gps_format; /* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds. Potentially useful for devices without user buttons. */ uint32_t auto_screen_carousel_secs; @@ -515,6 +497,9 @@ typedef struct _meshtastic_Config_DisplayConfig { /* If false (default), the device will display the time in 24-hour format on screen. If true, the device will display the time in 12-hour format on screen. */ bool use_12h_clock; + /* If false (default), the device will use short names for various display screens. + If true, node names will show in long format */ + bool use_long_node_name; } meshtastic_Config_DisplayConfig; /* Lora Config */ @@ -678,9 +663,9 @@ extern "C" { #define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST #define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1)) -#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC -#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR -#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1)) +#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED +#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED +#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat)(meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED+1)) #define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC #define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL @@ -721,7 +706,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode -#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_GpsCoordinateFormat +#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat #define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits #define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType #define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode @@ -742,7 +727,7 @@ extern "C" { #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} -#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0} +#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} @@ -753,7 +738,7 @@ extern "C" { #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} -#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0} +#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} @@ -820,6 +805,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10 #define meshtastic_Config_DisplayConfig_compass_orientation_tag 11 #define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12 +#define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13 #define meshtastic_Config_LoRaConfig_use_preset_tag 1 #define meshtastic_Config_LoRaConfig_modem_preset_tag 2 #define meshtastic_Config_LoRaConfig_bandwidth_tag 3 @@ -965,7 +951,8 @@ X(a, STATIC, SINGULAR, UENUM, displaymode, 8) \ X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \ X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \ X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) \ -X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12) +X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12) \ +X(a, STATIC, SINGULAR, BOOL, use_long_node_name, 13) #define meshtastic_Config_DisplayConfig_CALLBACK NULL #define meshtastic_Config_DisplayConfig_DEFAULT NULL @@ -1043,7 +1030,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size #define meshtastic_Config_BluetoothConfig_size 10 #define meshtastic_Config_DeviceConfig_size 100 -#define meshtastic_Config_DisplayConfig_size 32 +#define meshtastic_Config_DisplayConfig_size 34 #define meshtastic_Config_LoRaConfig_size 85 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 204 diff --git a/src/mesh/generated/meshtastic/device_ui.pb.cpp b/src/mesh/generated/meshtastic/device_ui.pb.cpp index 2fc8d9461..01940265f 100644 --- a/src/mesh/generated/meshtastic/device_ui.pb.cpp +++ b/src/mesh/generated/meshtastic/device_ui.pb.cpp @@ -28,3 +28,5 @@ PB_BIND(meshtastic_Map, meshtastic_Map, AUTO) + + diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h index 8f693e570..d9eb90773 100644 --- a/src/mesh/generated/meshtastic/device_ui.pb.h +++ b/src/mesh/generated/meshtastic/device_ui.pb.h @@ -74,6 +74,32 @@ typedef enum _meshtastic_Language { meshtastic_Language_TRADITIONAL_CHINESE = 31 } meshtastic_Language; +/* How the GPS coordinates are displayed on the OLED screen. */ +typedef enum _meshtastic_DeviceUIConfig_GpsCoordinateFormat { + /* GPS coordinates are displayed in the normal decimal degrees format: + DD.DDDDDD DDD.DDDDDD */ + meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC = 0, + /* GPS coordinates are displayed in the degrees minutes seconds format: + DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */ + meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS = 1, + /* Universal Transverse Mercator format: + ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */ + meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM = 2, + /* Military Grid Reference System format: + ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square, + E is easting, N is northing */ + meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS = 3, + /* Open Location Code (aka Plus Codes). */ + meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC = 4, + /* Ordnance Survey Grid Reference (the National Grid System of the UK). + Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square, + E is the easting, N is the northing */ + meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR = 5, + /* Maidenhead Locator System + Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System */ + meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS = 6 +} meshtastic_DeviceUIConfig_GpsCoordinateFormat; + /* Struct definitions */ typedef struct _meshtastic_NodeFilter { /* Filter unknown nodes */ @@ -163,6 +189,8 @@ typedef struct _meshtastic_DeviceUIConfig { /* Clockface analog style true for analog clockface, false for digital clockface */ bool is_clockface_analog; + /* How the GPS coordinates are formatted on the OLED screen. */ + meshtastic_DeviceUIConfig_GpsCoordinateFormat gps_format; } meshtastic_DeviceUIConfig; @@ -183,9 +211,14 @@ extern "C" { #define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE #define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1)) +#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC +#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MAX meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS +#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_DeviceUIConfig_GpsCoordinateFormat)(meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS+1)) + #define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme #define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language #define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode +#define meshtastic_DeviceUIConfig_gps_format_ENUMTYPE meshtastic_DeviceUIConfig_GpsCoordinateFormat @@ -193,12 +226,12 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0} +#define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN} #define meshtastic_NodeFilter_init_default {0, 0, 0, 0, 0, "", 0} #define meshtastic_NodeHighlight_init_default {0, 0, 0, 0, ""} #define meshtastic_GeoPoint_init_default {0, 0, 0} #define meshtastic_Map_init_default {false, meshtastic_GeoPoint_init_default, "", 0} -#define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0} +#define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN} #define meshtastic_NodeFilter_init_zero {0, 0, 0, 0, 0, "", 0} #define meshtastic_NodeHighlight_init_zero {0, 0, 0, 0, ""} #define meshtastic_GeoPoint_init_zero {0, 0, 0} @@ -241,6 +274,7 @@ extern "C" { #define meshtastic_DeviceUIConfig_compass_mode_tag 16 #define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17 #define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18 +#define meshtastic_DeviceUIConfig_gps_format_tag 19 /* Struct field encoding specification for nanopb */ #define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \ @@ -261,7 +295,8 @@ X(a, STATIC, SINGULAR, BYTES, calibration_data, 14) \ X(a, STATIC, OPTIONAL, MESSAGE, map_data, 15) \ X(a, STATIC, SINGULAR, UENUM, compass_mode, 16) \ X(a, STATIC, SINGULAR, UINT32, screen_rgb_color, 17) \ -X(a, STATIC, SINGULAR, BOOL, is_clockface_analog, 18) +X(a, STATIC, SINGULAR, BOOL, is_clockface_analog, 18) \ +X(a, STATIC, SINGULAR, UENUM, gps_format, 19) #define meshtastic_DeviceUIConfig_CALLBACK NULL #define meshtastic_DeviceUIConfig_DEFAULT NULL #define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter @@ -318,7 +353,7 @@ extern const pb_msgdesc_t meshtastic_Map_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size -#define meshtastic_DeviceUIConfig_size 201 +#define meshtastic_DeviceUIConfig_size 204 #define meshtastic_GeoPoint_size 33 #define meshtastic_Map_size 58 #define meshtastic_NodeFilter_size 47 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 9b6330596..7fab82ff7 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -360,7 +360,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2273 +#define meshtastic_BackupPreferences_size 2277 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index da224fb94..3ab6f02c1 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -187,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size -#define meshtastic_LocalConfig_size 747 -#define meshtastic_LocalModuleConfig_size 671 +#define meshtastic_LocalConfig_size 749 +#define meshtastic_LocalModuleConfig_size 673 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 2a4e77870..6292ce070 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_T_DECK_PRO = 102, /* Lilygo TLora Pager */ meshtastic_HardwareModel_T_LORA_PAGER = 103, - /* GAT562 Mesh Trial Tracker */ - meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104, + /* M5Stack Reserved */ + meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */ /* RAKwireless WisMesh Tag */ meshtastic_HardwareModel_WISMESH_TAG = 105, /* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */ @@ -274,6 +274,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_T_ECHO_LITE = 109, /* New Heltec LoRA32 with ESP32-S3 CPU */ meshtastic_HardwareModel_HELTEC_V4 = 110, + /* M5Stack C6L */ + meshtastic_HardwareModel_M5STACK_C6L = 111, + /* M5Stack Cardputer Adv */ + meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 16c4c230c..47d3b5baa 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -356,6 +356,9 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig { uint32_t health_update_interval; /* Enable/Disable the health telemetry module on-device display */ bool health_screen_enabled; + /* Enable/Disable the device telemetry module to send metrics to the mesh + Note: We will still send telemtry to the connected phone / client every minute over the API */ + bool device_telemetry_enabled; } meshtastic_ModuleConfig_TelemetryConfig; /* Canned Messages Module Config */ @@ -523,7 +526,7 @@ extern "C" { #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0, 0} -#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} @@ -539,7 +542,7 @@ extern "C" { #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0, 0} -#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} @@ -627,6 +630,7 @@ extern "C" { #define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11 #define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12 #define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13 +#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14 #define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3 @@ -825,7 +829,8 @@ X(a, STATIC, SINGULAR, UINT32, power_update_interval, 9) \ X(a, STATIC, SINGULAR, BOOL, power_screen_enabled, 10) \ X(a, STATIC, SINGULAR, BOOL, health_measurement_enabled, 11) \ X(a, STATIC, SINGULAR, UINT32, health_update_interval, 12) \ -X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13) +X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13) \ +X(a, STATIC, SINGULAR, BOOL, device_telemetry_enabled, 14) #define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL #define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL @@ -910,7 +915,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 -#define meshtastic_ModuleConfig_TelemetryConfig_size 46 +#define meshtastic_ModuleConfig_TelemetryConfig_size 48 #define meshtastic_ModuleConfig_size 227 #define meshtastic_RemoteHardwarePin_size 21 diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h index 9650668a8..2df8686a3 100644 --- a/src/mesh/udp/UdpMulticastHandler.h +++ b/src/mesh/udp/UdpMulticastHandler.h @@ -50,10 +50,10 @@ class UdpMulticastHandler final LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength); #endif meshtastic_MeshPacket mp; - mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP; LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength); bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp); if (isPacketDecoded && router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) { + mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP; mp.pki_encrypted = false; mp.public_key.size = 0; memset(mp.public_key.bytes, 0, sizeof(mp.public_key.bytes)); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 78c101765..79ea7bc0c 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -104,6 +104,10 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta (config.security.admin_key[2].size == 32 && memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) { LOG_INFO("PKC admin payload with authorized sender key"); + auto remoteNode = nodeDB->getMeshNode(mp.from); + if (remoteNode && !remoteNode->is_favorite) { + remoteNode->is_favorite = true; + } } else { myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp); LOG_INFO("Received PKC admin payload, but the sender public key does not match the admin authorized key!"); @@ -1040,32 +1044,19 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.get_module_config_response.payload_variant.serial = moduleConfig.serial; break; case meshtastic_AdminMessage_ModuleConfigType_EXTNOTIF_CONFIG: -#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION LOG_INFO("Get module config: External Notification"); res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_external_notification_tag; res.get_module_config_response.payload_variant.external_notification = moduleConfig.external_notification; -#else - LOG_DEBUG("External Notification module excluded from build, skipping config"); -#endif break; case meshtastic_AdminMessage_ModuleConfigType_STOREFORWARD_CONFIG: -#if !MESHTASTIC_EXCLUDE_STOREFORWARD LOG_INFO("Get module config: Store & Forward"); res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_store_forward_tag; res.get_module_config_response.payload_variant.store_forward = moduleConfig.store_forward; -#else - LOG_DEBUG("Store & Forward module excluded from build, skipping config"); -#endif break; case meshtastic_AdminMessage_ModuleConfigType_RANGETEST_CONFIG: -#if !MESHTASTIC_EXCLUDE_RANGETEST LOG_INFO("Get module config: Range Test"); res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_range_test_tag; res.get_module_config_response.payload_variant.range_test = moduleConfig.range_test; -#else - LOG_DEBUG("Range Test module excluded from build, skipping config"); - // Don't set payload variant - will result in empty response -#endif break; case meshtastic_AdminMessage_ModuleConfigType_TELEMETRY_CONFIG: LOG_INFO("Get module config: Telemetry"); @@ -1078,13 +1069,9 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.get_module_config_response.payload_variant.canned_message = moduleConfig.canned_message; break; case meshtastic_AdminMessage_ModuleConfigType_AUDIO_CONFIG: -#if !MESHTASTIC_EXCLUDE_AUDIO LOG_INFO("Get module config: Audio"); res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_audio_tag; res.get_module_config_response.payload_variant.audio = moduleConfig.audio; -#else - LOG_DEBUG("Audio module excluded from build, skipping config"); -#endif break; case meshtastic_AdminMessage_ModuleConfigType_REMOTEHARDWARE_CONFIG: LOG_INFO("Get module config: Remote Hardware"); @@ -1097,31 +1084,19 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.get_module_config_response.payload_variant.neighbor_info = moduleConfig.neighbor_info; break; case meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG: -#if !(NO_EXT_GPIO || MESHTASTIC_EXCLUDE_DETECTIONSENSOR) LOG_INFO("Get module config: Detection Sensor"); res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_detection_sensor_tag; res.get_module_config_response.payload_variant.detection_sensor = moduleConfig.detection_sensor; -#else - LOG_DEBUG("Detection Sensor module excluded from build, skipping config"); -#endif break; case meshtastic_AdminMessage_ModuleConfigType_AMBIENTLIGHTING_CONFIG: -#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING LOG_INFO("Get module config: Ambient Lighting"); res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_ambient_lighting_tag; res.get_module_config_response.payload_variant.ambient_lighting = moduleConfig.ambient_lighting; -#else - LOG_DEBUG("Ambient Lighting module excluded from build, skipping config"); -#endif break; case meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG: -#if !MESHTASTIC_EXCLUDE_PAXCOUNTER LOG_INFO("Get module config: Paxcounter"); res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag; res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter; -#else - LOG_DEBUG("Paxcounter module excluded from build, skipping config"); -#endif break; } diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index e8c85235a..e9f52bb7d 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -404,14 +404,14 @@ bool CannedMessageModule::isUpEvent(const InputEvent *event) return event->inputEvent == INPUT_BROKER_UP || ((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER || runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) && - event->inputEvent == INPUT_BROKER_ALT_PRESS); + (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS)); } bool CannedMessageModule::isDownEvent(const InputEvent *event) { return event->inputEvent == INPUT_BROKER_DOWN || ((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER || runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) && - event->inputEvent == INPUT_BROKER_USER_PRESS); + (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS)); } bool CannedMessageModule::isSelectEvent(const InputEvent *event) { @@ -1560,10 +1560,17 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node; if (node) { if (node->is_favorite) { +#if defined(M5STACK_UNITC6L) + snprintf(entryText, sizeof(entryText), "* %s", node->user.short_name); + } else { + snprintf(entryText, sizeof(entryText), "%s", node->user.short_name); + } +#else snprintf(entryText, sizeof(entryText), "* %s", node->user.long_name); } else { snprintf(entryText, sizeof(entryText), "%s", node->user.long_name); } +#endif } } } @@ -1734,7 +1741,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st int yOffset = y + 10; #else display->setFont(FONT_MEDIUM); +#if defined(M5STACK_UNITC6L) + int yOffset = y; +#else int yOffset = y + 10; +#endif #endif // --- Delivery Status Message --- @@ -1759,13 +1770,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } display->drawString(display->getWidth() / 2 + x, yOffset, buffer); +#if defined(M5STACK_UNITC6L) + yOffset += lineCount * FONT_HEIGHT_MEDIUM - 5; // only 1 line gap, no extra padding +#else yOffset += lineCount * FONT_HEIGHT_MEDIUM; // only 1 line gap, no extra padding - +#endif #ifndef USE_EINK // --- SNR + RSSI Compact Line --- if (this->ack) { display->setFont(FONT_SMALL); +#if defined(M5STACK_UNITC6L) + snprintf(buffer, sizeof(buffer), "SNR: %.1f dB \nRSSI: %d", this->lastRxSnr, this->lastRxRssi); +#else snprintf(buffer, sizeof(buffer), "SNR: %.1f dB RSSI: %d", this->lastRxSnr, this->lastRxRssi); +#endif display->drawString(display->getWidth() / 2 + x, yOffset, buffer); } #endif diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 65139fb6c..abafce070 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -9,6 +9,7 @@ #include "input/RotaryEncoderInterruptImpl1.h" #include "input/SerialKeyboardImpl.h" #include "input/UpDownInterruptImpl1.h" +#include "input/i2cButton.h" #include "modules/SystemCommandsModule.h" #if HAS_TRACKBALL #include "input/TrackballInterruptImpl1.h" @@ -198,6 +199,9 @@ void setupModules() #endif cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl->init(); +#if defined(M5STACK_UNITC6L) + i2cButton = new i2cButtonThread("i2cButtonThread"); +#endif #ifdef INPUTBROKER_MATRIX_TYPE kbMatrixImpl = new KbMatrixImpl(); kbMatrixImpl->init(); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index ee95168c3..0eb8e9bdd 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -11,6 +11,12 @@ #include #include +#ifdef NIMBLE_TWO +#include "NimBLEAdvertising.h" +#include "NimBLEExtAdvertising.h" +#include "PowerStatus.h" +#endif + NimBLECharacteristic *fromNumCharacteristic; NimBLECharacteristic *BatteryCharacteristic; NimBLECharacteristic *logRadioCharacteristic; @@ -56,13 +62,18 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread { PhoneAPI::onNowHasData(fromRadioNum); - LOG_DEBUG("BLE notify fromNum"); + uint8_t cc = bleServer->getConnectedCount(); + LOG_DEBUG("BLE notify fromNum: %d connections: %d", fromRadioNum, cc); uint8_t val[4]; put_le32(val, fromRadioNum); fromNumCharacteristic->setValue(val, sizeof(val)); +#ifdef NIMBLE_TWO + fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE); +#else fromNumCharacteristic->notify(); +#endif } /// Check the current underlying physical link to see if the client is currently connected @@ -79,7 +90,12 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks { +#ifdef NIMBLE_TWO + virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) +#else virtual void onWrite(NimBLECharacteristic *pCharacteristic) + +#endif { auto val = pCharacteristic->getValue(); @@ -97,7 +113,11 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks { +#ifdef NIMBLE_TWO + virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) +#else virtual void onRead(NimBLECharacteristic *pCharacteristic) +#endif { int tries = 0; bluetoothPhoneAPI->phoneWants = true; @@ -107,9 +127,7 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks tries++; } std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); - std::string fromRadioByteString(bluetoothPhoneAPI->fromRadioBytes, - bluetoothPhoneAPI->fromRadioBytes + bluetoothPhoneAPI->numBytes); - pCharacteristic->setValue(fromRadioByteString); + pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes); if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload bluetoothPhoneAPI->setIntervalFromNow(0); @@ -121,7 +139,17 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks class NimbleBluetoothServerCallback : public NimBLEServerCallbacks { +#ifdef NIMBLE_TWO + public: + NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; } + + private: + NimbleBluetooth *ble; + + virtual uint32_t onPassKeyDisplay() +#else virtual uint32_t onPassKeyRequest() +#endif { uint32_t passkey = config.bluetooth.fixed_pin; @@ -170,7 +198,11 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks return passkey; } +#ifdef NIMBLE_TWO + virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo) +#else virtual void onAuthenticationComplete(ble_gap_conn_desc *desc) +#endif { LOG_INFO("BLE authentication complete"); @@ -185,9 +217,20 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks } } +#ifdef NIMBLE_TWO + virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) + { + LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str()); + } + + virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) + { + LOG_INFO("BLE disconnect reason: %d", reason); +#else virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc) { LOG_INFO("BLE disconnect"); +#endif meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); bluetoothStatus->updateStatus(&newStatus); @@ -200,6 +243,10 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks bluetoothPhoneAPI->numBytes = 0; bluetoothPhoneAPI->queue_size = 0; } +#ifdef NIMBLE_TWO + // Restart Advertising + ble->startAdvertising(); +#endif } }; @@ -251,7 +298,11 @@ int NimbleBluetooth::getRssi() if (bleServer && isConnected()) { auto service = bleServer->getServiceByUUID(MESH_SERVICE_UUID); uint16_t handle = service->getHandle(); +#ifdef NIMBLE_TWO + return NimBLEDevice::getClientByHandle(handle)->getRssi(); +#else return NimBLEDevice::getClientByID(handle)->getRssi(); +#endif } return 0; // FIXME figure out where to source this } @@ -273,8 +324,11 @@ void NimbleBluetooth::setup() NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); } bleServer = NimBLEDevice::createServer(); - +#ifdef NIMBLE_TWO + NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this); +#else NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(); +#endif bleServer->setCallbacks(serverCallbacks, true); setupService(); startAdvertising(); @@ -318,8 +372,11 @@ void NimbleBluetooth::setupService() NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic) (uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1); - +#ifdef NIMBLE_TWO + NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904(); +#else NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904); +#endif batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8); batteryLevelDescriptor->setNamespace(1); batteryLevelDescriptor->setUnit(0x27ad); @@ -329,11 +386,40 @@ void NimbleBluetooth::setupService() void NimbleBluetooth::startAdvertising() { +#ifdef NIMBLE_TWO + NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + NimBLEExtAdvertisement legacyAdvertising; + + legacyAdvertising.setLegacyAdvertising(true); + legacyAdvertising.setScannable(true); + legacyAdvertising.setConnectable(true); + legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN); + if (powerStatus->getHasBattery() == 1) { + legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f)); + } + legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID)); + legacyAdvertising.setMinInterval(500); + legacyAdvertising.setMaxInterval(1000); + + NimBLEExtAdvertisement legacyScanResponse; + legacyScanResponse.setLegacyAdvertising(true); + legacyScanResponse.setConnectable(true); + legacyScanResponse.setName(getDeviceName()); + + if (!pAdvertising->setInstanceData(0, legacyAdvertising)) { + LOG_ERROR("BLE failed to set legacyAdvertising"); + } else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) { + LOG_ERROR("BLE failed to set legacyScanResponse"); + } else if (!pAdvertising->start(0, 0, 0)) { + LOG_ERROR("BLE failed to start legacyAdvertising"); + } +#else NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->reset(); pAdvertising->addServiceUUID(MESH_SERVICE_UUID); pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service pAdvertising->start(0); +#endif } /// Given a level between 0-100, update the BLE attribute @@ -341,7 +427,11 @@ void updateBatteryLevel(uint8_t level) { if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) { BatteryCharacteristic->setValue(&level, 1); +#ifdef NIMBLE_TWO + BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE); +#else BatteryCharacteristic->notify(); +#endif } } @@ -356,7 +446,11 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length) if (!bleServer || !isConnected() || length > 512) { return; } +#ifdef NIMBLE_TWO + logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE); +#else logRadioCharacteristic->notify(logMessage, length, true); +#endif } void clearNVS() @@ -366,4 +460,4 @@ void clearNVS() ESP.restart(); #endif } -#endif +#endif \ No newline at end of file diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index 45602e088..899355b4d 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -12,10 +12,15 @@ class NimbleBluetooth : BluetoothApi bool isConnected(); int getRssi(); void sendLog(const uint8_t *logMessage, size_t length); +#if defined(NIMBLE_TWO) + void startAdvertising(); +#endif private: void setupService(); +#if !defined(NIMBLE_TWO) void startAdvertising(); +#endif }; void setBluetoothEnable(bool enable); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index f3954840d..a87391739 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -197,6 +197,8 @@ #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO #elif defined(T_LORA_PAGER) #define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER +#elif defined(M5STACK_UNITC6L) +#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L #endif // ----------------------------------------------------------------------------- diff --git a/variants/esp32c6/m5stack_unitc6l/pins_arduino.h b/variants/esp32c6/m5stack_unitc6l/pins_arduino.h new file mode 100644 index 000000000..5b169a2d4 --- /dev/null +++ b/variants/esp32c6/m5stack_unitc6l/pins_arduino.h @@ -0,0 +1,28 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x2886 +#define USB_PID 0x0048 + +static const uint8_t TX = 16; +static const uint8_t RX = 17; + +static const uint8_t SDA = 10; +static const uint8_t SCL = 8; + +// Default SPI will be mapped to Radio +static const uint8_t MISO = 22; +static const uint8_t SCK = 20; +static const uint8_t MOSI = 21; +static const uint8_t SS = 6; + +// #define SPI_MOSI (11) +// #define SPI_SCK (14) +// #define SPI_MISO (2) +// #define SPI_CS (13) + +// #define SDCARD_CS SPI_CS + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini new file mode 100644 index 000000000..da1c70c0a --- /dev/null +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -0,0 +1,35 @@ +[env:m5stack-unitc6l] +extends = esp32c6_base +board = esp32-c6-devkitc-1 +;OpenOCD flash method +;upload_protocol = esp-builtin +;Normal method +upload_protocol = esptool +;upload_port = /dev/ttyACM2 +build_unflags = + -D HAS_BLUETOOTH + -D MESHTASTIC_EXCLUDE_BLUETOOTH + -D HAS_WIFI +lib_deps = + ${esp32c6_base.lib_deps} + adafruit/Adafruit NeoPixel@^1.12.3 + h2zero/NimBLE-Arduino@^2.3.6 +build_flags = + ${esp32c6_base.build_flags} + -D M5STACK_UNITC6L + -I variants/esp32c6/m5stack_unitc6l + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 + -D HAS_BLUETOOTH=1 + -D MESHTASTIC_EXCLUDE_WEBSERVER + -D MESHTASTIC_EXCLUDE_MQTT + -DCONFIG_BT_NIMBLE_EXT_ADV=1 + -DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 + -D NIMBLE_TWO +monitor_speed=115200 +lib_ignore = + NonBlockingRTTTL + libpax +build_src_filter = + ${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l> \ No newline at end of file diff --git a/variants/esp32c6/m5stack_unitc6l/variant.cpp b/variants/esp32c6/m5stack_unitc6l/variant.cpp new file mode 100644 index 000000000..8e26b4ab7 --- /dev/null +++ b/variants/esp32c6/m5stack_unitc6l/variant.cpp @@ -0,0 +1,74 @@ +#include "driver/gpio.h" +#include +#include +// I2C device addr +#define PI4IO_M_ADDR 0x43 + +// PI4IO registers +#define PI4IO_REG_CHIP_RESET 0x01 +#define PI4IO_REG_IO_DIR 0x03 +#define PI4IO_REG_OUT_SET 0x05 +#define PI4IO_REG_OUT_H_IM 0x07 +#define PI4IO_REG_IN_DEF_STA 0x09 +#define PI4IO_REG_PULL_EN 0x0B +#define PI4IO_REG_PULL_SEL 0x0D +#define PI4IO_REG_IN_STA 0x0F +#define PI4IO_REG_INT_MASK 0x11 +#define PI4IO_REG_IRQ_STA 0x13 +// PI4IO + +#define setbit(x, y) x |= (0x01 << y) +#define clrbit(x, y) x &= ~(0x01 << y) +#define reversebit(x, y) x ^= (0x01 << y) +#define getbit(x, y) ((x) >> (y)&0x01) + +void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value) +{ + Wire.beginTransmission(addr); + Wire.write(reg); + Wire.endTransmission(); + Wire.requestFrom(addr, 1); + *value = Wire.read(); +} + +/*******************************************************************/ +void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value) +{ + Wire.beginTransmission(addr); + Wire.write(reg); + Wire.write(value); + Wire.endTransmission(); +} +/*******************************************************************/ +void c6l_init() +{ + // P7 LoRa Reset + // P6 RF Switch + // P5 LNA Enable + + printf("pi4io_init\n"); + uint8_t in_data; + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, 0xFF); + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, &in_data); + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11000000); // 0: input 1: output + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00111100); // 使用到的引脚关闭High-Impedance + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11000011); // pull up/down select, 0 down, 1 up + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11000011); // pull up/down enable, 0 disable, 1 enable + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 默认高电平, 按键按下触发中断 + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 中断使能 0 enable, 1 disable + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // 默认输出为0 + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // 读取IRQ_STA清除标志 + + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, &in_data); + setbit(in_data, 6); // HIGH + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, in_data); +} diff --git a/variants/esp32c6/m5stack_unitc6l/variant.h b/variants/esp32c6/m5stack_unitc6l/variant.h new file mode 100644 index 000000000..d973aa281 --- /dev/null +++ b/variants/esp32c6/m5stack_unitc6l/variant.h @@ -0,0 +1,52 @@ +void c6l_init(); + +#define HAS_GPS 1 +#define GPS_RX_PIN 4 +#define GPS_TX_PIN 5 + +#define I2C_SDA 10 +#define I2C_SCL 8 + +#define PIN_BUZZER 11 + +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 2 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use +#define ENABLE_AMBIENTLIGHTING // Turn on Ambient Lighting + +// #define BUTTON_PIN 9 +#define BUTTON_EXTENDER + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +// WaveShare Core1262-868M OK +// https://www.waveshare.com/wiki/Core1262-868M +#define USE_SX1262 + +#define LORA_MISO 22 +#define LORA_SCK 20 +#define LORA_MOSI 21 +#define LORA_CS 23 +#define LORA_RESET RADIOLIB_NC +#define LORA_DIO1 7 +#define LORA_BUSY 19 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_BUSY +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +#define USE_SPISSD1306 +#ifdef USE_SPISSD1306 +#define SSD1306_NSS 6 // CS +#define SSD1306_RS 18 // DC +#define SSD1306_RESET 15 +// #define OLED_DG 1 +#endif +#define SCREEN_TRANSITION_FRAMERATE 10 +#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h index abe0a772a..35cb99435 100644 --- a/variants/esp32s3/t-deck-pro/variant.h +++ b/variants/esp32s3/t-deck-pro/variant.h @@ -93,11 +93,10 @@ // Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface // code) -#define MODEM_POWER_EN 41 -#define MODEM_PWRKEY 40 -#define MODEM_RST 9 -#define MODEM_RI 7 -#define MODEM_DTR 8 -#define MODEM_RX 10 -#define MODEM_TX 11 - +#define MODEM_POWER_EN 41 +#define MODEM_PWRKEY 40 +#define MODEM_RST 9 +#define MODEM_RI 7 +#define MODEM_DTR 8 +#define MODEM_RX 10 +#define MODEM_TX 11 diff --git a/variants/esp32s3/tlora-pager/rfswitch.h b/variants/esp32s3/tlora-pager/rfswitch.h index 337346ec5..0fba5a305 100644 --- a/variants/esp32s3/tlora-pager/rfswitch.h +++ b/variants/esp32s3/tlora-pager/rfswitch.h @@ -4,12 +4,8 @@ static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11 static const Module::RfSwitchMode_t rfswitch_table[] = { // mode DIO5 DIO6 - {LR11x0::MODE_STBY, {LOW, LOW}}, - {LR11x0::MODE_RX, {LOW, HIGH}}, - {LR11x0::MODE_TX, {HIGH, LOW}}, - {LR11x0::MODE_TX_HP, {HIGH, LOW}}, - {LR11x0::MODE_TX_HF, {LOW, LOW}}, - {LR11x0::MODE_GNSS, {LOW, LOW}}, - {LR11x0::MODE_WIFI, {LOW, LOW}}, - END_OF_MODE_TABLE, + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {LOW, HIGH}}, + {LR11x0::MODE_TX, {HIGH, LOW}}, {LR11x0::MODE_TX_HP, {HIGH, LOW}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, }; \ No newline at end of file diff --git a/version.properties b/version.properties index cbf8265d9..cc0449a72 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 9 +build = 10