diff --git a/firmware-patch.diff b/firmware-patch.diff index bbb016c..f32dc87 100644 --- a/firmware-patch.diff +++ b/firmware-patch.diff @@ -1,3 +1,681 @@ +diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml +index f6c1fd80c..80f5c6855 100644 +--- a/.github/actions/setup-base/action.yml ++++ b/.github/actions/setup-base/action.yml +@@ -5,7 +5,7 @@ runs: + using: composite + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} +diff --git a/.github/workflows/build_debian_src.yml b/.github/workflows/build_debian_src.yml +index d36e7fea1..d7d26f0e8 100644 +--- a/.github/workflows/build_debian_src.yml ++++ b/.github/workflows/build_debian_src.yml +@@ -24,7 +24,7 @@ jobs: + runs-on: ubuntu-24.04 + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + submodules: recursive + path: meshtasticd +diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml +index 57c1e72c7..9ac84c23e 100644 +--- a/.github/workflows/build_firmware.yml ++++ b/.github/workflows/build_firmware.yml +@@ -22,7 +22,7 @@ jobs: + outputs: + artifact-id: ${{ steps.upload.outputs.artifact-id }} + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} +diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml +deleted file mode 100644 +index 6d5462c93..000000000 +--- a/.github/workflows/build_one_arch.yml ++++ /dev/null +@@ -1,176 +0,0 @@ +-name: Build One Arch +- +-on: +- workflow_dispatch: +- inputs: +- # trunk-ignore(checkov/CKV_GHA_7) +- arch: +- type: choice +- options: +- - esp32 +- - esp32s3 +- - esp32c3 +- - esp32c6 +- - nrf52840 +- - rp2040 +- - rp2350 +- - stm32 +- - native +- +-permissions: read-all +- +-env: +- INPUT_ARCH: ${{ github.event.inputs.arch }} +- +-jobs: +- setup: +- 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 $INPUT_ARCH --level extra) +- echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" +- echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT +- outputs: +- selected_arch: ${{ steps.jsonStep.outputs.selected_arch }} +- +- 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: +- if: ${{ github.event_name != 'workflow_dispatch' }} +- needs: [setup, version] +- strategy: +- fail-fast: false +- matrix: +- build: ${{ fromJson(needs.setup.outputs.selected_arch) }} +- uses: ./.github/workflows/build_firmware.yml +- with: +- version: ${{ needs.version.outputs.long }} +- pio_env: ${{ matrix.build.board }} +- platform: ${{ matrix.build.arch }} +- +- build-debian-src: +- if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} +- 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 +- +- 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] +- 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@v6 +- 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@v5 +- 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@v6 +- 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@v5 +- 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 }} +diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml +index 46362a629..e4b332a06 100644 +--- a/.github/workflows/build_one_target.yml ++++ b/.github/workflows/build_one_target.yml +@@ -15,7 +15,6 @@ on: + - rp2040 + - rp2350 + - stm32 +- - native + target: + type: string + required: false +@@ -42,10 +41,9 @@ jobs: + - rp2040 + - rp2350 + - stm32 +- + runs-on: ubuntu-24.04 + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x +@@ -60,13 +58,13 @@ jobs: + echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY + echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY + echo "Targets:" >> $GITHUB_STEP_SUMMARY +- echo $TARGETS >> $GITHUB_STEP_SUMMARY ++ echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY + + version: + if: ${{ inputs.target != '' }} + runs-on: ubuntu-latest + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + - name: Get release version string + run: | + echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT +@@ -87,25 +85,6 @@ jobs: + 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 +- + gather-artifacts: + permissions: + contents: write +@@ -114,7 +93,7 @@ jobs: + needs: [version, build] + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} +diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml +index 26a9cff18..8d19af894 100644 +--- a/.github/workflows/docker_build.yml ++++ b/.github/workflows/docker_build.yml +@@ -47,7 +47,7 @@ jobs: + runs-on: ${{ inputs.runs-on }} + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} +diff --git a/.github/workflows/docker_manifest.yml b/.github/workflows/docker_manifest.yml +index 20b9ceee6..396ddb68e 100644 +--- a/.github/workflows/docker_manifest.yml ++++ b/.github/workflows/docker_manifest.yml +@@ -83,7 +83,7 @@ jobs: + runs-on: ubuntu-24.04 + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} +diff --git a/.github/workflows/hook_copr.yml b/.github/workflows/hook_copr.yml +index 2204cc02c..eb4ebc57b 100644 +--- a/.github/workflows/hook_copr.yml ++++ b/.github/workflows/hook_copr.yml +@@ -19,7 +19,7 @@ jobs: + runs-on: ubuntu-24.04 + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + submodules: recursive + ref: ${{ github.ref }} +diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml +index 7ea033d55..38373a2fc 100644 +--- a/.github/workflows/main_matrix.yml ++++ b/.github/workflows/main_matrix.yml +@@ -35,7 +35,7 @@ jobs: + - check + runs-on: ubuntu-24.04 + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x +@@ -59,7 +59,7 @@ jobs: + version: + runs-on: ubuntu-latest + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + - name: Get release version string + run: | + echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT +@@ -81,7 +81,7 @@ jobs: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }} + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + - name: Build base + id: base + uses: ./.github/actions/setup-base +@@ -163,7 +163,7 @@ jobs: + needs: [version, build] + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} +@@ -242,7 +242,7 @@ jobs: + - package-pio-deps-native-tft + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 +@@ -311,7 +311,7 @@ jobs: + needs: [release-artifacts, version] + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 +@@ -366,7 +366,7 @@ jobs: + esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 +diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml +index 6d69258c9..154b230c7 100644 +--- a/.github/workflows/merge_queue.yml ++++ b/.github/workflows/merge_queue.yml +@@ -17,7 +17,7 @@ jobs: + - check + runs-on: ubuntu-24.04 + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x +@@ -40,7 +40,7 @@ jobs: + version: + runs-on: ubuntu-latest + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + - name: Get release version string + run: | + echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT +@@ -62,7 +62,7 @@ jobs: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'workflow_dispatch' }} + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + - name: Build base + id: base + uses: ./.github/actions/setup-base +@@ -142,7 +142,7 @@ jobs: + needs: [version, build] + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} +@@ -221,7 +221,7 @@ jobs: + - package-pio-deps-native-tft + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 +@@ -290,7 +290,7 @@ jobs: + needs: [release-artifacts, version] + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 +@@ -345,7 +345,7 @@ jobs: + esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 +diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml +index f26073ec4..045e94895 100644 +--- a/.github/workflows/nightly.yml ++++ b/.github/workflows/nightly.yml +@@ -14,7 +14,7 @@ jobs: + + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Trunk Check + uses: trunk-io/trunk-action@v1 +@@ -31,7 +31,7 @@ jobs: + pull-requests: write # For trunk to create PRs + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Trunk Upgrade + uses: trunk-io/trunk-action/upgrade@v1 +diff --git a/.github/workflows/package_obs.yml b/.github/workflows/package_obs.yml +index b8a829d9a..2b202ed95 100644 +--- a/.github/workflows/package_obs.yml ++++ b/.github/workflows/package_obs.yml +@@ -34,7 +34,7 @@ jobs: + needs: build-debian-src + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + submodules: recursive + path: meshtasticd +diff --git a/.github/workflows/package_pio_deps.yml b/.github/workflows/package_pio_deps.yml +index c52dfe348..cb10a79f3 100644 +--- a/.github/workflows/package_pio_deps.yml ++++ b/.github/workflows/package_pio_deps.yml +@@ -24,7 +24,7 @@ jobs: + runs-on: ubuntu-24.04 + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} +diff --git a/.github/workflows/package_ppa.yml b/.github/workflows/package_ppa.yml +index 2d6c257e6..2e3278041 100644 +--- a/.github/workflows/package_ppa.yml ++++ b/.github/workflows/package_ppa.yml +@@ -32,7 +32,7 @@ jobs: + needs: build-debian-src + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + submodules: recursive + path: meshtasticd +diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml +index c3a964e04..048186538 100644 +--- a/.github/workflows/pr_tests.yml ++++ b/.github/workflows/pr_tests.yml +@@ -40,7 +40,7 @@ jobs: + checks: write + pull-requests: write + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + with: + submodules: recursive + +diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml +index 4e5a48dfe..f21b13ee1 100644 +--- a/.github/workflows/release_channels.yml ++++ b/.github/workflows/release_channels.yml +@@ -60,7 +60,7 @@ jobs: + shell: bash + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + # Always use master branch for version bumps + ref: master +diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml +index dfb828bf6..d044f9038 100644 +--- a/.github/workflows/sec_sast_semgrep_cron.yml ++++ b/.github/workflows/sec_sast_semgrep_cron.yml +@@ -21,7 +21,7 @@ jobs: + steps: + # step 1 + - name: clone application source code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + # step 2 + - name: full scan +diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml +index e93b2ae8b..e9b4108a1 100644 +--- a/.github/workflows/sec_sast_semgrep_pull.yml ++++ b/.github/workflows/sec_sast_semgrep_pull.yml +@@ -13,7 +13,7 @@ jobs: + steps: + # step 1 + - name: clone application source code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + fetch-depth: 0 + +diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml +index 591d52bd0..a2328022e 100644 +--- a/.github/workflows/test_native.yml ++++ b/.github/workflows/test_native.yml +@@ -14,7 +14,7 @@ jobs: + name: Native Simulator Tests + runs-on: ubuntu-latest + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} +@@ -70,7 +70,7 @@ jobs: + name: Native PlatformIO Tests + runs-on: ubuntu-latest + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} +@@ -127,7 +127,7 @@ jobs: + - platformio-tests + if: always() + steps: +- - uses: actions/checkout@v5 ++ - uses: actions/checkout@v6 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} +diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml +index 1ec435512..4a97853e2 100644 +--- a/.github/workflows/tests.yml ++++ b/.github/workflows/tests.yml +@@ -20,7 +20,7 @@ jobs: + runs-on: test-runner + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + # - uses: actions/setup-python@v5 + # with: +diff --git a/.github/workflows/trunk_annotate_pr.yml b/.github/workflows/trunk_annotate_pr.yml +index 23dcf8d09..59ab25c28 100644 +--- a/.github/workflows/trunk_annotate_pr.yml ++++ b/.github/workflows/trunk_annotate_pr.yml +@@ -18,7 +18,7 @@ jobs: + + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Trunk Check + uses: trunk-io/trunk-action@v1 +diff --git a/.github/workflows/trunk_check.yml b/.github/workflows/trunk_check.yml +index 41731d491..874374fe0 100644 +--- a/.github/workflows/trunk_check.yml ++++ b/.github/workflows/trunk_check.yml +@@ -16,7 +16,7 @@ jobs: + + steps: + - name: Checkout +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + + - name: Trunk Check + uses: trunk-io/trunk-action@v1 +diff --git a/.github/workflows/trunk_format_pr.yml b/.github/workflows/trunk_format_pr.yml +index 51082fc5f..8fa0cc1eb 100644 +--- a/.github/workflows/trunk_format_pr.yml ++++ b/.github/workflows/trunk_format_pr.yml +@@ -15,7 +15,7 @@ jobs: + pull-requests: write + steps: + - name: Checkout repository +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} +diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml +index c06e06b0a..af0557fda 100644 +--- a/.github/workflows/update_protobufs.yml ++++ b/.github/workflows/update_protobufs.yml +@@ -11,7 +11,7 @@ jobs: + pull-requests: write + steps: + - name: Checkout code +- uses: actions/checkout@v5 ++ uses: actions/checkout@v6 + with: + submodules: true + diff --git a/.gitignore b/.gitignore index cc742c6c1..545b0923a 100644 --- a/.gitignore @@ -15,12 +693,83 @@ index cc742c6c1..545b0923a 100644 +# Ignore Python vendor directory +pyvendor \ No newline at end of file +diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml +index 1fd8790f2..ccb426745 100644 +--- a/.trunk/trunk.yaml ++++ b/.trunk/trunk.yaml +@@ -4,31 +4,31 @@ cli: + plugins: + sources: + - id: trunk +- ref: v1.7.3 ++ ref: v1.7.4 + uri: https://github.com/trunk-io/plugins + lint: + enabled: +- - checkov@3.2.492 +- - renovate@42.5.4 ++ - checkov@3.2.495 ++ - renovate@42.24.1 + - prettier@3.6.2 +- - trufflehog@3.90.13 ++ - trufflehog@3.91.1 + - yamllint@1.37.1 +- - bandit@1.8.6 ++ - bandit@1.9.2 + - trivy@0.67.2 + - taplo@0.10.0 +- - ruff@0.14.4 ++ - ruff@0.14.6 + - isort@7.0.0 +- - markdownlint@0.45.0 ++ - markdownlint@0.46.0 + - oxipng@9.1.5 + - svgo@4.0.0 +- - actionlint@1.7.8 ++ - actionlint@1.7.9 + - flake8@7.3.0 + - hadolint@2.14.0 + - shfmt@3.6.0 + - shellcheck@0.11.0 + - black@25.11.0 + - git-diff-check +- - gitleaks@8.29.0 ++ - gitleaks@8.30.0 + - clang-format@16.0.3 + ignore: + - linters: [ALL] +diff --git a/arch/nrf52/nrf52840.ini b/arch/nrf52/nrf52840.ini +index 5e846b3b7..e13443152 100644 +--- a/arch/nrf52/nrf52840.ini ++++ b/arch/nrf52/nrf52840.ini +@@ -8,7 +8,7 @@ lib_deps = + ${environmental_base.lib_deps} + ${environmental_extra.lib_deps} + # renovate: datasource=git-refs depName=Kongduino-Adafruit_nRFCrypto packageName=https://github.com/Kongduino/Adafruit_nRFCrypto gitBranch=master +- https://github.com/Kongduino/Adafruit_nRFCrypto/archive/5f838d2709461a2c981f642917aa50254a25c14c.zip ++ https://github.com/Kongduino/Adafruit_nRFCrypto/archive/8cde7189b5ead9dcd49f72601b43b969c0bbc06e.zip + + ; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board. + +diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini +index 7732533c9..1a9fd10ce 100644 +--- a/arch/stm32/stm32.ini ++++ b/arch/stm32/stm32.ini +@@ -2,7 +2,7 @@ + extends = arduino_base + platform = + # renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32 +- platformio/ststm32@19.3.0 ++ platformio/ststm32@19.4.0 + platform_packages = + # TODO renovate + platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip diff --git a/bin/mpm_pio.py b/bin/mpm_pio.py new file mode 100644 -index 000000000..c06961ab9 +index 000000000..9c000d6e6 --- /dev/null +++ b/bin/mpm_pio.py -@@ -0,0 +1,63 @@ +@@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +Mesh Plugin Manager (MPM) - PlatformIO build integration shim. @@ -56,6 +805,12 @@ index 000000000..c06961ab9 +if os.path.isdir(_pyvendor) and _pyvendor not in sys.path: + sys.path.insert(0, _pyvendor) + ++# Add pyvendor to PYTHONPATH so subprocesses (like nanopb_generator) can find modules ++current_pythonpath = os.environ.get("PYTHONPATH", "") ++if _pyvendor not in current_pythonpath: ++ os.environ["PYTHONPATH"] = f"{_pyvendor}:{current_pythonpath}" if current_pythonpath else _pyvendor ++ print(f"MPM: Added {_pyvendor} to PYTHONPATH") ++ +# Add pyvendor/bin to PATH so nanopb_generator and other console scripts can be found +_pyvendor_bin = os.path.join(_pyvendor, "bin") +if os.path.isdir(_pyvendor_bin): @@ -84,8 +839,129 @@ index 000000000..c06961ab9 + + main() + +diff --git a/boards/ThinkNode-M3.json b/boards/ThinkNode-M3.json +new file mode 100644 +index 000000000..ff21e046a +--- /dev/null ++++ b/boards/ThinkNode-M3.json +@@ -0,0 +1,53 @@ ++{ ++ "build": { ++ "arduino": { ++ "ldscript": "nrf52840_s140_v6.ld" ++ }, ++ "core": "nRF5", ++ "cpu": "cortex-m4", ++ "extra_flags": "-DNRF52840_XXAA", ++ "f_cpu": "64000000L", ++ "hwids": [ ++ ["0x239A", "0x4405"], ++ ["0x239A", "0x0029"], ++ ["0x239A", "0x002A"] ++ ], ++ "usb_product": "elecrow_eink", ++ "mcu": "nrf52840", ++ "variant": "ELECROW-ThinkNode-M3", ++ "variants_dir": "variants", ++ "bsp": { ++ "name": "adafruit" ++ }, ++ "softdevice": { ++ "sd_flags": "-DS140", ++ "sd_name": "s140", ++ "sd_version": "6.1.1", ++ "sd_fwid": "0x00B6" ++ }, ++ "bootloader": { ++ "settings_addr": "0xFF000" ++ } ++ }, ++ "connectivity": ["bluetooth"], ++ "debug": { ++ "jlink_device": "nRF52840_xxAA", ++ "onboard_tools": ["jlink"], ++ "svd_path": "nrf52840.svd", ++ "openocd_target": "nrf52840-mdk-rs" ++ }, ++ "frameworks": ["arduino"], ++ "name": "elecrow nrf", ++ "upload": { ++ "maximum_ram_size": 248832, ++ "maximum_size": 815104, ++ "speed": 115200, ++ "protocol": "nrfutil", ++ "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"], ++ "use_1200bps_touch": true, ++ "require_upload_port": true, ++ "wait_for_upload_port": true ++ }, ++ "url": "", ++ "vendor": "ELECROW" ++} +diff --git a/boards/muzi-base.json b/boards/muzi-base.json +new file mode 100644 +index 000000000..5f65c0dc8 +--- /dev/null ++++ b/boards/muzi-base.json +@@ -0,0 +1,56 @@ ++{ ++ "build": { ++ "arduino": { ++ "ldscript": "nrf52840_s140_v6.ld" ++ }, ++ "core": "nRF5", ++ "cpu": "cortex-m4", ++ "extra_flags": "-DARDUINO_NRF52840_MUZI_BASE -DNRF52840_XXAA", ++ "f_cpu": "64000000L", ++ "hwids": [["0x239A", "0xcafe"]], ++ "mcu": "nrf52840", ++ "variant": "muzi-base", ++ "variants_dir": "variants", ++ "bsp": { ++ "name": "adafruit" ++ }, ++ "softdevice": { ++ "sd_flags": "-DS140", ++ "sd_name": "s140", ++ "sd_version": "6.1.1", ++ "sd_fwid": "0x00B6" ++ }, ++ "bootloader": { ++ "settings_addr": "0xFF000" ++ } ++ }, ++ "connectivity": ["bluetooth"], ++ "debug": { ++ "jlink_device": "nRF52840_xxAA", ++ "onboard_tools": ["jlink"], ++ "svd_path": "nrf52840.svd", ++ "openocd_target": "nrf52840-mdk-rs" ++ }, ++ "frameworks": ["arduino"], ++ "name": "Muzi Base", ++ "url": "https://muzi.works/", ++ "vendor": "MuziWorks", ++ "upload": { ++ "maximum_ram_size": 248832, ++ "maximum_size": 815104, ++ "speed": 115200, ++ "protocol": "nrfutil", ++ "protocols": [ ++ "jlink", ++ "nrfjprog", ++ "nrfutil", ++ "blackmagic", ++ "cmsis-dap", ++ "mbed", ++ "stlink" ++ ], ++ "use_1200bps_touch": true, ++ "require_upload_port": true, ++ "wait_for_upload_port": true ++ } ++} diff --git a/platformio.ini b/platformio.ini -index d6ff155e4..b92615db3 100644 +index d6ff155e4..c775b2aab 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,7 +14,9 @@ description = Meshtastic @@ -107,6 +983,32 @@ index d6ff155e4..b92615db3 100644 -DUSE_THREAD_NAMES -DTINYGPS_OPTION_NO_CUSTOM_FIELDS -DPB_ENABLE_MALLOC=1 +@@ -90,7 +93,7 @@ framework = arduino + lib_deps = + ${env.lib_deps} + # renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL +- end2endzone/NonBlockingRTTTL@1.3.0 ++ end2endzone/NonBlockingRTTTL@1.4.0 + build_flags = ${env.build_flags} -Os + build_src_filter = ${env.build_src_filter} - - + +@@ -169,7 +172,7 @@ lib_deps = + # renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master + https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip + # renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226 +- robtillaart/INA226@0.6.4 ++ robtillaart/INA226@0.6.5 + # renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library + sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2 + # renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library +@@ -213,6 +216,6 @@ lib_deps = + # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master + https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip + # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core +- sensirion/Sensirion Core@0.7.1 ++ sensirion/Sensirion Core@0.7.2 + # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x + sensirion/Sensirion I2C SCD4x@1.1.0 diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 000000000..84e8e8fdf @@ -260,6 +1162,810 @@ index 000000000..e45155ab7 @@ -0,0 +1,2 @@ +../mpm + +diff --git a/src/Power.cpp b/src/Power.cpp +index fa8661d01..7bb8896ce 100644 +--- a/src/Power.cpp ++++ b/src/Power.cpp +@@ -278,6 +278,11 @@ class AnalogBatteryLevel : public HasBatteryLevel + break; + } + } ++#if defined(BATTERY_CHARGING_INV) ++ // bit of trickery to show 99% up until the charge finishes ++ if (!digitalRead(BATTERY_CHARGING_INV) && battery_SOC > 99) ++ battery_SOC = 99; ++#endif + return clamp((int)(battery_SOC), 0, 100); + } + +@@ -455,6 +460,8 @@ class AnalogBatteryLevel : public HasBatteryLevel + } + // if it's not HIGH - check the battery + #endif ++#elif defined(MUZI_BASE) ++ return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; + #endif + return getBattVoltage() > chargingVolt; + } +@@ -470,6 +477,8 @@ class AnalogBatteryLevel : public HasBatteryLevel + #endif + #ifdef EXT_CHRG_DETECT + return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value; ++#elif defined(BATTERY_CHARGING_INV) ++ return !digitalRead(BATTERY_CHARGING_INV); + #else + #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) + if (hasINA()) { +@@ -698,11 +707,18 @@ bool Power::setup() + []() { + power->setIntervalFromNow(0); + runASAP = true; +- BaseType_t higherWake = 0; + }, + CHANGE); + #endif +- ++#ifdef BATTERY_CHARGING_INV ++ attachInterrupt( ++ BATTERY_CHARGING_INV, ++ []() { ++ power->setIntervalFromNow(0); ++ runASAP = true; ++ }, ++ CHANGE); ++#endif + enabled = found; + low_voltage_counter = 0; + +@@ -759,6 +775,8 @@ void Power::shutdown() + if (screen) { + #ifdef T_DECK_PRO + screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button ++#elif defined(USE_EINK) ++ screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen + #else + screen->showSimpleBanner("Shutting Down...", 0); // stays on screen + #endif +@@ -1435,7 +1453,7 @@ class LipoCharger : public HasBatteryLevel + /** + * return true if there is an external power source detected + */ +- virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; } ++ virtual bool isVbusIn() override { return PPM->isVbusIn(); } + + /** + * return true if the battery is currently charging +diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp +index 322b877ff..67b680233 100644 +--- a/src/PowerFSM.cpp ++++ b/src/PowerFSM.cpp +@@ -57,21 +57,21 @@ static bool isPowered() + + static void sdsEnter() + { +- LOG_DEBUG("State: SDS"); ++ LOG_POWERFSM("State: SDS"); + // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw + doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, false); + } + + static void lowBattSDSEnter() + { +- LOG_DEBUG("State: Lower batt SDS"); ++ LOG_POWERFSM("State: Lower batt SDS"); + doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, true); + } + extern Power *power; + + static void shutdownEnter() + { +- LOG_DEBUG("State: SHUTDOWN"); ++ LOG_POWERFSM("State: SHUTDOWN"); + shutdownAtMsec = millis(); + } + +@@ -81,7 +81,7 @@ static uint32_t secsSlept; + + static void lsEnter() + { +- LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs); ++ LOG_POWERFSM("lsEnter begin, ls_secs=%u", config.power.ls_secs); + if (screen) + screen->setOn(false); + secsSlept = 0; // How long have we been sleeping this time +@@ -155,12 +155,12 @@ static void lsIdle() + + static void lsExit() + { +- LOG_INFO("Exit state: LS"); ++ LOG_POWERFSM("State: lsExit"); + } + + static void nbEnter() + { +- LOG_DEBUG("State: NB"); ++ LOG_POWERFSM("State: nbEnter"); + if (screen) + screen->setOn(false); + #ifdef ARCH_ESP32 +@@ -173,6 +173,7 @@ static void nbEnter() + + static void darkEnter() + { ++ LOG_POWERFSM("State: darkEnter"); + setBluetoothEnable(true); + if (screen) + screen->setOn(false); +@@ -180,7 +181,7 @@ static void darkEnter() + + static void serialEnter() + { +- LOG_DEBUG("State: SERIAL"); ++ LOG_POWERFSM("State: serialEnter"); + setBluetoothEnable(false); + if (screen) { + screen->setOn(true); +@@ -189,13 +190,14 @@ static void serialEnter() + + static void serialExit() + { ++ LOG_POWERFSM("State: serialExit"); + // Turn bluetooth back on when we leave serial stream API + setBluetoothEnable(true); + } + + static void powerEnter() + { +- // LOG_DEBUG("State: POWER"); ++ LOG_POWERFSM("State: powerEnter"); + if (!isPowered()) { + // If we got here, we are in the wrong state - we should be in powered, let that state handle things + LOG_INFO("Loss of power in Powered"); +@@ -210,6 +212,7 @@ static void powerEnter() + + static void powerIdle() + { ++ // LOG_POWERFSM("State: powerIdle"); // very chatty + if (!isPowered()) { + // If we got here, we are in the wrong state + LOG_INFO("Loss of power in Powered"); +@@ -219,14 +222,13 @@ static void powerIdle() + + static void powerExit() + { +- if (screen) +- screen->setOn(true); ++ LOG_POWERFSM("State: powerExit"); + setBluetoothEnable(true); + } + + static void onEnter() + { +- LOG_DEBUG("State: ON"); ++ LOG_POWERFSM("State: onEnter"); + if (screen) + screen->setOn(true); + setBluetoothEnable(true); +@@ -234,6 +236,7 @@ static void onEnter() + + static void onIdle() + { ++ LOG_POWERFSM("State: onIdle"); + if (isPowered()) { + // If we got here, we are in the wrong state - we should be in powered, let that state handle things + powerFSM.trigger(EVENT_POWER_CONNECTED); +@@ -242,7 +245,7 @@ static void onIdle() + + static void bootEnter() + { +- LOG_DEBUG("State: BOOT"); ++ LOG_POWERFSM("State: bootEnter"); + } + + State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN"); +@@ -319,11 +322,6 @@ void PowerFSM_setup() + // if any packet destined for phone arrives, turn on bluetooth at least + powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); + +- // Removed 2.7: we don't show the nodes individually for every node on the screen anymore +- // powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); +- // powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); +- // powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); +- + // Show the received text message + powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); + powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); +diff --git a/src/PowerFSM.h b/src/PowerFSM.h +index 6330a5fc6..182ac082a 100644 +--- a/src/PowerFSM.h ++++ b/src/PowerFSM.h +@@ -2,6 +2,12 @@ + + #include "configuration.h" + ++#ifdef PowerFSMDebug ++#define LOG_POWERFSM(...) LOG_DEBUG(__VA_ARGS__) ++#else ++#define LOG_POWERFSM(...) ++#endif ++ + // See sw-design.md for documentation + + #define EVENT_PRESS 1 +diff --git a/src/configuration.h b/src/configuration.h +index 8ec3b2211..d30280d8b 100644 +--- a/src/configuration.h ++++ b/src/configuration.h +@@ -250,8 +250,9 @@ along with this program. If not, see . + // Touchscreen + // ----------------------------------------------------------------------------- + #define FT6336U_ADDR 0x48 +-#define CST328_ADDR 0x1A ++#define CST328_ADDR 0x1A // same address as CST226SE + #define CHSC6X_ADDR 0x2E ++#define CST226SE_ADDR_ALT 0x5A + + // ----------------------------------------------------------------------------- + // RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected) +@@ -396,6 +397,13 @@ along with this program. If not, see . + #define HAS_RGB_LED + #endif + ++#ifndef LED_STATE_OFF ++#define LED_STATE_OFF 0 ++#endif ++#ifndef LED_STATE_ON ++#define LED_STATE_ON 1 ++#endif ++ + // default mapping of pins + #if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN) + #define ALT_BUTTON_PIN PIN_BUTTON2 +diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h +index 55980face..cced980a6 100644 +--- a/src/detect/ScanI2C.h ++++ b/src/detect/ScanI2C.h +@@ -85,7 +85,8 @@ class ScanI2C + DRV2605, + BH1750, + DA217, +- CHSC6X ++ CHSC6X, ++ CST226SE + } DeviceType; + + // typedef uint8_t DeviceAddress; +diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp +index 167728ad3..db269ac64 100644 +--- a/src/detect/ScanI2CTwoWire.cpp ++++ b/src/detect/ScanI2CTwoWire.cpp +@@ -499,7 +499,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) + SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); + SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address); + SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address); +- SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address); ++ case CST328_ADDR: ++ // Do we have the CST328 or the CST226SE ++ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1); ++ if (registerValue == 0xA9) { ++ type = CST226SE; ++ logFoundDevice("CST226SE", (uint8_t)addr.address); ++ } else { ++ type = CST328; ++ logFoundDevice("CST328", (uint8_t)addr.address); ++ } ++ break; ++ + SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address); + case LTR553ALS_ADDR: + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register +@@ -528,8 +539,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) + #endif + + case MLX90614_ADDR_DEF: +- registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1); +- if (registerValue == 0x5a) { ++ // Do we have the MLX90614 or the MPR121KB or the CST226SE ++ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x06), 1); ++ if (registerValue == 0xAB) { ++ type = CST226SE; ++ logFoundDevice("CST226SE", (uint8_t)addr.address); ++ } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1) == 0x5a) { + type = MLX90614; + logFoundDevice("MLX90614", (uint8_t)addr.address); + } else { +@@ -547,6 +562,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) + case ICM20948_ADDR: // same as BMX160_ADDR + case ICM20948_ADDR_ALT: // same as MPU6050_ADDR + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); ++#ifdef HAS_ICM20948 ++ type = ICM20948; ++ logFoundDevice("ICM20948", (uint8_t)addr.address); ++ break; ++#endif + if (registerValue == 0xEA) { + type = ICM20948; + logFoundDevice("ICM20948", (uint8_t)addr.address); +diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp +index 0404ae5f8..a61a71dde 100644 +--- a/src/gps/GPS.cpp ++++ b/src/gps/GPS.cpp +@@ -38,14 +38,16 @@ template std::size_t array_count(const T (&)[N]) + return N; + } + +-#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) +-#if defined(GPS_SERIAL_PORT) +-HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT; +-#else +-HardwareSerial *GPS::_serial_gps = &Serial1; ++#ifndef GPS_SERIAL_PORT ++#define GPS_SERIAL_PORT Serial1 + #endif ++ ++#if defined(ARCH_NRF52) ++Uart *GPS::_serial_gps = &GPS_SERIAL_PORT; ++#elif defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) ++HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT; + #elif defined(ARCH_RP2040) +-SerialUART *GPS::_serial_gps = &Serial1; ++SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT; + #else + HardwareSerial *GPS::_serial_gps = nullptr; + #endif +@@ -1525,10 +1527,7 @@ GPS *GPS::createGps() + int8_t _rx_gpio = config.position.rx_gpio; + int8_t _tx_gpio = config.position.tx_gpio; + int8_t _en_gpio = config.position.gps_en_gpio; +-#if HAS_GPS && !defined(ARCH_ESP32) +- _rx_gpio = 1; // We only specify GPS serial ports on ESP32. Otherwise, these are just flags. +- _tx_gpio = 1; +-#endif ++ + #if defined(GPS_RX_PIN) + if (!_rx_gpio) + _rx_gpio = GPS_RX_PIN; +@@ -1602,16 +1601,28 @@ GPS *GPS::createGps() + _serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256 + #endif + +-// ESP32 has a special set of parameters vs other arduino ports +-#if defined(ARCH_ESP32) + LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio); + LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio); ++ ++// ESP32 has a special set of parameters vs other arduino ports ++#if defined(ARCH_ESP32) + _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio); + #elif defined(ARCH_RP2040) ++ _serial_gps->setPinout(new_gps->tx_gpio, new_gps->rx_gpio); + _serial_gps->setFIFOSize(256); + _serial_gps->begin(GPS_BAUDRATE); +-#else ++#elif defined(ARCH_NRF52) ++ _serial_gps->setPins(new_gps->rx_gpio, new_gps->tx_gpio); + _serial_gps->begin(GPS_BAUDRATE); ++#elif defined(ARCH_STM32WL) ++ _serial_gps->setTx(new_gps->tx_gpio); ++ _serial_gps->setRx(new_gps->rx_gpio); ++ _serial_gps->begin(GPS_BAUDRATE); ++#elif defined(ARCH_PORTDUINO) ++ // Portduino can't set the GPS pins directly. ++ _serial_gps->begin(GPS_BAUDRATE); ++#else ++#error Unsupported architecture! + #endif + } + return new_gps; +diff --git a/src/gps/GPS.h b/src/gps/GPS.h +index 8ba1ce0a6..59cee7113 100644 +--- a/src/gps/GPS.h ++++ b/src/gps/GPS.h +@@ -194,6 +194,8 @@ class GPS : private concurrency::OSThread + /** If !NULL we will use this serial port to construct our GPS */ + #if defined(ARCH_RP2040) + static SerialUART *_serial_gps; ++#elif defined(ARCH_NRF52) ++ static Uart *_serial_gps; + #else + static HardwareSerial *_serial_gps; + #endif +diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp +index 692f3c2d2..1122f0a51 100644 +--- a/src/gps/RTC.cpp ++++ b/src/gps/RTC.cpp +@@ -112,7 +112,11 @@ RTCSetResult readFromRTC() + #elif defined(RX8130CE_RTC) + if (rtc_found.address == RX8130CE_RTC) { + uint32_t now = millis(); ++#ifdef MUZI_BASE ++ ArtronShop_RX8130CE rtc(&Wire1); ++#else + ArtronShop_RX8130CE rtc(&Wire); ++#endif + tm t; + if (rtc.getTime(&t)) { + tv.tv_sec = gm_mktime(&t); +@@ -245,7 +249,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd + } + #elif defined(RX8130CE_RTC) + if (rtc_found.address == RX8130CE_RTC) { ++#ifdef MUZI_BASE ++ ArtronShop_RX8130CE rtc(&Wire1); ++#else + ArtronShop_RX8130CE rtc(&Wire); ++#endif + tm *t = gmtime(&tv->tv_sec); + if (rtc.setTime(*t)) { + LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, +diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp +index 86599d5b3..0864e5ae1 100644 +--- a/src/graphics/Screen.cpp ++++ b/src/graphics/Screen.cpp +@@ -324,7 +324,7 @@ static int8_t prevFrame = -1; + // Combined dynamic node list frame cycling through LastHeard, HopSignal, and Distance modes + // Uses a single frame and changes data every few seconds (E-Ink variant is separate) + +-#if defined(ESP_PLATFORM) && defined(USE_ST7789) ++#if defined(ESP_PLATFORM) && (defined(USE_ST7789) || defined(USE_ST7796)) + SPIClass SPI1(HSPI); + #endif + +@@ -356,7 +356,19 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O + #else + dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); + #endif ++#if defined(USE_ST7796) ++#ifdef ESP_PLATFORM ++ dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7796_SDA, ++ ST7796_MISO, ST7796_SCK, TFT_SPI_FREQUENCY); ++#else ++ dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); ++#endif ++#endif ++#if defined(USE_ST7789) + static_cast(dispdev)->setRGB(TFT_MESH); ++#elif defined(USE_ST7796) ++ static_cast(dispdev)->setRGB(TFT_MESH); ++#endif + #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); +@@ -435,6 +447,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) + PMU->enablePowerOutput(XPOWERS_ALDO2); + #endif + ++#if defined(MUZI_BASE) ++ dispdev->init(); ++ dispdev->setBrightness(brightness); ++ dispdev->flipScreenVertically(); ++ dispdev->resetDisplay(); ++ digitalWrite(SCREEN_12V_ENABLE, HIGH); ++ delay(100); ++#endif + #if !ARCH_PORTDUINO + dispdev->displayOn(); + #endif +@@ -466,6 +486,15 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) + pinMode(VTFT_LEDA, OUTPUT); + digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON); + #endif ++#endif ++#ifdef USE_ST7796 ++ ui->init(); ++#ifdef ESP_PLATFORM ++ analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT); ++#else ++ pinMode(VTFT_LEDA, OUTPUT); ++ digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON); ++#endif + #endif + enabled = true; + setInterval(0); // Draw ASAP +@@ -484,6 +513,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) + #endif + + dispdev->displayOff(); ++ ++#ifdef SCREEN_12V_ENABLE ++ digitalWrite(SCREEN_12V_ENABLE, LOW); ++#endif + #ifdef USE_ST7789 + SPI1.end(); + #if defined(ARCH_ESP32) +@@ -500,6 +533,21 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) + nrf_gpio_cfg_default(ST7789_NSS); + #endif + #endif ++#ifdef USE_ST7796 ++ SPI1.end(); ++#if defined(ARCH_ESP32) ++ pinMode(VTFT_LEDA, OUTPUT); ++ digitalWrite(VTFT_LEDA, LOW); ++ pinMode(ST7796_RESET, ANALOG); ++ pinMode(ST7796_RS, ANALOG); ++ pinMode(ST7796_NSS, ANALOG); ++#else ++ nrf_gpio_cfg_default(VTFT_LEDA); ++ nrf_gpio_cfg_default(ST7796_RESET); ++ nrf_gpio_cfg_default(ST7796_RS); ++ nrf_gpio_cfg_default(ST7796_NSS); ++#endif ++#endif + + #ifdef T_WATCH_S3 + PMU->disablePowerOutput(XPOWERS_ALDO2); +@@ -534,7 +582,7 @@ void Screen::setup() + static_cast(dispdev)->setDetected(model); + #endif + +-#ifdef USE_SH1107_128_64 ++#if defined(USE_SH1107_128_64) || defined(USE_SH1107) + static_cast(dispdev)->setSubtype(7); + #endif + +@@ -542,6 +590,13 @@ void Screen::setup() + // Apply custom RGB color (e.g. Heltec T114/T190) + static_cast(dispdev)->setRGB(TFT_MESH); + #endif ++#if defined(MUZI_BASE) ++ dispdev->delayPoweron = true; ++#endif ++#if defined(USE_ST7796) && defined(TFT_MESH) ++ // Custom text color, if defined in variant.h ++ static_cast(dispdev)->setRGB(TFT_MESH); ++#endif + + // === Initialize display and UI system === + ui->init(); +@@ -605,6 +660,8 @@ void Screen::setup() + static_cast(dispdev)->flipScreenVertically(); + #elif defined(USE_ST7789) + static_cast(dispdev)->flipScreenVertically(); ++#elif defined(USE_ST7796) ++ static_cast(dispdev)->mirrorScreen(); + #elif !defined(M5STACK_UNITC6L) + dispdev->flipScreenVertically(); + #endif +@@ -637,7 +694,7 @@ void Screen::setup() + touchScreenImpl1->init(); + } + } +-#elif HAS_TOUCHSCREEN && !defined(USE_EINK) ++#elif HAS_TOUCHSCREEN && !defined(USE_EINK) && !HAS_CST226SE + touchScreenImpl1 = + new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); + touchScreenImpl1->init(); +@@ -1549,6 +1606,7 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event) + + int Screen::handleInputEvent(const InputEvent *event) + { ++ LOG_INPUT("Screen Input event %u! kb %u", event->inputEvent, event->kbchar); + if (!screenOn) + return 0; + +diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h +index 74b8d7c5d..a40579ff5 100644 +--- a/src/graphics/Screen.h ++++ b/src/graphics/Screen.h +@@ -83,6 +83,8 @@ class Screen + #include + #elif defined(USE_SPISSD1306) + #include ++#elif defined(USE_ST7796) ++#include + #else + // the SH1106/SSD1306 variant is auto-detected + #include +@@ -249,6 +251,8 @@ class Screen : public concurrency::OSThread + + bool isOverlayBannerShowing(); + ++ bool isScreenOn() { return screenOn; } ++ + // Stores the last 4 of our hardware ID, to make finding the device for pairing easier + // FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class + char ourId[5]; +diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h +index c497a27b2..bcb4c4987 100644 +--- a/src/graphics/ScreenFonts.h ++++ b/src/graphics/ScreenFonts.h +@@ -73,7 +73,7 @@ + #endif + + #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ +- defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \ ++ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ + !defined(DISPLAY_FORCE_SMALL_FONTS) + // The screen is bigger so use bigger fonts + #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 +diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp +index 79c1e7e61..6bccb1653 100644 +--- a/src/graphics/draw/DebugRenderer.cpp ++++ b/src/graphics/draw/DebugRenderer.cpp +@@ -97,7 +97,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 + (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit + #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ +- ARCH_PORTDUINO) && \ ++ defined(USE_ST7796) || \ ++ ARCH_PORTDUINO) && \ + !defined(DISPLAY_FORCE_SMALL_FONTS) + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, + 8, imgQuestionL1); +@@ -109,7 +110,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 + #endif + } else { + #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ +- defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \ ++ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ + !defined(DISPLAY_FORCE_SMALL_FONTS) + display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16, + 8, imgSFL1); +@@ -125,7 +126,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 + // TODO: Raspberry Pi supports more than just the one screen size + #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ +- ARCH_PORTDUINO) && \ ++ defined(USE_ST7796) || \ ++ ARCH_PORTDUINO) && \ + !defined(DISPLAY_FORCE_SMALL_FONTS) + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, + imgInfoL1); +diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp +index c50fe5cf1..3d23acc9f 100644 +--- a/src/graphics/draw/UIRenderer.cpp ++++ b/src/graphics/draw/UIRenderer.cpp +@@ -257,7 +257,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes + } + + #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ +- defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \ ++ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ + !defined(DISPLAY_FORCE_SMALL_FONTS) + + if (isHighResolution) { +diff --git a/src/graphics/images.h b/src/graphics/images.h +index 8670d78d9..998fe8e2a 100644 +--- a/src/graphics/images.h ++++ b/src/graphics/images.h +@@ -27,8 +27,7 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 + 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; + + #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ +- defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ +- ARCH_PORTDUINO) && \ ++ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(USE_ST7796) || defined(ST7796_CS) || ARCH_PORTDUINO) && \ + !defined(DISPLAY_FORCE_SMALL_FONTS) + const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; + const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; +diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h +index 36328ca64..022101f7d 100644 +--- a/src/input/InputBroker.h ++++ b/src/input/InputBroker.h +@@ -3,6 +3,12 @@ + #include "Observer.h" + #include "freertosinc.h" + ++#ifdef InputBrokerDebug ++#define LOG_INPUT(...) LOG_DEBUG(__VA_ARGS__) ++#else ++#define LOG_INPUT(...) ++#endif ++ + enum input_broker_event { + INPUT_BROKER_NONE = 0, + INPUT_BROKER_SELECT = 10, +diff --git a/src/main.cpp b/src/main.cpp +index 8fc2c097b..da2e39604 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -877,7 +877,7 @@ 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_ST7796) || \ + defined(USE_SPISSD1306) + screen = new graphics::Screen(screen_found, screen_model, screen_geometry); + #elif defined(ARCH_PORTDUINO) +@@ -1154,7 +1154,7 @@ 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_ST7796) || \ + defined(USE_SPISSD1306) + if (screen) + screen->setup(); +diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp +index 3831a384d..af6dd92e9 100644 +--- a/src/mesh/LR11x0Interface.cpp ++++ b/src/mesh/LR11x0Interface.cpp +@@ -244,6 +244,8 @@ template void LR11x0Interface::startReceive() + // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. + int err = + lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); ++ if (err) ++ LOG_ERROR("StartReceive error: %d", err); + assert(err == RADIOLIB_ERR_NONE); + + RadioLibInterface::startReceive(); +@@ -304,4 +306,4 @@ template bool LR11x0Interface::sleep() + + return true; + } +-#endif +\ No newline at end of file ++#endif +diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp +index bb3fc6dca..4e99a22ef 100644 +--- a/src/mesh/NodeDB.cpp ++++ b/src/mesh/NodeDB.cpp +@@ -664,7 +664,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(USE_SPISSD1306) ++ defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) || defined(USE_ST7796) + 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); +@@ -734,6 +734,9 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) + config.display.screen_on_secs = 30; + config.display.wake_on_tap_or_motion = true; + #endif ++#ifdef COMPASS_ORIENTATION ++ config.display.compass_orientation = COMPASS_ORIENTATION; ++#endif + #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI + if (WiFiOTA::isUpdated()) { + WiFiOTA::recoverConfig(&config.network); +diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp +index f435f6060..9cbacc877 100644 +--- a/src/modules/CannedMessageModule.cpp ++++ b/src/modules/CannedMessageModule.cpp +@@ -836,6 +836,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) + if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) { + payload = 0x08; + lastTouchMillis = millis(); ++ requestFocus(); + runOnce(); + return true; + } +@@ -844,6 +845,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) + if (event->inputEvent == INPUT_BROKER_LEFT) { + payload = INPUT_BROKER_LEFT; + lastTouchMillis = millis(); ++ requestFocus(); + runOnce(); + return true; + } +@@ -851,6 +853,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) + if (event->inputEvent == INPUT_BROKER_RIGHT) { + payload = INPUT_BROKER_RIGHT; + lastTouchMillis = millis(); ++ requestFocus(); + runOnce(); + return true; + } +diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp +index 4fe49cc1b..91e96b8d4 100644 +--- a/src/modules/ExternalNotificationModule.cpp ++++ b/src/modules/ExternalNotificationModule.cpp +@@ -314,11 +314,10 @@ void ExternalNotificationModule::stopNow() + audioThread->stop(); + #endif + // Turn off all outputs +- LOG_INFO("Turning off setExternalStates: "); ++ LOG_INFO("Turning off setExternalStates"); + for (int i = 0; i < 3; i++) { + setExternalState(i, false); + externalTurnedOn[i] = 0; +- LOG_INFO("%d ", i); + } + setIntervalFromNow(0); + #ifdef T_WATCH_S3 diff --git a/src/modules/ModuleRegistry.cpp b/src/modules/ModuleRegistry.cpp new file mode 100644 index 000000000..5c83dc70f @@ -336,10 +2042,10 @@ index 000000000..82f571a5e +#endif // MODULE_REGISTRY_H \ No newline at end of file diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp -index e477574dd..49df725dd 100644 +index e477574dd..3cd0f6405 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp -@@ -9,6 +9,8 @@ +@@ -9,10 +9,14 @@ #include "input/UpDownInterruptImpl1.h" #include "input/i2cButton.h" #include "modules/SystemCommandsModule.h" @@ -348,7 +2054,39 @@ index e477574dd..49df725dd 100644 #if HAS_TRACKBALL #include "input/TrackballInterruptImpl1.h" #endif -@@ -298,6 +300,9 @@ void setupModules() + ++#include "modules/StatusLEDModule.h" ++ + #if !MESHTASTIC_EXCLUDE_I2C + #include "input/cardKbI2cImpl.h" + #endif +@@ -119,6 +123,10 @@ void setupModules() + buzzerFeedbackThread = new BuzzerFeedbackThread(); + } + #endif ++#if defined(LED_CHARGE) || defined(LED_PAIRING) ++ statusLEDModule = new StatusLEDModule(); ++#endif ++ + #if !MESHTASTIC_EXCLUDE_ADMIN + adminModule = new AdminModule(); + #endif +@@ -175,12 +183,13 @@ void setupModules() + // new ReplyModule(); + #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { ++#ifndef T_LORA_PAGER + rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); + if (!rotaryEncoderInterruptImpl1->init()) { + delete rotaryEncoderInterruptImpl1; + rotaryEncoderInterruptImpl1 = nullptr; + } +-#ifdef T_LORA_PAGER ++#elif defined(T_LORA_PAGER) + // use a special FSM based rotary encoder version for T-LoRa Pager + rotaryEncoderImpl = new RotaryEncoderImpl(); + if (!rotaryEncoderImpl->init()) { +@@ -298,6 +307,9 @@ void setupModules() if (moduleConfig.has_range_test && moduleConfig.range_test.enabled) new RangeTestModule(); #endif @@ -358,3 +2096,1275 @@ index e477574dd..49df725dd 100644 // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // acks routingModule = new RoutingModule(); +diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp +index 575e9fa96..719e342b1 100644 +--- a/src/modules/SerialModule.cpp ++++ b/src/modules/SerialModule.cpp +@@ -64,7 +64,8 @@ SerialModule *serialModule; + SerialModuleRadio *serialModuleRadio; + + #if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ +- defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) ++ defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || \ ++ defined(MUZI_BASE) + SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") + { + api_type = TYPE_SERIAL; +@@ -204,7 +205,7 @@ int32_t SerialModule::runOnce() + Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); + } + #elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ +- !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) ++ !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) + if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { + #ifdef ARCH_RP2040 + Serial2.setFIFOSize(RX_BUFFER); +@@ -261,7 +262,7 @@ int32_t SerialModule::runOnce() + } + + #if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ +- !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) ++ !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) + else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { + processWXSerial(); + +@@ -536,7 +537,8 @@ ParsedLine parseLine(const char *line) + void SerialModule::processWXSerial() + { + #if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \ +- !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) ++ !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && \ ++ !defined(ARCH_STM32WL) && !defined(MUZI_BASE) + static unsigned int lastAveraged = 0; + static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. + static double dir_sum_sin = 0; +diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp +new file mode 100644 +index 000000000..fc9ed310e +--- /dev/null ++++ b/src/modules/StatusLEDModule.cpp +@@ -0,0 +1,94 @@ ++#include "StatusLEDModule.h" ++#include "MeshService.h" ++#include "configuration.h" ++#include ++ ++/* ++StatusLEDModule manages the device's status LEDs, updating their states based on power and Bluetooth status. ++It reflects charging, charged, discharging, and Bluetooth connection states using the appropriate LEDs. ++*/ ++StatusLEDModule *statusLEDModule; ++ ++StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule") ++{ ++ bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); ++ powerStatusObserver.observe(&powerStatus->onNewStatus); ++} ++ ++int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) ++{ ++ switch (arg->getStatusType()) { ++ case STATUS_TYPE_POWER: { ++ meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg; ++ if (powerStatus->getHasUSB()) { ++ power_state = charging; ++ if (powerStatus->getBatteryChargePercent() >= 100) { ++ power_state = charged; ++ } ++ } else { ++ power_state = discharging; ++ } ++ break; ++ } ++ case STATUS_TYPE_BLUETOOTH: { ++ meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)arg; ++ switch (bluetoothStatus->getConnectionState()) { ++ case meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED: { ++ ble_state = unpaired; ++ PAIRING_LED_starttime = millis(); ++ break; ++ } ++ case meshtastic::BluetoothStatus::ConnectionState::PAIRING: { ++ ble_state = pairing; ++ PAIRING_LED_starttime = millis(); ++ break; ++ } ++ case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: { ++ ble_state = connected; ++ PAIRING_LED_starttime = millis(); ++ break; ++ } ++ } ++ ++ break; ++ } ++ } ++ return 0; ++}; ++ ++int32_t StatusLEDModule::runOnce() ++{ ++ ++ if (power_state == charging) { ++ CHARGE_LED_state = !CHARGE_LED_state; ++ } else if (power_state == charged) { ++ CHARGE_LED_state = LED_STATE_ON; ++ } else { ++ CHARGE_LED_state = LED_STATE_OFF; ++ } ++ ++ if (!config.bluetooth.enabled || PAIRING_LED_starttime + 30 * 1000 < millis()) { ++ PAIRING_LED_state = LED_STATE_OFF; ++ } else if (ble_state == unpaired) { ++ if (slowTrack) { ++ PAIRING_LED_state = !PAIRING_LED_state; ++ slowTrack = false; ++ } else { ++ slowTrack = true; ++ } ++ } else if (ble_state == pairing) { ++ PAIRING_LED_state = !PAIRING_LED_state; ++ } else { ++ PAIRING_LED_state = LED_STATE_ON; ++ } ++ ++#ifdef LED_CHARGE ++ digitalWrite(LED_CHARGE, CHARGE_LED_state); ++#endif ++ // digitalWrite(green_LED_PIN, LED_STATE_OFF); ++#ifdef LED_PAIRING ++ digitalWrite(LED_PAIRING, PAIRING_LED_state); ++#endif ++ ++ return (my_interval); ++} +diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h +new file mode 100644 +index 000000000..d9e3a4f33 +--- /dev/null ++++ b/src/modules/StatusLEDModule.h +@@ -0,0 +1,44 @@ ++#pragma once ++ ++#include "BluetoothStatus.h" ++#include "MeshModule.h" ++#include "PowerStatus.h" ++#include "concurrency/OSThread.h" ++#include "configuration.h" ++#include ++#include ++ ++class StatusLEDModule : private concurrency::OSThread ++{ ++ bool slowTrack = false; ++ ++ public: ++ StatusLEDModule(); ++ ++ int handleStatusUpdate(const meshtastic::Status *); ++ ++ protected: ++ unsigned int my_interval = 1000; // interval in millisconds ++ virtual int32_t runOnce() override; ++ ++ CallbackObserver bluetoothStatusObserver = ++ CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); ++ CallbackObserver powerStatusObserver = ++ CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); ++ ++ private: ++ bool CHARGE_LED_state = LED_STATE_OFF; ++ bool PAIRING_LED_state = LED_STATE_OFF; ++ ++ uint32_t PAIRING_LED_starttime = 0; ++ ++ enum PowerState { discharging, charging, charged }; ++ ++ PowerState power_state = discharging; ++ ++ enum BLEState { unpaired, pairing, connected }; ++ ++ BLEState ble_state = unpaired; ++}; ++ ++extern StatusLEDModule *statusLEDModule; +diff --git a/src/modules/SystemCommandsModule.cpp b/src/modules/SystemCommandsModule.cpp +index dc5d8b41f..7fa4485c8 100644 +--- a/src/modules/SystemCommandsModule.cpp ++++ b/src/modules/SystemCommandsModule.cpp +@@ -1,4 +1,5 @@ + #include "SystemCommandsModule.h" ++#include "input/InputBroker.h" + #include "meshUtils.h" + #if HAS_SCREEN + #include "graphics/Screen.h" +@@ -22,7 +23,7 @@ SystemCommandsModule::SystemCommandsModule() + + int SystemCommandsModule::handleInputEvent(const InputEvent *event) + { +- LOG_INFO("Input event %u! kb %u", event->inputEvent, event->kbchar); ++ LOG_INPUT("SystemCommands Input event %u! kb %u", event->inputEvent, event->kbchar); + // System commands (all others fall through) + switch (event->kbchar) { + // Fn key symbols +diff --git a/src/modules/Telemetry/Sensor/AHT10.cpp b/src/modules/Telemetry/Sensor/AHT10.cpp +index 52fdc05c0..c38fd2a92 100644 +--- a/src/modules/Telemetry/Sensor/AHT10.cpp ++++ b/src/modules/Telemetry/Sensor/AHT10.cpp +@@ -35,7 +35,7 @@ bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement) + // prefer other sensors like bmp280, bmp3xx + if (!measurement->variant.environment_metrics.has_temperature) { + measurement->variant.environment_metrics.has_temperature = true; +- measurement->variant.environment_metrics.temperature = temp.temperature; ++ measurement->variant.environment_metrics.temperature = temp.temperature + AHT10_TEMP_OFFSET; + } + + if (!measurement->variant.environment_metrics.has_relative_humidity) { +diff --git a/src/modules/Telemetry/Sensor/AHT10.h b/src/modules/Telemetry/Sensor/AHT10.h +index ab3f5806c..f85f04aa0 100644 +--- a/src/modules/Telemetry/Sensor/AHT10.h ++++ b/src/modules/Telemetry/Sensor/AHT10.h +@@ -6,6 +6,10 @@ + + #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + ++#ifndef AHT10_TEMP_OFFSET ++#define AHT10_TEMP_OFFSET 0 ++#endif ++ + #include "../mesh/generated/meshtastic/telemetry.pb.h" + #include "TelemetrySensor.h" + #include +diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp +index 56f794306..5888c20be 100755 +--- a/src/motion/BMX160Sensor.cpp ++++ b/src/motion/BMX160Sensor.cpp +@@ -115,8 +115,13 @@ int32_t BMX160Sensor::runOnce() + void BMX160Sensor::calibrate(uint16_t forSeconds) + { + #if !defined(MESHTASTIC_EXCLUDE_SCREEN) ++ sBmx160SensorData_t magAccel; ++ sBmx160SensorData_t gAccel; + LOG_DEBUG("BMX160 calibration started for %is", forSeconds); +- highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; ++ sensor.getAllData(&magAccel, NULL, &gAccel); ++ highestX = magAccel.x, lowestX = magAccel.x; ++ highestY = magAccel.y, lowestY = magAccel.y; ++ highestZ = magAccel.z, lowestZ = magAccel.z; + + doCalibration = true; + uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided +diff --git a/src/motion/ICM20948Sensor.cpp b/src/motion/ICM20948Sensor.cpp +index ebb0f7b66..9455eafe0 100755 +--- a/src/motion/ICM20948Sensor.cpp ++++ b/src/motion/ICM20948Sensor.cpp +@@ -47,6 +47,21 @@ int32_t ICM20948Sensor::runOnce() + int32_t ICM20948Sensor::runOnce() + { + #if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN ++#if defined(MUZI_BASE) // temporarily gated to single device due to feature freeze ++ if (screen && !screen->isScreenOn() && !config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) { ++ if (!isAsleep) { ++ LOG_DEBUG("sleeping IMU"); ++ sensor->sleep(true); ++ isAsleep = true; ++ } ++ return MOTION_SENSOR_CHECK_INTERVAL_MS; ++ } ++ if (isAsleep) { ++ sensor->sleep(false); ++ isAsleep = false; ++ } ++#endif ++ + float magX = 0, magY = 0, magZ = 0; + if (sensor->dataReady()) { + sensor->getAGMT(); +@@ -156,8 +171,20 @@ int32_t ICM20948Sensor::runOnce() + void ICM20948Sensor::calibrate(uint16_t forSeconds) + { + #if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN ++ LOG_DEBUG("Old calibration data: highestX = %f, lowestX = %f, highestY = %f, lowestY = %f, highestZ = %f, lowestZ = %f", ++ highestX, lowestX, highestY, lowestY, highestZ, lowestZ); + LOG_DEBUG("BMX160 calibration started for %is", forSeconds); +- highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; ++ if (sensor->dataReady()) { ++ sensor->getAGMT(); ++ highestX = sensor->agmt.mag.axes.x; ++ lowestX = sensor->agmt.mag.axes.x; ++ highestY = sensor->agmt.mag.axes.y; ++ lowestY = sensor->agmt.mag.axes.y; ++ highestZ = sensor->agmt.mag.axes.z; ++ lowestZ = sensor->agmt.mag.axes.z; ++ } else { ++ highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; ++ } + + doCalibration = true; + uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided +diff --git a/src/motion/ICM20948Sensor.h b/src/motion/ICM20948Sensor.h +index 27ce4f451..a9b7b69d0 100755 +--- a/src/motion/ICM20948Sensor.h ++++ b/src/motion/ICM20948Sensor.h +@@ -82,7 +82,13 @@ class ICM20948Sensor : public MotionSensor + private: + ICM20948Singleton *sensor = nullptr; + bool showingScreen = false; ++#ifdef MUZI_BASE ++ bool isAsleep = false; ++ float highestX = 449.000000, lowestX = -140.000000, highestY = 422.000000, lowestY = -232.000000, highestZ = 749.000000, ++ lowestZ = 98.000000; ++#else + float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; ++#endif + + public: + explicit ICM20948Sensor(ScanI2C::FoundDevice foundDevice); +diff --git a/src/platform/extra_variants/tbeam_displayshield/variant.cpp b/src/platform/extra_variants/tbeam_displayshield/variant.cpp +new file mode 100644 +index 000000000..7beac2293 +--- /dev/null ++++ b/src/platform/extra_variants/tbeam_displayshield/variant.cpp +@@ -0,0 +1,43 @@ ++#include "configuration.h" ++ ++#ifdef HAS_CST226SE ++ ++#include "TouchDrvCSTXXX.hpp" ++#include "input/TouchScreenImpl1.h" ++#include ++ ++TouchDrvCSTXXX tsPanel; ++static constexpr uint8_t PossibleAddresses[2] = {CST328_ADDR, CST226SE_ADDR_ALT}; ++uint8_t i2cAddress = 0; ++ ++bool readTouch(int16_t *x, int16_t *y) ++{ ++ int16_t x_array[1], y_array[1]; ++ uint8_t touched = tsPanel.getPoint(x_array, y_array, 1); ++ if (touched > 0) { ++ *y = x_array[0]; ++ *x = (TFT_WIDTH - y_array[0]); ++ // Check bounds ++ if (*x < 0 || *x >= TFT_WIDTH || *y < 0 || *y >= TFT_HEIGHT) { ++ return false; ++ } ++ return true; // Valid touch detected ++ } ++ return false; // No valid touch data ++} ++ ++void lateInitVariant() ++{ ++ tsPanel.setTouchDrvModel(TouchDrv_CST226); ++ for (uint8_t addr : PossibleAddresses) { ++ if (tsPanel.begin(Wire, addr, I2C_SDA, I2C_SCL)) { ++ i2cAddress = addr; ++ LOG_DEBUG("CST226SE init OK at address 0x%02X", addr); ++ touchScreenImpl1 = new TouchScreenImpl1(TFT_WIDTH, TFT_HEIGHT, readTouch); ++ touchScreenImpl1->init(); ++ return; ++ } ++ } ++ LOG_ERROR("CST226SE init failed at all known addresses"); ++} ++#endif +diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h +index 6ddb41b16..1568e1790 100644 +--- a/src/platform/nrf52/architecture.h ++++ b/src/platform/nrf52/architecture.h +@@ -57,17 +57,19 @@ + #define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO + #elif defined(R1_NEO) + #define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO ++#elif defined(RAK3401) ++#define HW_VENDOR meshtastic_HardwareModel_RAK3401 + // MAke sure all custom RAK4630 boards are defined before the generic RAK4630 + #elif defined(RAK4630) + #define HW_VENDOR meshtastic_HardwareModel_RAK4631 +-#elif defined(RAK3401) +-#define HW_VENDOR meshtastic_HardwareModel_RAK3401 + #elif defined(TTGO_T_ECHO) + #define HW_VENDOR meshtastic_HardwareModel_T_ECHO + #elif defined(T_ECHO_LITE) + #define HW_VENDOR meshtastic_HardwareModel_T_ECHO_LITE + #elif defined(ELECROW_ThinkNode_M1) + #define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1 ++#elif defined(ELECROW_ThinkNode_M3) ++#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M3 + #elif defined(ELECROW_ThinkNode_M6) + #define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M6 + #elif defined(NANO_G2_ULTRA) +@@ -106,6 +108,8 @@ + #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 + #elif defined(HELTEC_MESH_SOLAR) + #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR ++#elif defined(MUZI_BASE) ++#define HW_VENDOR meshtastic_HardwareModel_RESERVED_FRIED_CHICKEN + #else + #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN + #endif +@@ -130,7 +134,9 @@ + + #endif + ++#ifdef PIN_LED1 + #define LED_PIN PIN_LED1 // LED1 on nrf52840-DK ++#endif + + #ifdef PIN_BUTTON1 + #define BUTTON_PIN PIN_BUTTON1 +diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp +index 827863f33..c03cc4454 100644 +--- a/src/platform/nrf52/main-nrf52.cpp ++++ b/src/platform/nrf52/main-nrf52.cpp +@@ -30,6 +30,11 @@ + #include "BQ25713.h" + #endif + ++// Weak empty variant initialization function. ++// May be redefined by variant files. ++void variant_shutdown() __attribute__((weak)); ++void variant_shutdown() {} ++ + static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0); + static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main; + +@@ -391,6 +396,7 @@ void cpuDeepSleep(uint32_t msecToWake) + NRF_GPIO->DIRCLR = (1 << pin); + } + #endif ++ variant_shutdown(); + + // Sleepy trackers or sensors can low power "sleep" + // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event +diff --git a/src/power.h b/src/power.h +index 8fc7c8f45..3f28dedb2 100644 +--- a/src/power.h ++++ b/src/power.h +@@ -34,8 +34,8 @@ + #define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300 + #elif defined(SEEED_SOLAR_NODE) + #define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786 +-#elif defined(R1_NEO) +-#define OCV_ARRAY 4330, 4292, 4254, 4216, 4178, 4140, 4102, 4064, 4026, 3988, 3950 ++#elif defined(WISMESH_TAG) ++#define OCV_ARRAY 4240, 4112, 4029, 3970, 3906, 3846, 3824, 3802, 3776, 3650, 3072 + #else // LiIon + #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 + #endif +diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini +index e53f22d30..c635081ff 100644 +--- a/variants/esp32/tbeam/platformio.ini ++++ b/variants/esp32/tbeam/platformio.ini +@@ -4,12 +4,22 @@ extends = esp32_base + board = ttgo-t-beam + board_level = pr + board_check = true +-lib_deps = +- ${esp32_base.lib_deps} +-build_flags = +- ${esp32_base.build_flags} ++lib_deps = ${esp32_base.lib_deps} ++build_flags = ${esp32_base.build_flags} + -D TBEAM_V10 + -I variants/esp32/tbeam + -DBOARD_HAS_PSRAM + -mfix-esp32-psram-cache-issue + upload_speed = 921600 ++ ++[env:tbeam-displayshield] ++extends = env:tbeam ++ ++build_flags = ++ ${env:tbeam.build_flags} ++ -D USE_ST7796 ++ ++lib_deps = ++ ${env:tbeam.lib_deps} ++ https://github.com/meshtastic/st7796/archive/refs/tags/1.0.5.zip ; display addon ++ lewisxhe/SensorLib@0.3.1 ; touchscreen addon +\ No newline at end of file +diff --git a/variants/esp32/tbeam/variant.h b/variants/esp32/tbeam/variant.h +index 5b521a2de..2d144a888 100644 +--- a/variants/esp32/tbeam/variant.h ++++ b/variants/esp32/tbeam/variant.h +@@ -42,4 +42,35 @@ + #define GPS_UBLOX + #define GPS_RX_PIN 34 + #define GPS_TX_PIN 12 +-// #define GPS_DEBUG +\ No newline at end of file ++// #define GPS_DEBUG ++ ++// Used when the display shield is chosen ++#ifdef USE_ST7796 ++ ++#undef EXT_NOTIFY_OUT ++#undef LED_STATE_ON ++#undef LED_PIN ++ ++#define HAS_CST226SE 1 ++#define HAS_TOUCHSCREEN 1 ++// #define TOUCH_IRQ 35 // broken in this version of the lib 0.3.1 ++#ifndef TOUCH_IRQ ++#define TOUCH_IRQ -1 ++#endif ++#define CANNED_MESSAGE_MODULE_ENABLE 1 ++#define USE_VIRTUAL_KEYBOARD 1 ++ ++#define ST7796_NSS 25 ++#define ST7796_RS 13 // DC ++#define ST7796_SDA 14 // MOSI ++#define ST7796_SCK 15 ++#define ST7796_RESET 2 ++#define ST7796_MISO -1 ++#define ST7796_BUSY -1 ++#define VTFT_LEDA 4 ++#define TFT_SPI_FREQUENCY 60000000 ++#define TFT_HEIGHT 222 ++#define TFT_WIDTH 480 ++#define BRIGHTNESS_DEFAULT 100 // Medium Low Brightness ++#define SCREEN_TRANSITION_FRAMERATE 5 // fps ++#endif +\ No newline at end of file +diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini +new file mode 100644 +index 000000000..958e48e48 +--- /dev/null ++++ b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini +@@ -0,0 +1,17 @@ ++[env:thinknode_m3] ++extends = nrf52840_base ++board = ThinkNode-M3 ++board_check = true ++debug_tool = jlink ++build_flags = ++ ${nrf52840_base.build_flags} ++ -Ivariants/nrf52840/ELECROW-ThinkNode-M3 ++ -DELECROW_ThinkNode_M3 ++ -DGPS_POWER_TOGGLE ++ -D CONFIG_NFCT_PINS_AS_GPIOS=1 ++ -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" ++build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M3> ++lib_deps = ++ ${nrf52840_base.lib_deps} ++ khoih-prog/nRF52_PWM@^1.0.1 ++ lewisxhe/PCF8563_Library@^1.0.1 +diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h b/variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h +new file mode 100644 +index 000000000..77ae9ef73 +--- /dev/null ++++ b/variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h +@@ -0,0 +1,15 @@ ++#include "RadioLib.h" ++#include "nrf.h" ++ ++// set RF switch configuration for ELECROW ThinkNode M3 ++// ELECROW ThinkNode M3 uses DIO5 and DIO6 for RF switching ++ ++static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; ++ ++static const Module::RfSwitchMode_t rfswitch_table[] = { ++ // mode DIO5 DIO6 ++ {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, ++ {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, ++ {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, ++ {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, ++}; +diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp +new file mode 100644 +index 000000000..b7a7b7342 +--- /dev/null ++++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp +@@ -0,0 +1,93 @@ ++/* ++ Copyright (c) 2014-2015 Arduino LLC. All right reserved. ++ Copyright (c) 2016 Sandeep Mistry All right reserved. ++ Copyright (c) 2018, Adafruit Industries (adafruit.com) ++ ++ This library is free software; you can redistribute it and/or ++ modify it under the terms of the GNU Lesser General Public ++ License as published by the Free Software Foundation; either ++ version 2.1 of the License, or (at your option) any later version. ++ ++ This library is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ See the GNU Lesser General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with this library; if not, write to the Free Software ++ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ++*/ ++ ++#include "variant.h" ++#include "meshUtils.h" ++#include "nrf.h" ++#include "wiring_constants.h" ++#include "wiring_digital.h" ++ ++const uint32_t g_ADigitalPinMap[] = { ++ // P0 ++ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ++ ++ // P1 ++ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; ++ ++void initVariant() ++{ ++ pinMode(KEY_POWER, OUTPUT); ++ digitalWrite(KEY_POWER, HIGH); ++ pinMode(RGB_POWER, OUTPUT); ++ digitalWrite(RGB_POWER, HIGH); ++ pinMode(green_LED_PIN, OUTPUT); ++ digitalWrite(green_LED_PIN, LED_STATE_OFF); ++ pinMode(LED_BLUE, OUTPUT); ++ pinMode(PIN_POWER_USB, INPUT); ++ pinMode(PIN_POWER_DONE, INPUT); ++ pinMode(PIN_POWER_CHRG, INPUT); ++ pinMode(BUTTON_PIN, INPUT_PULLUP); ++ pinMode(EEPROM_POWER, OUTPUT); ++ digitalWrite(EEPROM_POWER, HIGH); ++ pinMode(PIN_EN1, OUTPUT); ++ digitalWrite(PIN_EN1, HIGH); ++ pinMode(PIN_EN2, OUTPUT); ++ digitalWrite(PIN_EN2, HIGH); ++ pinMode(ACC_POWER, OUTPUT); ++ digitalWrite(ACC_POWER, LOW); ++ pinMode(DHT_POWER, OUTPUT); ++ digitalWrite(DHT_POWER, HIGH); ++ pinMode(Battery_POWER, OUTPUT); ++ digitalWrite(Battery_POWER, HIGH); ++ pinMode(GPS_POWER, OUTPUT); ++ digitalWrite(GPS_POWER, HIGH); ++} ++ ++// called from main-nrf52.cpp during the cpuDeepSleep() function ++void variant_shutdown() ++{ ++ digitalWrite(EEPROM_POWER, LOW); ++ digitalWrite(KEY_POWER, LOW); ++ ++ for (int pin = 0; pin < 48; pin++) { ++ if (pin == PIN_POWER_USB || pin == BUTTON_PIN || pin == PIN_EN1 || pin == PIN_EN2 || pin == DHT_POWER || ++ pin == ACC_POWER || pin == Battery_POWER || pin == GPS_POWER || pin == LR1110_SPI_MISO_PIN || ++ pin == LR1110_SPI_MOSI_PIN || pin == LR1110_SPI_SCK_PIN || pin == LR1110_SPI_NSS_PIN || pin == LR1110_BUSY_PIN || ++ pin == LR1110_NRESET_PIN || pin == LR1110_IRQ_PIN || pin == GPS_TX_PIN || pin == GPS_RX_PIN || pin == green_LED_PIN || ++ pin == red_LED_PIN || pin == LED_BLUE) { ++ continue; ++ } ++ pinMode(pin, OUTPUT); ++ digitalWrite(pin, LOW); ++ if (pin >= 32) { ++ NRF_P1->DIRCLR = (1 << (pin - 32)); ++ } else { ++ NRF_GPIO->DIRCLR = (1 << pin); ++ } ++ } ++ ++ nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input ++ nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; ++ nrf_gpio_cfg_sense_set(BUTTON_PIN, sense1); ++ ++ nrf_gpio_cfg_input(PIN_POWER_USB, NRF_GPIO_PIN_PULLDOWN); // Configure the pin to be woken up as an input ++ nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_HIGH; ++ nrf_gpio_cfg_sense_set(PIN_POWER_USB, sense2); ++} +\ No newline at end of file +diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +new file mode 100644 +index 000000000..cf940172b +--- /dev/null ++++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +@@ -0,0 +1,122 @@ ++/* ++ Copyright (c) 2014-2015 Arduino LLC. All right reserved. ++ Copyright (c) 2016 Sandeep Mistry All right reserved. ++ Copyright (c) 2018, Adafruit Industries (adafruit.com) ++ ++ This library is free software; you can redistribute it and/or ++ modify it under the terms of the GNU Lesser General Public ++ License as published by the Free Software Foundation; either ++ version 2.1 of the License, or (at your option) any later version. ++ This library is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ See the GNU Lesser General Public License for more details. ++ You should have received a copy of the GNU Lesser General Public ++ License along with this library; if not, write to the Free Software ++ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ++*/ ++ ++#ifndef _VARIANT_ELECROW_EINK_V1_0_ ++#define _VARIANT_ELECROW_EINK_V1_0_ ++ ++#ifdef __cplusplus ++extern "C" { ++#endif // __cplusplus ++ ++#include "WVariant.h" ++ ++#define VARIANT_MCK (64000000ul) ++#define USE_LFXO // Board uses 32khz crystal for LF ++ ++#define ELECROW_ThinkNode_M3 1 ++// Number of pins defined in PinDescription array ++#define PINS_COUNT (48) ++#define NUM_DIGITAL_PINS (48) ++#define NUM_ANALOG_INPUTS (1) ++#define NUM_ANALOG_OUTPUTS (0) ++ ++// Power Pin ++#define NRF_APM ++#define GPS_POWER 14 ++#define PIN_POWER_USB 31 ++#define EXT_PWR_DETECT PIN_POWER_USB ++#define PIN_POWER_DONE 24 ++#define PIN_POWER_CHRG 32 ++#define KEY_POWER 16 ++#define ACC_POWER 2 ++#define DHT_POWER 3 ++#define Battery_POWER 17 ++#define RGB_POWER 29 ++#define EEPROM_POWER 7 ++ ++// LED ++#define red_LED_PIN 33 ++#define LED_POWER red_LED_PIN ++#define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED ++#define green_LED_PIN 35 ++#define LED_BLUE 37 ++#define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED ++ ++#define LED_BUILTIN -1 ++#define LED_STATE_ON LOW ++#define LED_STATE_OFF HIGH ++ ++// BUZZER ++#define PIN_BUZZER 23 ++#define PIN_EN1 36 ++#define PIN_EN2 34 ++/*Wire Interfaces*/ ++#define WIRE_INTERFACES_COUNT 1 ++#define PIN_WIRE_SDA 26 ++#define PIN_WIRE_SCL 27 ++ ++// Temperature correction for sensor ++#define AHT10_TEMP_OFFSET -5.0 ++ ++/*GPS pins*/ ++#define HAS_GPS 1 ++#define GPS_BAUDRATE 9600 ++#define PIN_GPS_RESET 25 ++#define PIN_GPS_STANDBY 21 ++#define GPS_TX_PIN 20 ++#define GPS_RX_PIN 22 ++#define GPS_THREAD_INTERVAL 50 ++#define PIN_SERIAL1_RX GPS_TX_PIN ++#define PIN_SERIAL1_TX GPS_RX_PIN ++// Button ++#define BUTTON_PIN 12 ++#define BUTTON_PIN_ALT (0 + 12) ++// Battery ++#define BATTERY_PIN 5 ++#define BATTERY_SENSE_RESOLUTION_BITS 12 ++#define BATTERY_SENSE_RESOLUTION 4096.0 ++#undef AREF_VOLTAGE ++#define AREF_VOLTAGE 2.4 ++#define VBAT_AR_INTERNAL AR_INTERNAL_2_4 ++#define ADC_MULTIPLIER (1.75) ++/*SPI Interfaces*/ ++#define SPI_INTERFACES_COUNT 1 ++#define PIN_SPI_MISO (32 + 15) // P1.15 47 ++#define PIN_SPI_MOSI (32 + 14) // P1.14 46 ++#define PIN_SPI_SCK (32 + 13) // P1.13 45 ++#define PIN_SPI_NSS (32 + 12) // P1.12 44 ++/*LORA Interfaces*/ ++#define USE_LR1110 ++#define LR1110_IRQ_PIN 40 ++#define LR1110_NRESET_PIN 42 ++#define LR1110_BUSY_PIN 43 ++#define LR1110_SPI_NSS_PIN 44 ++#define LR1110_SPI_SCK_PIN 45 ++#define LR1110_SPI_MOSI_PIN 46 ++#define LR1110_SPI_MISO_PIN 47 ++#define LR11X0_DIO3_TCXO_VOLTAGE 3.3 ++#define LR11X0_DIO_AS_RF_SWITCH ++ ++// PCF8563 RTC Module ++#define PCF8563_RTC 0x51 ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +index b84079e66..09872d409 100644 +--- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp ++++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +@@ -32,11 +32,11 @@ const uint32_t g_ADigitalPinMap[] = { + + void initVariant() + { +- pinMode(PIN_LED1, OUTPUT); +- ledOff(PIN_LED1); ++ pinMode(LED_CHARGE, OUTPUT); ++ ledOff(LED_CHARGE); + +- pinMode(PIN_LED2, OUTPUT); +- ledOff(PIN_LED2); ++ pinMode(LED_PAIRING, OUTPUT); ++ ledOff(LED_PAIRING); + + pinMode(VDD_FLASH_EN, OUTPUT); + digitalWrite(VDD_FLASH_EN, HIGH); +diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +index 98c654df2..5e543b21f 100644 +--- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h ++++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +@@ -40,10 +40,11 @@ extern "C" { + #define NUM_ANALOG_OUTPUTS (0) + + // LEDs +-#define PIN_LED1 (12) +-#define PIN_LED2 (7) +-#define LED_BUILTIN PIN_LED1 +-#define LED_BLUE PIN_LED2 ++#define LED_BUILTIN -1 ++#define LED_BLUE -1 ++#define LED_CHARGE (12) ++#define LED_PAIRING (7) ++ + #define LED_STATE_ON 1 + + // USB power detection +diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h +index 39cbc8f01..143d20459 100644 +--- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h ++++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h +@@ -116,13 +116,13 @@ No longer populated on PCB + #define PIN_GPS_PPS (32 + 4) + // Seems to be missing on this new board + // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +-#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU +-#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS ++#define GPS_TX_PIN (32 + 7) // This is for bits going TOWARDS the CPU ++#define GPS_RX_PIN (32 + 5) // This is for bits going TOWARDS the GPS + + #define GPS_THREAD_INTERVAL 50 + +-#define PIN_SERIAL1_RX GPS_TX_PIN +-#define PIN_SERIAL1_TX GPS_RX_PIN ++#define PIN_SERIAL1_RX GPS_RX_PIN ++#define PIN_SERIAL1_TX GPS_TX_PIN + + // PCF8563 RTC Module + #define PCF8563_RTC 0x51 +diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h +index b6082fdc6..3493577bc 100644 +--- a/variants/nrf52840/heltec_mesh_node_t114/variant.h ++++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h +@@ -167,13 +167,13 @@ No longer populated on PCB + #define PIN_GPS_PPS (32 + 4) + // Seems to be missing on this new board + // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +-#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU +-#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS ++#define GPS_TX_PIN (32 + 7) // This is for bits going TOWARDS the CPU ++#define GPS_RX_PIN (32 + 5) // This is for bits going TOWARDS the GPS + + #define GPS_THREAD_INTERVAL 50 + +-#define PIN_SERIAL1_RX GPS_TX_PIN +-#define PIN_SERIAL1_TX GPS_RX_PIN ++#define PIN_SERIAL1_RX GPS_RX_PIN ++#define PIN_SERIAL1_TX GPS_TX_PIN + + // PCF8563 RTC Module + #define PCF8563_RTC 0x51 +diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h +index 7c43d8ba7..7a8fc579f 100644 +--- a/variants/nrf52840/heltec_mesh_solar/variant.h ++++ b/variants/nrf52840/heltec_mesh_solar/variant.h +@@ -116,13 +116,13 @@ No longer populated on PCB + #define PIN_GPS_PPS (32 + 4) + // Seems to be missing on this new board + // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +-#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU +-#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS ++#define GPS_TX_PIN (32 + 7) // This is for bits going TOWARDS the CPU ++#define GPS_RX_PIN (32 + 5) // This is for bits going TOWARDS the GPS + + #define GPS_THREAD_INTERVAL 50 + +-#define PIN_SERIAL1_RX GPS_TX_PIN +-#define PIN_SERIAL1_TX GPS_RX_PIN ++#define PIN_SERIAL1_RX GPS_RX_PIN ++#define PIN_SERIAL1_TX GPS_TX_PIN + + /* + * SPI Interfaces +diff --git a/variants/nrf52840/muzi_base/platformio.ini b/variants/nrf52840/muzi_base/platformio.ini +new file mode 100644 +index 000000000..49393f4e0 +--- /dev/null ++++ b/variants/nrf52840/muzi_base/platformio.ini +@@ -0,0 +1,15 @@ ++[env:muzi-base] ++extends = nrf52840_base ++board = muzi-base ++build_flags = ${nrf52840_base.build_flags} ++ -I variants/nrf52840/muzi_base ++ -D MUZI_BASE ++ -D CONFIG_NFCT_PINS_AS_GPIOS=1 ++ -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" ++ ++build_src_filter = ${nrf52840_base.build_src_filter} +<../variants/nrf52840/muzi_base> ++lib_deps = ++ ${nrf52840_base.lib_deps} ++ artronshop/ArtronShop_RX8130CE@1.0.0 ++ ++ +diff --git a/variants/nrf52840/muzi_base/rfswitch.h b/variants/nrf52840/muzi_base/rfswitch.h +new file mode 100644 +index 000000000..589f24767 +--- /dev/null ++++ b/variants/nrf52840/muzi_base/rfswitch.h +@@ -0,0 +1,11 @@ ++#include "RadioLib.h" ++ ++static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; ++ ++static const Module::RfSwitchMode_t rfswitch_table[] = { ++ // mode DIO5 DIO6 ++ {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, ++ {LR11x0::MODE_TX, {LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, ++ {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, ++ {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, ++}; +diff --git a/variants/nrf52840/muzi_base/variant.cpp b/variants/nrf52840/muzi_base/variant.cpp +new file mode 100644 +index 000000000..da01de974 +--- /dev/null ++++ b/variants/nrf52840/muzi_base/variant.cpp +@@ -0,0 +1,83 @@ ++#include "variant.h" ++#include "nrf.h" ++#include "wiring_constants.h" ++#include "wiring_digital.h" ++ ++const uint32_t g_ADigitalPinMap[] = { ++ // P0 ++ 0, ++ 1, ++ 2, ++ 3, ++ 4, ++ 5, ++ 6, ++ 7, ++ 8, ++ 9, ++ 10, ++ 11, ++ 12, ++ 13, ++ 14, ++ 15, ++ 16, ++ 17, ++ 18, ++ 19, ++ 20, ++ 21, ++ 22, ++ 23, ++ 24, ++ 25, ++ 26, ++ 27, ++ 28, ++ 29, ++ 30, ++ 31, ++ ++ // P1 ++ 32, ++ 33, ++ 34, ++ 35, ++ 36, ++ 37, ++ 38, ++ 39, ++ 40, ++ 41, ++ 42, ++ 43, ++ 44, ++ 45, ++ 46, ++ 47, ++}; ++ ++void initVariant() ++{ ++ // Initialize the digital pins as inputs or outputs ++ pinMode(PIN_LED1, OUTPUT); ++ digitalWrite(PIN_LED1, HIGH); ++ ++ pinMode(PIN_LED2, OUTPUT); ++ digitalWrite(PIN_LED2, HIGH); ++ ++ // Initialize LoRa pins ++ pinMode(SX126X_RESET, OUTPUT); ++ digitalWrite(SX126X_RESET, HIGH); ++ ++ pinMode(SX126X_CS, OUTPUT); ++ digitalWrite(SX126X_CS, HIGH); ++ ++ pinMode(GPS_EN_GPIO, OUTPUT); ++ digitalWrite(GPS_EN_GPIO, HIGH); // GPS on initially ++ ++ pinMode(SCREEN_12V_ENABLE, OUTPUT); ++ digitalWrite(SCREEN_12V_ENABLE, LOW); // ++ ++ pinMode(BATTERY_CHARGING_INV, INPUT); ++} +diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h +new file mode 100644 +index 000000000..96604c400 +--- /dev/null ++++ b/variants/nrf52840/muzi_base/variant.h +@@ -0,0 +1,192 @@ ++#pragma once ++ ++#ifndef _VARIANT_MUZI_BASE_ ++#define _VARIANT_MUZI_BASE_ ++ ++/** Master clock frequency */ ++#define VARIANT_MCK (64000000ul) ++ ++#define USE_LFXO // Board uses 32khz crystal for LF ++ ++/*---------------------------------------------------------------------------- ++ * Headers ++ *----------------------------------------------------------------------------*/ ++ ++#include "WVariant.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif // __cplusplus ++ ++// Number of pins defined in PinDescription array ++#define PINS_COUNT (48) ++#define NUM_DIGITAL_PINS (48) ++#define NUM_ANALOG_INPUTS (6) ++#define NUM_ANALOG_OUTPUTS (0) ++ ++// Define I2C Peripherals ++#define WIRE_INTERFACES_COUNT 2 ++ ++// this is the OLED bus ++#define PIN_WIRE_SDA (0 + 24) // P0.24 ++#define PIN_WIRE_SCL (0 + 25) // P0.25 ++ ++// IMU bus ++#define PIN_WIRE1_SDA (0 + 04) // P0.04 ++#define PIN_WIRE1_SCL (0 + 06) // P0.06 ++ ++#define COMPASS_ORIENTATION meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270 ++#define HAS_ICM20948 // forces the i2c address to be seen as this sensor ++ ++#define HAS_RTC 1 ++#define RX8130CE_RTC 0x32 ++ ++// LEDs ++#define PIN_LED1 (32 + 3) // P1.03, Green ++#define PIN_LED2 (32 + 4) // P1.04, Blue ++ ++#define LED_BUILTIN -1 // PIN_LED1 ++#define LED_BLUE PIN_LED2 ++#define LED_STATE_ON 0 // State when LED is lit ++ ++// Buttons ++#define HAS_TRACKBALL 1 ++#define TB_UP (0 + 21) ++#define TB_DOWN (0 + 17) ++#define TB_LEFT (32 + 05) ++#define TB_RIGHT (0 + 16) ++#define TB_PRESS (0 + 10) ++#define TB_DIRECTION FALLING ++ ++#define CANCEL_BUTTON_PIN (0 + 15) // P0.15 ++#define CANCEL_BUTTON_ACTIVE_LOW true ++#define CANCEL_BUTTON_ACTIVE_PULLUP false ++ ++// Switch ++#define SWITCH_MODE1 (32 + 9) // P1.09, Top Position ++#define SWITCH_MODE2 (0 + 12) // P0.12, Middle Position ++#define PIN_GPS_SWITCH SWITCH_MODE2 ++ ++/* ++ * SPI Interfaces ++ */ ++ ++#define SPI_INTERFACES_COUNT 1 ++ ++// For LORA, spi 0 ++#define PIN_SPI_MISO (32 + 15) // P1.15 ++#define PIN_SPI_MOSI (32 + 14) // P1.14 ++#define PIN_SPI_SCK (32 + 13) // P1.13 ++ ++#define LORA_SCK PIN_SPI_SCK ++#define LORA_MISO PIN_SPI_MISO ++#define LORA_MOSI PIN_SPI_MOSI ++#define LORA_CS (32 + 12) // P1.12 ++ ++#define USE_SX1262 ++#define SX126X_CS LORA_CS ++#define SX126X_DIO1 (32 + 6) // P1.06 ++#define SX126X_BUSY (32 + 11) // P1.11 ++#define SX126X_RESET (32 + 10) // P1.10 ++#define SX126X_DIO2_AS_RF_SWITCH ++#define SX126X_DIO3_TCXO_VOLTAGE 3.3 ++ ++#define USE_LR1121 ++#define LR1121_IRQ_PIN (32 + 8) // P1.08 ++#define LR1121_NRESET_PIN (32 + 10) // P1.10 ++#define LR1121_BUSY_PIN (32 + 11) // P1.11 ++#define LR1121_SPI_NSS_PIN LORA_CS ++#define LR1121_SPI_SCK_PIN LORA_SCK ++#define LR1121_SPI_MOSI_PIN LORA_MOSI ++#define LR1121_SPI_MISO_PIN LORA_MISO ++#define LR11X0_DIO3_TCXO_VOLTAGE 3.0 ++#define LR11X0_DIO_AS_RF_SWITCH ++ ++// GPS ++#define GPS_RX_PIN (0 + 20) // P0.20 ++#define GPS_TX_PIN (0 + 19) // P0.19 ++#define GPS_EN_GPIO (32 + 1) // P1.01 ++ ++#define PIN_SERIAL1_RX GPS_RX_PIN ++#define PIN_SERIAL1_TX GPS_TX_PIN ++ ++#define PIN_BUZZER (0 + 22) // P0.22 ++ ++// Battery monitoring ++#define BATTERY_PIN (0 + 31) // P0.31 ++ ++// #define CHARGER_FAULT (0 + 27) // P0.27 ++#define BATTERY_CHARGING_INV (32 + 02) // P1.02 ++#define BATTERY_SENSE_RESOLUTION_BITS 12 ++#define BATTERY_SENSE_RESOLUTION 4096.0 ++#define ADC_MULTIPLIER 1.537 ++ ++#define OCV_ARRAY 4050, 4010, 3990, 3930, 3870, 3820, 3740, 3630, 3550, 3450, 3100 ++ ++// Display - I2C display ++#define HAS_SCREEN 1 ++#define SCREEN_12V_ENABLE (0 + 23) // P0.23 ++#define USE_SH1107 ++ ++#define USERPREFS_OEM_TEXT "muzi_works_logo" ++#define USERPREFS_OEM_FONT_SIZE 0 ++#define USERPREFS_OEM_IMAGE_WIDTH 88 // 11 bytes wide ++#define USERPREFS_OEM_IMAGE_HEIGHT 47 // 517 bytes total ++#define USERPREFS_OEM_IMAGE_DATA \ ++ { \ ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, \ ++ 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ ++ 0xF7, 0x0F, 0xFF, 0x00, 0xF0, 0xFF, 0x0F, 0xC0, 0xFF, 0x07, 0x78, 0xFF, 0x9F, 0xFF, 0x01, 0xF0, 0xFF, 0x0F, 0xC0, \ ++ 0xFF, 0x03, 0x78, 0x3F, 0xFE, 0xF3, 0x01, 0xF0, 0xFF, 0x0F, 0x00, 0xE0, 0x03, 0x78, 0x1F, 0xFC, 0xC0, 0x03, 0xF0, \ ++ 0xFF, 0x0F, 0x00, 0xE0, 0x01, 0x78, 0x0F, 0xF8, 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0xF0, 0x00, 0x78, 0x0F, 0x78, \ ++ 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0x70, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0x78, 0x00, \ ++ 0x78, 0x07, 0x70, 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0x3C, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xF0, 0xFF, 0x0F, \ ++ 0x00, 0x1C, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0x1E, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, \ ++ 0xE0, 0xFF, 0x0F, 0x00, 0x0F, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xE0, 0xFF, 0x07, 0x80, 0x07, 0x00, 0x78, 0x07, \ ++ 0x70, 0x80, 0x03, 0xC0, 0xFF, 0x07, 0x80, 0x07, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xC0, 0xFF, 0x03, 0xC0, 0xFF, \ ++ 0x07, 0x78, 0x07, 0x70, 0x80, 0x03, 0x00, 0xFF, 0x01, 0xE0, 0xFF, 0x07, 0x78, 0x07, 0x70, 0x80, 0x03, 0x00, 0x7C, \ ++ 0x00, 0xF0, 0xFF, 0x07, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ ++ 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ ++ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xE3, 0xE7, 0xC7, 0x1F, 0xF8, 0x0F, 0xF0, 0xE7, 0xE3, 0x07, 0x7C, 0xC7, 0xE7, \ ++ 0xC3, 0x0F, 0xE0, 0x07, 0xE0, 0xC7, 0xE1, 0x03, 0x70, 0xC7, 0xC3, 0xE3, 0x87, 0xC1, 0x07, 0xC0, 0xC7, 0xF8, 0xE3, \ ++ 0x71, 0xC7, 0xC3, 0xE3, 0xE3, 0xC7, 0xC7, 0xC7, 0x47, 0xF8, 0xF3, 0x7F, 0x8F, 0xC3, 0xF1, 0xE3, 0x8F, 0xC7, 0x8F, \ ++ 0x27, 0xFC, 0xE3, 0x7F, 0x8F, 0x81, 0xF1, 0xF1, 0x8F, 0xC7, 0xCF, 0x07, 0xFE, 0x03, 0x7E, 0x8F, 0x99, 0xF1, 0xF1, \ ++ 0x8F, 0x07, 0xC0, 0x07, 0xFF, 0x07, 0x78, 0x9F, 0x99, 0xF9, 0xF1, 0x8F, 0x07, 0xE0, 0x07, 0xFE, 0x3F, 0x70, 0x1F, \ ++ 0x18, 0xF8, 0xF3, 0x8F, 0x07, 0xF0, 0x27, 0xFC, 0xFF, 0x71, 0x3F, 0x18, 0xF8, 0xE3, 0xC7, 0xC7, 0xF1, 0x47, 0xF8, \ ++ 0xF3, 0x63, 0x3F, 0x3C, 0xFC, 0xC3, 0xC3, 0xC7, 0xE3, 0xC7, 0xF0, 0xE1, 0x71, 0x3F, 0x3C, 0xFC, 0x07, 0xE0, 0xC7, \ ++ 0xC7, 0xC7, 0xE1, 0x03, 0x70, 0x7F, 0x7E, 0xFE, 0x0F, 0xF0, 0xC7, 0x87, 0xC7, 0xC3, 0x07, 0x78, 0xFF, 0xFF, 0xFF, \ ++ 0x7F, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, \ ++ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ ++ 0xFF, 0xFF, 0x7F \ ++ } ++ ++// QSPI Pins ++#define PIN_QSPI_SCK (0 + 3) ++#define PIN_QSPI_CS (0 + 26) ++#define PIN_QSPI_IO0 (0 + 30) ++#define PIN_QSPI_IO1 (0 + 29) ++#define PIN_QSPI_IO2 (0 + 28) ++#define PIN_QSPI_IO3 (0 + 2) ++ ++// On-board QSPI Flash ++#define EXTERNAL_FLASH_DEVICES W25Q32JVSS ++#define EXTERNAL_FLASH_USE_QSPI ++ ++// NFC is disabled via CONFIG_NFCT_PINS_AS_GPIOS=1 build flag ++// This configures P0.09 and P0.10 as regular GPIO pins instead of NFC pins ++ ++#ifdef __cplusplus ++} ++#endif ++ ++/*---------------------------------------------------------------------------- ++ * Arduino objects - C++ only ++ *----------------------------------------------------------------------------*/ ++#ifdef __cplusplus ++#endif ++ ++#endif // _VARIANT_MUZI_BASE_ +\ No newline at end of file +diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h +index 901e993e3..b1d96ebd0 100644 +--- a/variants/nrf52840/r1-neo/variant.h ++++ b/variants/nrf52840/r1-neo/variant.h +@@ -132,7 +132,8 @@ static const uint8_t SCK = PIN_SPI_SCK; + #undef AREF_VOLTAGE + #define AREF_VOLTAGE 3.0 + #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +-#define ADC_MULTIPLIER 1.73 ++#define ADC_MULTIPLIER 1.667 ++#define OCV_ARRAY 4120, 4020, 4000, 3940, 3870, 3820, 3750, 3630, 3550, 3450, 3100 + + #define HAS_RTC 1 + +diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h +index 4f3a53ebf..b2692e448 100644 +--- a/variants/nrf52840/t-echo/variant.h ++++ b/variants/nrf52840/t-echo/variant.h +@@ -182,13 +182,13 @@ External serial flash WP25R1635FZUIL0 + #define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake + // Seems to be missing on this new board + // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +-#define GPS_TX_PIN (32 + 9) // This is for bits going TOWARDS the CPU +-#define GPS_RX_PIN (32 + 8) // This is for bits going TOWARDS the GPS ++#define GPS_TX_PIN (32 + 8) // This is for bits going TOWARDS the CPU ++#define GPS_RX_PIN (32 + 9) // This is for bits going TOWARDS the GPS + + #define GPS_THREAD_INTERVAL 50 + +-#define PIN_SERIAL1_RX GPS_TX_PIN +-#define PIN_SERIAL1_TX GPS_RX_PIN ++#define PIN_SERIAL1_RX GPS_RX_PIN ++#define PIN_SERIAL1_TX GPS_TX_PIN + + // PCF8563 RTC Module + #define PCF8563_RTC 0x51