diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml deleted file mode 100644 index 616f51746..000000000 --- a/.github/workflows/build_esp32.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build ESP32 - -on: - workflow_call: - inputs: - board: - required: true - type: string - -permissions: read-all - -jobs: - build-esp32: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build ESP32 - id: build - uses: ./.github/actions/build-variant - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - remove-debug-flags: >- - ./arch/esp32/esp32.ini - ./arch/esp32/esp32s2.ini - ./arch/esp32/esp32s3.ini - ./arch/esp32/esp32c3.ini - ./arch/esp32/esp32c6.ini - build-script-path: bin/build-esp32.sh - ota-firmware-source: firmware.bin - ota-firmware-target: release/bleota.bin - artifact-paths: | - release/*.bin - release/*.elf - #include-web-ui: true - arch: esp32 diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml deleted file mode 100644 index 1b6b832e9..000000000 --- a/.github/workflows/build_esp32_c3.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build ESP32-C3 - -on: - workflow_call: - inputs: - board: - required: true - type: string - -permissions: read-all - -jobs: - build-esp32-c3: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build ESP32-C3 - id: build - uses: ./.github/actions/build-variant - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - remove-debug-flags: >- - ./arch/esp32/esp32.ini - ./arch/esp32/esp32s2.ini - ./arch/esp32/esp32s3.ini - ./arch/esp32/esp32c3.ini - ./arch/esp32/esp32c6.ini - build-script-path: bin/build-esp32.sh - ota-firmware-source: firmware-c3.bin - ota-firmware-target: release/bleota-c3.bin - artifact-paths: | - release/*.bin - release/*.elf - #include-web-ui: true - arch: esp32c3 diff --git a/.github/workflows/build_esp32_c6.yml b/.github/workflows/build_esp32_c6.yml deleted file mode 100644 index 29dac51e1..000000000 --- a/.github/workflows/build_esp32_c6.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build ESP32-C6 - -on: - workflow_call: - inputs: - board: - required: true - type: string - -permissions: read-all - -jobs: - build-esp32-c6: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build ESP32-C6 - id: build - uses: ./.github/actions/build-variant - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - remove-debug-flags: >- - ./arch/esp32/esp32.ini - ./arch/esp32/esp32s2.ini - ./arch/esp32/esp32s3.ini - ./arch/esp32/esp32c3.ini - ./arch/esp32/esp32c6.ini - build-script-path: bin/build-esp32.sh - ota-firmware-source: firmware-c3.bin - ota-firmware-target: release/bleota-c3.bin - artifact-paths: | - release/*.bin - release/*.elf - #include-web-ui: true - arch: esp32c6 diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml deleted file mode 100644 index 7e0373503..000000000 --- a/.github/workflows/build_esp32_s3.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build ESP32-S3 - -on: - workflow_call: - inputs: - board: - required: true - type: string - -permissions: read-all - -jobs: - build-esp32-s3: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build ESP32-S3 - id: build - uses: ./.github/actions/build-variant - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - remove-debug-flags: >- - ./arch/esp32/esp32.ini - ./arch/esp32/esp32s2.ini - ./arch/esp32/esp32s3.ini - ./arch/esp32/esp32c3.ini - ./arch/esp32/esp32c6.ini - build-script-path: bin/build-esp32.sh - ota-firmware-source: firmware-s3.bin - ota-firmware-target: release/bleota-s3.bin - artifact-paths: | - release/*.bin - release/*.elf - #include-web-ui: true - arch: esp32s3 diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml new file mode 100644 index 000000000..df1035e62 --- /dev/null +++ b/.github/workflows/build_firmware.yml @@ -0,0 +1,66 @@ +name: Build + +on: + workflow_call: + inputs: + version: + required: true + type: string + platform: + required: true + type: string + pio_env: + required: true + type: string + +permissions: read-all + +jobs: + pio-build: + name: build-${{ inputs.platform }} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Set OTA firmware source and target + if: startsWith(inputs.platform, 'esp32') + id: ota_dir + env: + PIO_PLATFORM: ${{ inputs.platform }} + run: | + if [ "$PIO_PLATFORM" = "esp32s3" ]; then + echo "src=firmware-s3.bin" >> $GITHUB_OUTPUT + echo "tgt=release/bleota-s3.bin" >> $GITHUB_OUTPUT + elif [ "$PIO_PLATFORM" = "esp32c3" ] || [ "$PIO_PLATFORM" = "esp32c6" ]; then + echo "src=firmware-c3.bin" >> $GITHUB_OUTPUT + echo "tgt=release/bleota-c3.bin" >> $GITHUB_OUTPUT + elif [ "$PIO_PLATFORM" = "esp32" ]; then + echo "src=firmware.bin" >> $GITHUB_OUTPUT + echo "tgt=release/bleota.bin" >> $GITHUB_OUTPUT + fi + + - name: Build ${{ inputs.platform }} + id: build + uses: meshtastic/gh-action-firmware@main + with: + pio_platform: ${{ inputs.platform }} + pio_env: ${{ inputs.pio_env }} + pio_target: build + ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }} + ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }} + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip + overwrite: true + path: | + release/*.bin + release/*.elf + release/*.uf2 + release/*.hex + release/*-ota.zip diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml deleted file mode 100644 index 786508f86..000000000 --- a/.github/workflows/build_nrf52.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Build NRF52 - -on: - workflow_call: - inputs: - board: - required: true - type: string - -permissions: read-all - -jobs: - build-nrf52: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build NRF52 - id: build - uses: ./.github/actions/build-variant - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - build-script-path: bin/build-nrf52.sh - artifact-paths: | - release/*.hex - release/*.uf2 - release/*.elf - release/*.zip - arch: nrf52840 diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml deleted file mode 100644 index 53fee34d2..000000000 --- a/.github/workflows/build_rpi2040.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Build RPI2040 - -on: - workflow_call: - inputs: - board: - required: true - type: string - -permissions: read-all - -jobs: - build-rpi2040: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build Raspberry Pi 2040 - id: build - uses: ./.github/actions/build-variant - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - build-script-path: bin/build-rpi2040.sh - artifact-paths: | - release/*.uf2 - release/*.elf - arch: rp2040 diff --git a/.github/workflows/build_stm32.yml b/.github/workflows/build_stm32.yml deleted file mode 100644 index dc469d994..000000000 --- a/.github/workflows/build_stm32.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Build STM32 - -on: - workflow_call: - inputs: - board: - required: true - type: string - -permissions: read-all - -jobs: - build-stm32: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build STM32WL - id: build - uses: ./.github/actions/build-variant - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - board: ${{ inputs.board }} - build-script-path: bin/build-stm32.sh - artifact-paths: | - release/*.hex - release/*.bin - release/*.elf - arch: stm32 diff --git a/.github/workflows/daily_packaging.yml b/.github/workflows/daily_packaging.yml index 18939d567..eb61554f2 100644 --- a/.github/workflows/daily_packaging.yml +++ b/.github/workflows/daily_packaging.yml @@ -30,7 +30,11 @@ jobs: strategy: fail-fast: false matrix: - series: [plucky, oracular, noble, jammy] + series: + - jammy # 22.04 + - noble # 24.04 + - plucky # 25.04 + - questing # 25.10 uses: ./.github/workflows/package_ppa.yml with: ppa_repo: ppa:meshtastic/daily diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 03e61d572..a98bdc011 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -30,18 +30,31 @@ jobs: strategy: fail-fast: false matrix: - arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32, check] - runs-on: ubuntu-latest + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + - check + runs-on: ubuntu-24.04 steps: - - id: checkout - uses: actions/checkout@v4 - name: Checkout base - - id: jsonStep + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + cache: pip + - run: pip install -U platformio + - name: Generate matrix + id: jsonStep run: | if [[ "$GITHUB_HEAD_REF" == "" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr) fi echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT @@ -52,9 +65,25 @@ jobs: esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} rp2040: ${{ steps.jsonStep.outputs.rp2040 }} + rp2350: ${{ steps.jsonStep.outputs.rp2350 }} stm32: ${{ steps.jsonStep.outputs.stm32 }} check: ${{ steps.jsonStep.outputs.check }} + version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Get release version string + run: | + echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT + id: version + env: + BUILD_LOCATION: local + outputs: + long: ${{ steps.version.outputs.long }} + deb: ${{ steps.version.outputs.deb }} + check: needs: setup strategy: @@ -72,69 +101,95 @@ jobs: run: bin/check-all.sh ${{ matrix.board }} build-esp32: - needs: setup + needs: [setup, version] strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32) }} - uses: ./.github/workflows/build_esp32.yml + uses: ./.github/workflows/build_firmware.yml with: - board: ${{ matrix.board }} + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32 - build-esp32-s3: - needs: setup + build-esp32s3: + needs: [setup, version] strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_esp32_s3.yml + uses: ./.github/workflows/build_firmware.yml with: - board: ${{ matrix.board }} + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32s3 - build-esp32-c3: - needs: setup + build-esp32c3: + needs: [setup, version] strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_esp32_c3.yml + uses: ./.github/workflows/build_firmware.yml with: - board: ${{ matrix.board }} + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32c3 - build-esp32-c6: - needs: setup + build-esp32c6: + needs: [setup, version] strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_esp32_c6.yml + uses: ./.github/workflows/build_firmware.yml with: - board: ${{ matrix.board }} + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32c6 - build-nrf52: - needs: setup + build-nrf52840: + needs: [setup, version] strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_nrf52.yml + uses: ./.github/workflows/build_firmware.yml with: - board: ${{ matrix.board }} + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: nrf52840 - build-rpi2040: - needs: setup + build-rp2040: + needs: [setup, version] strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_rpi2040.yml + uses: ./.github/workflows/build_firmware.yml with: - board: ${{ matrix.board }} + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: rp2040 + + build-rp2350: + needs: [setup, version] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: rp2350 build-stm32: - needs: setup + needs: [setup, version] strategy: fail-fast: false matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_stm32.yml + uses: ./.github/workflows/build_firmware.yml with: - board: ${{ matrix.board }} + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: stm32 build-debian-src: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/build_debian_src.yml with: series: UNRELEASED @@ -209,16 +264,26 @@ jobs: strategy: fail-fast: false matrix: - arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32] + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 runs-on: ubuntu-latest needs: [ + version, build-esp32, - build-esp32-s3, - build-esp32-c3, - build-esp32-c6, - build-nrf52, - build-rpi2040, + build-esp32s3, + build-esp32c3, + build-esp32c6, + build-nrf52840, + build-rp2040, + build-rp2350, build-stm32, ] steps: @@ -237,17 +302,13 @@ jobs: - name: Display structure of downloaded files run: ls -R - - name: Get release version string - run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - name: Move files up run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat - name: Repackage in single firmware zip uses: actions/upload-artifact@v4 with: - name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} + name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} overwrite: true path: | ./firmware-*.bin @@ -263,7 +324,7 @@ jobs: - uses: actions/download-artifact@v4 with: - name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} + name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true path: ./output @@ -277,12 +338,12 @@ jobs: chmod +x ./output/device-update.sh - name: Zip firmware - run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output + run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output - name: Repackage in single elfs zip uses: actions/upload-artifact@v4 with: - name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip overwrite: true path: ./*.elf retention-days: 30 @@ -290,8 +351,8 @@ jobs: - uses: scruplelesswizard/comment-artifact@main if: ${{ github.event_name == 'pull_request' }} with: - name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} - description: "Download firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" + name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} + description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" github-token: ${{ secrets.GITHUB_TOKEN }} release-artifacts: @@ -300,6 +361,7 @@ jobs: outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} needs: + - version - gather-artifacts - build-debian-src - package-pio-deps-native-tft @@ -312,44 +374,36 @@ jobs: with: python-version: 3.x - - 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 - - name: Create release uses: softprops/action-gh-release@v2 id: create_release with: draft: true prerelease: true - name: Meshtastic Firmware ${{ steps.version.outputs.long }} Alpha - tag_name: v${{ steps.version.outputs.long }} + name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha + tag_name: v${{ needs.version.outputs.long }} body: | Autogenerated by github action, developer should edit as required before publishing... - name: Download source deb uses: actions/download-artifact@v4 with: - pattern: firmware-debian-${{ steps.version.outputs.deb }}~UNRELEASED-src + pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src merge-multiple: true path: ./output/debian-src - name: Download `native-tft` pio deps uses: actions/download-artifact@v4 with: - pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }} + pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} merge-multiple: true path: ./output/pio-deps-native-tft - name: Zip Linux sources working-directory: output run: | - zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src - zip -9 -r ./platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip ./pio-deps-native-tft + zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src + zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft # For diagnostics - name: Display structure of downloaded files @@ -359,8 +413,8 @@ jobs: # Only run when targeting master branch with workflow_dispatch if: ${{ github.ref_name == 'master' }} run: | - gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip - gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip + gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip + gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -368,10 +422,18 @@ jobs: strategy: fail-fast: false matrix: - arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32] + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 runs-on: ubuntu-latest if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [release-artifacts] + needs: [release-artifacts, version] steps: - name: Checkout uses: actions/checkout@v4 @@ -381,13 +443,9 @@ jobs: with: python-version: 3.x - - name: Get release version string - run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - uses: actions/download-artifact@v4 with: - pattern: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} + pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true path: ./output @@ -400,16 +458,16 @@ jobs: chmod +x ./output/device-update.sh - name: Zip firmware - run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output + run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output - uses: actions/download-artifact@v4 with: - name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip merge-multiple: true path: ./elfs - name: Zip debug elfs - run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./elfs + run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./elfs # For diagnostics - name: Display structure of downloaded files @@ -419,17 +477,18 @@ jobs: # Only run when targeting master branch with workflow_dispatch if: ${{ github.ref_name == 'master' }} run: | - gh release upload v${{ steps.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip - gh release upload v${{ steps.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip + gh release upload v${{ needs.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish-firmware: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [release-firmware] + needs: [release-firmware, version] env: - targets: esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,stm32 + targets: |- + esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 steps: - name: Checkout uses: actions/checkout@v4 @@ -439,13 +498,9 @@ jobs: with: python-version: 3.x - - name: Get release version string - run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - id: version - - uses: actions/download-artifact@v4 with: - pattern: firmware-{${{ env.targets }}}-${{ steps.version.outputs.long }} + pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} merge-multiple: true path: ./publish @@ -459,9 +514,9 @@ jobs: external_repository: meshtastic/meshtastic.github.io publish_branch: master publish_dir: ./publish - destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ steps.version.outputs.long }} + destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }} keep_files: true user_name: github-actions[bot] user_email: github-actions[bot]@users.noreply.github.com - commit_message: ${{ steps.version.outputs.long }} + commit_message: ${{ needs.version.outputs.long }} enable_jekyll: true diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 36ec22f17..309772b12 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -8,6 +8,7 @@ permissions: read-all jobs: trunk_check: + if: github.repository == 'meshtastic/firmware' name: Trunk Check and Upload runs-on: ubuntu-24.04 @@ -21,6 +22,7 @@ jobs: trunk-token: ${{ secrets.TRUNK_TOKEN }} trunk_upgrade: + if: github.repository == 'meshtastic/firmware' # See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#automatic-upgrades name: Trunk Upgrade (PR) runs-on: ubuntu-24.04 diff --git a/.github/workflows/pr_enforce_labels.yml b/.github/workflows/pr_enforce_labels.yml new file mode 100644 index 000000000..93114e2c7 --- /dev/null +++ b/.github/workflows/pr_enforce_labels.yml @@ -0,0 +1,24 @@ +name: Check PR Labels + +on: + pull_request: + types: [opened, edited, labeled, unlabeled, synchronize, reopened] + +permissions: + pull-requests: read + contents: read + +jobs: + check-label: + runs-on: ubuntu-24.04 + steps: + - name: Check for PR labels + uses: actions/github-script@v7 + with: + script: | + const labels = context.payload.pull_request.labels.map(label => label.name); + const requiredLabels = ['bugfix', 'enhancement', 'hardware-support', 'dependencies', 'submodules', 'github_actions', 'trunk']; + const hasRequiredLabel = labels.some(label => requiredLabels.includes(label)); + if (!hasRequiredLabel) { + core.setFailed(`PR must have at least one of the following labels before it can be merged: ${requiredLabels.join(', ')}.`); + } diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml index aac57fcbf..e52e67227 100644 --- a/.github/workflows/release_channels.yml +++ b/.github/workflows/release_channels.yml @@ -20,7 +20,11 @@ jobs: strategy: fail-fast: false matrix: - series: [plucky, oracular, noble, jammy] + series: + - jammy # 22.04 + - noble # 24.04 + - plucky # 25.04 + # - questing # 25.10 uses: ./.github/workflows/package_ppa.yml with: ppa_repo: |- @@ -99,8 +103,9 @@ jobs: with: base: ${{ github.event.repository.default_branch }} branch: create-pull-request/bump-version + labels: github_actions title: Bump release version - commit-message: automated bumps + commit-message: Automated version bumps add-paths: | version.properties debian/changelog diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml index d7eef29b4..e391aa07b 100644 --- a/.github/workflows/sec_sast_semgrep_cron.yml +++ b/.github/workflows/sec_sast_semgrep_cron.yml @@ -13,6 +13,7 @@ permissions: jobs: semgrep-full: + if: github.repository == 'meshtastic/firmware' runs-on: ubuntu-24.04 container: image: semgrep/semgrep diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml index 5ae6bdfc9..5a11fdfa8 100644 --- a/.github/workflows/stale_bot.yml +++ b/.github/workflows/stale_bot.yml @@ -11,6 +11,7 @@ permissions: jobs: stale_issues: + if: github.repository == 'meshtastic/firmware' name: Close Stale Issues runs-on: ubuntu-latest diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml index 536d93665..dc05959fd 100644 --- a/.github/workflows/test_native.yml +++ b/.github/workflows/test_native.yml @@ -143,7 +143,7 @@ jobs: merge-multiple: true - name: Test Report - uses: dorny/test-reporter@v2.1.0 + uses: dorny/test-reporter@v2.1.1 with: name: PlatformIO Tests path: testreport.xml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 28b6a40a5..34b28b39c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,9 +12,11 @@ permissions: jobs: native-tests: + if: github.repository == 'meshtastic/firmware' uses: ./.github/workflows/test_native.yml hardware-tests: + if: github.repository == 'meshtastic/firmware' runs-on: test-runner steps: - name: Checkout code diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index ccdcc19ae..3952d9d02 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -34,7 +34,9 @@ jobs: uses: peter-evans/create-pull-request@v7 with: branch: create-pull-request/update-protobufs + labels: submodules title: Update protobufs and classes + commit-message: Update protobufs add-paths: | protobufs src/mesh diff --git a/.gitignore b/.gitignore index b63f431d1..cc742c6c1 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,7 @@ release/ .vscode/extensions.json /compile_commands.json src/mesh/raspihttp/certificate.pem -src/mesh/raspihttp/private_key.pem \ No newline at end of file +src/mesh/raspihttp/private_key.pem + +# Ignore logo (set at build time with platformio-custom.py) +data/boot/logo.* diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 2ddebdf1d..2d4d3fc16 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,15 +8,15 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.447 - - renovate@41.17.2 + - checkov@3.2.451 + - renovate@41.40.0 - prettier@3.6.2 - - trufflehog@3.89.2 + - trufflehog@3.90.1 - yamllint@1.37.1 - - bandit@1.8.5 - - trivy@0.64.0 + - bandit@1.8.6 + - trivy@0.64.1 - taplo@0.9.3 - - ruff@0.12.1 + - ruff@0.12.4 - isort@6.0.1 - markdownlint@0.45.0 - oxipng@9.1.5 @@ -28,7 +28,7 @@ lint: - shellcheck@0.10.0 - black@25.1.0 - git-diff-check - - gitleaks@8.27.2 + - gitleaks@8.28.0 - clang-format@16.0.3 ignore: - linters: [ALL] diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index cbc5e753b..edadd2298 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -49,13 +49,13 @@ lib_deps = ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master - https://github.com/meshtastic/esp32_https_server/archive/896f1771ceb5979987a0b41028bf1b4e7aad419b.zip + https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino h2zero/NimBLE-Arduino@^1.4.3 # renovate: datasource=git-refs depName=libpax packageName=https://github.com/mverch67/libpax gitBranch=master https://github.com/mverch67/libpax/archive/6f52ee989301cdabaeef00bcbf93bff55708ce2f.zip - # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib - lewisxhe/XPowersLib@^0.2.7 + # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib + https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini index 26b5c0f5b..1afb9b547 100644 --- a/arch/esp32/esp32c6.ini +++ b/arch/esp32/esp32c6.ini @@ -28,7 +28,7 @@ lib_deps = ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib - lewisxhe/XPowersLib@^0.2.7 + lewisxhe/XPowersLib@0.3.0 # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 429e010f5..693ab63b7 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -2,7 +2,7 @@ [portduino_base] platform = # renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop - https://github.com/meshtastic/platform-native/archive/681ee029207e9fd040afa223df6e54074cbbe084.zip + https://github.com/meshtastic/platform-native/archive/6cb7a455b440dd0738e8ed74a18136ed5cf7ea63.zip framework = arduino build_src_filter = @@ -30,6 +30,8 @@ lib_deps = lovyan03/LovyanGFX@^1.2.0 # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip + # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library + adafruit/Adafruit seesaw Library@1.7.9 build_flags = ${arduino_base.build_flags} @@ -37,7 +39,7 @@ build_flags = -Isrc/platform/portduino -DRADIOLIB_EEPROM_UNSUPPORTED -DPORTDUINO_LINUX_HARDWARE - -DHAS_UDP_MULTICAST + -DHAS_UDP_MULTICAST=1 -lpthread -lstdc++fs -lbluetooth @@ -48,4 +50,7 @@ build_flags = -std=gnu17 -std=c++17 -lib_ignore = Adafruit NeoPixel +lib_ignore = + Adafruit NeoPixel + Adafruit ST7735 and ST7789 Library + SD diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini index e7a340f92..153ca9f3e 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -16,18 +16,27 @@ build_flags = ${arduino_base.build_flags} -flto -Isrc/platform/stm32wl -g - -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - -DMESHTASTIC_EXCLUDE_INPUTBROKER - -DMESHTASTIC_EXCLUDE_I2C - -DMESHTASTIC_EXCLUDE_POWERMON - -DMESHTASTIC_EXCLUDE_SCREEN - -DMESHTASTIC_EXCLUDE_MQTT - -DMESHTASTIC_EXCLUDE_BLUETOOTH - -DMESHTASTIC_EXCLUDE_GPS - ;-DDEBUG_MUTE + -DMESHTASTIC_EXCLUDE_AUDIO=1 + -DMESHTASTIC_EXCLUDE_ATAK=1 ; ATAK is quite big, disable it for big flash savings. + -DMESHTASTIC_EXCLUDE_INPUTBROKER=1 + -DMESHTASTIC_EXCLUDE_POWERMON=1 + -DMESHTASTIC_EXCLUDE_SCREEN=1 + -DMESHTASTIC_EXCLUDE_MQTT=1 + -DMESHTASTIC_EXCLUDE_BLUETOOTH=1 + -DMESHTASTIC_EXCLUDE_WIFI=1 + -DMESHTASTIC_EXCLUDE_TZ=1 ; Exclude TZ to save some flash space. + -DSERIAL_RX_BUFFER_SIZE=256 ; For GPS - the default of 64 is too small. + -DHAS_SCREEN=0 ; Always disable screen for STM32, it is not supported. + -DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF ; This is REQUIRED for at least traceroute debug prints - without it the length ends up uninitialized. + -DDEBUG_MUTE ; You can #undef DEBUG_MUTE in certain source files if you need the logs. -fmerge-all-constants -ffunction-sections -fdata-sections + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 + -DHAL_DAC_MODULE_ONLY + -DHAL_RNG_MODULE_ENABLED build_src_filter = ${arduino_base.build_src_filter} - - - - - - - - - - - - - - @@ -39,9 +48,9 @@ debug_tool = stlink lib_deps = ${env.lib_deps} ${radiolib_base.lib_deps} + # renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip lib_ignore = - mathertel/OneButton@2.6.1 - Wire + OneButton diff --git a/bin/build-esp32.sh b/bin/build-esp32.sh index 96578e914..92836db23 100755 --- a/bin/build-esp32.sh +++ b/bin/build-esp32.sh @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update -e $1 +platformio pkg install -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* diff --git a/bin/build-firmware.sh b/bin/build-firmware.sh index c53f1b660..fdd7caa11 100644 --- a/bin/build-firmware.sh +++ b/bin/build-firmware.sh @@ -11,7 +11,7 @@ elif (echo $2 | grep -q "nrf52"); then elif (echo $2 | grep -q "stm32"); then bin/build-stm32.sh $1 elif (echo $2 | grep -q "rpi2040"); then - bin/build-rpi2040.sh $1 + bin/build-rp2xx0.sh $1 else echo "Unknown target $2" exit 1 diff --git a/bin/build-native.sh b/bin/build-native.sh index 51379ad76..fff86e87e 100755 --- a/bin/build-native.sh +++ b/bin/build-native.sh @@ -25,7 +25,7 @@ mkdir -p $OUTDIR/ rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -pio pkg update --environment "$PIO_ENV" || platformioFailed +pio pkg install --environment "$PIO_ENV" || platformioFailed pio run --environment "$PIO_ENV" || platformioFailed cp ".pio/build/$PIO_ENV/program" "$OUTDIR/meshtasticd_linux_$(uname -m)" cp bin/native-install.* $OUTDIR diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index 9d0b3dfdd..deca209d2 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update -e $1 +platformio pkg install -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* diff --git a/bin/build-rpi2040.sh b/bin/build-rp2xx0.sh similarity index 96% rename from bin/build-rpi2040.sh rename to bin/build-rp2xx0.sh index dad6a7e67..cb4865914 100755 --- a/bin/build-rpi2040.sh +++ b/bin/build-rp2xx0.sh @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update -e $1 +platformio pkg install -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* diff --git a/bin/build-stm32.sh b/bin/build-stm32wl.sh similarity index 95% rename from bin/build-stm32.sh rename to bin/build-stm32wl.sh index 76c5a75fb..f62df4842 100755 --- a/bin/build-stm32.sh +++ b/bin/build-stm32wl.sh @@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale -platformio pkg update -e $1 +platformio pkg install -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" rm -f .pio/build/$1/firmware.* diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index b40fb85a5..b4cc81792 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -199,6 +199,10 @@ HostMetrics: # UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString +Config: +# DisplayMode: TWOCOLOR # uncomment to force BaseUI +# DisplayMode: COLOR # uncomment to force MUI + General: MaxNodes: 200 MaxMessageQueue: 100 diff --git a/bin/config.d/display-waveshare-1-44.yaml b/bin/config.d/display-waveshare-1-44.yaml index 1d85a4a3b..d37f6cf6a 100644 --- a/bin/config.d/display-waveshare-1-44.yaml +++ b/bin/config.d/display-waveshare-1-44.yaml @@ -22,5 +22,5 @@ Input: TrackballLeft: 5 TrackballRight: 26 TrackballPress: 13 - + TrackballDirection: FALLING # User: 21 diff --git a/bin/config.d/lora-ws-raspberry-pico-to-orangepi-03.yaml b/bin/config.d/lora-ws-raspberry-pico-to-orangepi-03.yaml new file mode 100644 index 000000000..37d7e27d2 --- /dev/null +++ b/bin/config.d/lora-ws-raspberry-pico-to-orangepi-03.yaml @@ -0,0 +1,52 @@ +# https://www.waveshare.com/pico-lora-sx1262-868m.htm +# http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero-3.html +# +# See Orange Pi Zero3 manual, chapter 3.16, page 124 for 26-pin header pinout +# +# Pin Connection +# Waveshare Orange Pi Zero3 +# 36 3.3V 17 +# 15 MOSI 19 +# 16 MISO 21 +# 14 CLK 23 +# 38 GND 25 +# 4 BUSY 18 +# 20 RESET 22 +# 5 CS 24 +# 26 DIO1/IRQ 26 + +Lora: + Module: sx1262 # Waveshare Raspberry Pico Lora module + DIO2_AS_RF_SWITCH: true + DIO3_TCXO_VOLTAGE: true + # Specify either the spidev1_1 or the CS below, not both! + # On DietPi Linux, when using the user overlay dietpi-spi1_1.dtbo, CS will be configured with spidev1.1 + spidev: spidev1.1 # See Orange Pi Zero3 manual, chapter 3.18.3, page 130 +# CS: # CS PIN_24 -> chip 1, line 233 +# pin: 24 +# gpiochip: 1 +# line: 233 + SCK: # SCK PIN_23 -> chip 1, line 230 + pin: 23 + gpiochip: 1 + line: 230 + Busy: # BUSY PIN_18 -> chip 1, line 78 + pin: 18 + gpiochip: 1 + line: 78 + MOSI: # MOSI PIN_19 -> chip 1, line 231 + pin: 19 + gpiochip: 1 + line: 231 + MISO: # MISO PIN_21 -> chip 1, line 232 + pin: 21 + gpiochip: 1 + line: 232 + Reset: # NRST PIN_22 -> chip 1, line 71 + pin: 22 + gpiochip: 1 + line: 71 + IRQ: # DIO1 PIN_26 -> chip 1, line 74 + pin: 26 + gpiochip: 1 + line: 74 diff --git a/bin/device-install.sh b/bin/device-install.sh index 42d0c4089..4674113b6 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -7,12 +7,7 @@ MCU="" # Variant groups BIGDB_8MB=( - # Check if FILENAME contains "-tft-" and set target partitionScheme accordingly. -if [[ $FILENAME == *"-tft-"* ]]; then - TFT_BUILD=true -fi - -# Extract BASENAME from %FILENAME% for later use.r-s3" + "picomputer-s3" "unphone" "seeed-sensecap-indicator" "crowpanel-esp32s3" diff --git a/bin/device-update.sh b/bin/device-update.sh index 6adfe4e0e..ce0b5e434 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -30,6 +30,17 @@ Flash image file to device, leave existing system intact." EOF } +# Check for --change-mode and remove it from arguments +NEW_ARGS=() +for arg in "$@"; do + if [ "$arg" = "--change-mode" ]; then + CHANGE_MODE=true + else + NEW_ARGS+=("$arg") + fi +done + +set -- "${NEW_ARGS[@]}" while getopts ":hp:P:f:" opt; do case "${opt}" in @@ -43,9 +54,6 @@ while getopts ":hp:P:f:" opt; do ;; f) FILENAME=${OPTARG} ;; - --change-mode) - CHANGE_MODE=true - ;; *) echo "Invalid flag." show_help >&2 @@ -55,7 +63,7 @@ while getopts ":hp:P:f:" opt; do done shift "$((OPTIND-1))" -if [[ $CHANGE_MODE == true ]]; then +if [ "$CHANGE_MODE" = true ]; then $ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status exit 0 fi diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py index 0ce6b0f6b..aaa76aa45 100755 --- a/bin/generate_ci_matrix.py +++ b/bin/generate_ci_matrix.py @@ -2,50 +2,71 @@ """Generate the CI matrix.""" -import configparser import json -import os import sys import random - -rootdir = "variants/" +import re +from platformio.project.config import ProjectConfig options = sys.argv[1:] outlist = [] if len(options) < 1: - print(json.dumps(outlist)) - exit() + print(json.dumps(outlist)) + exit(1) -for subdir, dirs, files in os.walk(rootdir): - for file in files: - if file == "platformio.ini": - config = configparser.ConfigParser() - config.read(subdir + "/" + file) - for c in config.sections(): - if c.startswith("env:"): - section = config[c].name[4:] - if "extends" in config[config[c].name]: - if options[0] + "_base" in config[config[c].name]["extends"]: - if "board_level" in config[config[c].name]: - if ( - config[config[c].name]["board_level"] == "extra" - ) & ("extra" in options): - outlist.append(section) - else: - outlist.append(section) - # Add the TFT variants if the base variant is selected - elif section.replace("-tft", "") in outlist and config[config[c].name].get("board_level") != "extra": - outlist.append(section) - elif section.replace("-inkhud", "") in outlist and config[config[c].name].get("board_level") != "extra": - outlist.append(section) - if "board_check" in config[config[c].name]: - if (config[config[c].name]["board_check"] == "true") & ( - "check" in options - ): - outlist.append(section) -if ("quick" in options) & (len(outlist) > 3): - print(json.dumps(random.sample(outlist, 3))) +cfg = ProjectConfig.get_instance() +pio_envs = cfg.envs() + +# Gather all PlatformIO environments for filtering later +all_envs = [] +for pio_env in pio_envs: + env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags') + env_platform = None + for flag in env_build_flags: + # Extract the platform from the build flags + # Example flag: -I variants/esp32s3/heltec-v3 + match = re.search(r"-I\s?variants/([^/]+)", flag) + if match: + env_platform = match.group(1) + break + # Intentionally fail if platform cannot be determined + if not env_platform: + print(f"Error: Could not determine platform for environment '{pio_env}'") + exit(1) + # Store env details as a dictionary, and add to 'all_envs' list + env = { + 'name': pio_env, + 'platform': env_platform, + 'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None), + 'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False)) + } + all_envs.append(env) + +# Filter outputs based on options +# Check is mutually exclusive with other options (except 'pr') +if "check" in options: + for env in all_envs: + if env['board_check']: + if "pr" in options: + if env['board_level'] == 'pr': + outlist.append(env['name']) + else: + outlist.append(env['name']) +# Filter (non-check) builds by platform else: - print(json.dumps(outlist)) \ No newline at end of file + for env in all_envs: + if options[0] == env['platform']: + # Always include board_level = 'pr' + if env['board_level'] == 'pr': + outlist.append(env['name']) + # Include board_level = 'extra' when requested + elif "extra" in options and env['board_level'] == "extra": + outlist.append(env['name']) + # If no board level is specified, include in release builds (not PR) + elif "pr" not in options and not env['board_level']: + outlist.append(env['name']) + +# Return as a JSON list +print(json.dumps(outlist)) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index ed57386a3..116155807 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,15 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.4 + + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.3 + + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.1 diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index 600f9447f..fc1b4bc2e 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -3,6 +3,7 @@ # trunk-ignore-all(flake8/F821): For SConstruct imports import sys from os.path import join +import subprocess import json import re @@ -92,6 +93,17 @@ prefsLoc = projenv["PROJECT_DIR"] + "/version.properties" verObj = readProps(prefsLoc) print("Using meshtastic platformio-custom.py, firmware version " + verObj["long"] + " on " + env.get("PIOENV")) +# get repository owner if git is installed +try: + r_owner = ( + subprocess.check_output(["git", "config", "--get", "remote.origin.url"]) + .decode("utf-8") + .strip().split("/") + ) + repo_owner = r_owner[-2] + "/" + r_owner[-1].replace(".git", "") +except subprocess.CalledProcessError: + repo_owner = "unknown" + jsonLoc = env["PROJECT_DIR"] + "/userPrefs.jsonc" with open(jsonLoc) as f: jsonStr = re.sub("//.*","", f.read(), flags=re.MULTILINE) @@ -117,6 +129,7 @@ flags = [ "-DAPP_VERSION=" + verObj["long"], "-DAPP_VERSION_SHORT=" + verObj["short"], "-DAPP_ENV=" + env.get("PIOENV"), + "-DAPP_REPO=" + repo_owner, ] + pref_flags print ("Using flags:") @@ -131,3 +144,33 @@ for lb in env.GetLibBuilders(): if lb.name == "meshtastic-device-ui": lb.env.Append(CPPDEFINES=[("APP_VERSION", verObj["long"])]) break + +# Get the display resolution from macros +def get_display_resolution(build_flags): + # Check "DISPLAY_SIZE" to determine the screen resolution + for flag in build_flags: + if isinstance(flag, tuple) and flag[0] == "DISPLAY_SIZE": + screen_width, screen_height = map(int, flag[1].split("x")) + return screen_width, screen_height + print("No screen resolution defined in build_flags. Please define DISPLAY_SIZE.") + exit(1) + +def load_boot_logo(source, target, env): + build_flags = env.get("CPPDEFINES", []) + logo_w, logo_h = get_display_resolution(build_flags) + print(f"TFT build with {logo_w}x{logo_h} resolution detected") + + # Load the boot logo from `branding/logo_x.png` if it exists + source_path = join(env["PROJECT_DIR"], "branding", f"logo_{logo_w}x{logo_h}.png") + dest_dir = join(env["PROJECT_DIR"], "data", "boot") + dest_path = join(dest_dir, "logo.png") + if env.File(source_path).exists(): + print(f"Loading boot logo from {source_path}") + # Prepare the destination + env.Execute(f"mkdir -p {dest_dir} && rm -f {dest_path}") + # Copy the logo to the `data/boot` directory + env.Execute(f"cp {source_path} {dest_path}") + +# Load the boot logo on TFT builds +if ("HAS_TFT", 1) in env.get("CPPDEFINES", []): + env.AddPreAction('$BUILD_DIR/littlefs.bin', load_boot_logo) diff --git a/boards/heltec_mesh_node_t114.json b/boards/heltec_mesh_node_t114.json index d516c9701..eda0ac3df 100644 --- a/boards/heltec_mesh_node_t114.json +++ b/boards/heltec_mesh_node_t114.json @@ -10,7 +10,8 @@ "hwids": [ ["0x239A", "0x4405"], ["0x239A", "0x0029"], - ["0x239A", "0x002A"] + ["0x239A", "0x002A"], + ["0x2886", "0x1667"] ], "usb_product": "HT-n5262", "mcu": "nrf52840", diff --git a/boards/seeed_wio_tracker_L1.json b/boards/seeed_wio_tracker_L1.json index 7c7bc62fa..e2bb93573 100644 --- a/boards/seeed_wio_tracker_L1.json +++ b/boards/seeed_wio_tracker_L1.json @@ -7,7 +7,10 @@ "cpu": "cortex-m4", "extra_flags": "-DARDUINO_MDBT50Q_RX -DNRF52840_XXAA", "f_cpu": "64000000L", - "hwids": [["0x2886", "0x1668"]], + "hwids": [ + ["0x2886", "0x1668"], + ["0x2886", "0x1667"] + ], "usb_product": "TRACKER L1", "mcu": "nrf52840", "variant": "seeed_wio_tracker_L1", diff --git a/boards/t-deck-pro.json b/boards/t-deck-pro.json new file mode 100644 index 000000000..2f4bd594a --- /dev/null +++ b/boards/t-deck-pro.json @@ -0,0 +1,43 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_qspi", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "LilyGo T-Deck Pro S3 (16M Flash 8M QSPI PSRAM )", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "monitor": { + "speed": 115200 + }, + "url": "https://lilygo.cc/products/t-deck-pro", + "vendor": "LilyGo" +} diff --git a/boards/tracker-t1000-e.json b/boards/tracker-t1000-e.json index 2be716e22..9e8870041 100644 --- a/boards/tracker-t1000-e.json +++ b/boards/tracker-t1000-e.json @@ -11,7 +11,8 @@ ["0x239A", "0x8029"], ["0x239A", "0x0029"], ["0x239A", "0x002A"], - ["0x239A", "0x802A"] + ["0x239A", "0x802A"], + ["0x2886", "0x0057"] ], "usb_product": "T1000-E-BOOT", "mcu": "nrf52840", diff --git a/boards/wiscore_rak3312.json b/boards/wiscore_rak3312.json new file mode 100644 index 000000000..192e1c03c --- /dev/null +++ b/boards/wiscore_rak3312.json @@ -0,0 +1,41 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DRAK3312", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1", + "-DBOARD_HAS_PSRAM" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "dio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "rak3312" + }, + "connectivity": ["wifi", "bluetooth"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "WisCore RAK3312 Board", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.rakwireless.com/en-us", + "vendor": "rakwireless" +} diff --git a/branding/README.md b/branding/README.md new file mode 100644 index 000000000..3a558bf20 --- /dev/null +++ b/branding/README.md @@ -0,0 +1,17 @@ +# Meshtastic Branding / Whitelabeling + +This directory is consumed during the creation of **event** firmware. + +`bin/platformio-custom.py` determines the display resolution, and locates the corresponding `logo_x.png`. + +Ex: + +- `logo_800x480.png` +- `logo_480x480.png` +- `logo_480x320.png` +- `logo_320x480.png` +- `logo_320x240.png` + +This file is copied to `data/boot/logo.png` before filesytem image compilation. + +For additional examples see the [`event/defcon33` branch](https://github.com/meshtastic/firmware/tree/event/defcon33). diff --git a/debian/changelog b/debian/changelog index 70a01bab4..02a32f2f1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -meshtasticd (2.7.1.0) UNRELEASED; urgency=medium +meshtasticd (2.7.4.0) UNRELEASED; urgency=medium [ Austin Lane ] * Initial packaging @@ -25,4 +25,13 @@ meshtasticd (2.7.1.0) UNRELEASED; urgency=medium [ ] * GitHub Actions Automatic version bump - -- Fri, 27 Jun 2025 20:12:21 +0000 + [ ] + * GitHub Actions Automatic version bump + + [ Ubuntu ] + * GitHub Actions Automatic version bump + + [ ] + * GitHub Actions Automatic version bump + + -- Sat, 19 Jul 2025 11:36:55 +0000 diff --git a/platformio.ini b/platformio.ini index a275840d5..deaf9658d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -6,7 +6,8 @@ default_envs = tbeam extra_configs = arch/*/*.ini - variants/*/platformio.ini + variants/*/*/platformio.ini + variants/*/diy/*/platformio.ini src/graphics/niche/InkHUD/PlatformioConfig.ini description = Meshtastic @@ -49,11 +50,11 @@ build_flags = -Wno-missing-field-initializers -DMESHTASTIC_EXCLUDE_DROPZONE=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 -DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1 - -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware + -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware -DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1 -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage - #-DBUILD_EPOCH=$UNIX_TIME - #-D OLED_PL=1 + #-DBUILD_EPOCH=$UNIX_TIME + #-D OLED_PL=1 monitor_speed = 115200 monitor_filters = direct @@ -105,18 +106,18 @@ lib_deps = [radiolib_base] lib_deps = # renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib - jgromes/RadioLib@7.2.0 + jgromes/RadioLib@7.2.1 [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/4b7bf369adfa5a7bd419fa8293d21206576d52d0.zip + https://github.com/meshtastic/device-ui/archive/c75d545bf9e8d1fe20051c319f427f711113ff22.zip ; Common libs for environmental measurements in telemetry module [environmental_base] lib_deps = # renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO - adafruit/Adafruit BusIO@1.17.1 + adafruit/Adafruit BusIO@1.17.2 # renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor adafruit/Adafruit Unified Sensor@1.1.15 # renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library @@ -130,7 +131,7 @@ lib_deps = # renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library adafruit/Adafruit MCP9808 Library@2.0.2 # renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library - adafruit/Adafruit INA260 Library@1.5.2 + adafruit/Adafruit INA260 Library@1.5.3 # renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219 adafruit/Adafruit INA219@1.2.3 # renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor @@ -169,8 +170,8 @@ lib_deps = adafruit/Adafruit PCT2075@1.0.5 # renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150 dfrobot/DFRobot_BMM150@1.0.0 - - ; (not included in native / portduino) + +; (not included in native / portduino) [environmental_extra] lib_deps = # renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library @@ -197,4 +198,7 @@ lib_deps = boschsensortec/BME68x Sensor Library@1.3.40408 # 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 - sensirion/Sensirion I2C SCD4x@^0.4.0 + # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core + sensirion/Sensirion Core@0.7.1 + # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x + sensirion/Sensirion I2C SCD4x@1.1.0 diff --git a/protobufs b/protobufs index 86c738e80..9bac2886f 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 86c738e8061ec09625ee52bc61ba862414384ce6 +Subproject commit 9bac2886f9344f25716921467a82e8b0326107cd diff --git a/src/Power.cpp b/src/Power.cpp index fb5db416e..b489bc33c 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -20,6 +20,11 @@ #include "meshUtils.h" #include "sleep.h" +#if defined(ARCH_PORTDUINO) +#include "api/WiFiServerAPI.h" +#include "input/LinuxInputImpl.h" +#endif + // Working USB detection for powered/charging states on the RAK platform #ifdef NRF_APM #include "nrfx_power.h" @@ -103,7 +108,7 @@ NullSensor ina3221Sensor; #endif -#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_STM32WL) +#if !MESHTASTIC_EXCLUDE_I2C #include "modules/Telemetry/Sensor/MAX17048Sensor.h" #include extern std::pair nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1]; @@ -120,6 +125,15 @@ NullSensor max17048Sensor; RAK9154Sensor rak9154Sensor; #endif +#ifdef HAS_PPM +// note: XPOWERS_CHIP_XXX must be defined in variant.h +#include +#endif + +#ifdef HAS_BQ27220 +#include "bq27220.h" +#endif + #ifdef HAS_PMU XPowersLibInterface *PMU = NULL; #else @@ -278,7 +292,7 @@ class AnalogBatteryLevel : public HasBatteryLevel } #endif -#if HAS_TELEMETRY && !defined(ARCH_STM32WL) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#if HAS_TELEMETRY && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (hasINA()) { return getINAVoltage(); } @@ -456,8 +470,7 @@ class AnalogBatteryLevel : public HasBatteryLevel #ifdef EXT_CHRG_DETECT return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value; #else -#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_STM32WL) && \ - !defined(DISABLE_INA_CHARGING_DETECTION) +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) if (hasINA()) { // get current flow from INA sensor - negative value means power flowing into the battery // default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD @@ -503,7 +516,7 @@ class AnalogBatteryLevel : public HasBatteryLevel } #endif -#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_STM32WL) +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR uint16_t getINAVoltage() { if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) { @@ -666,6 +679,8 @@ bool Power::setup() found = true; } else if (lipoInit()) { found = true; + } else if (lipoChargerInit()) { + found = true; } else if (analogInit()) { found = true; } @@ -680,9 +695,59 @@ bool Power::setup() return found; } +void Power::powerCommandsCheck() +{ + if (rebootAtMsec && millis() > rebootAtMsec) { + LOG_INFO("Rebooting"); + reboot(); + } + + if (shutdownAtMsec && millis() > shutdownAtMsec) { + shutdownAtMsec = 0; + shutdown(); + } +} + +void Power::reboot() +{ + notifyReboot.notifyObservers(NULL); +#if defined(ARCH_ESP32) + ESP.restart(); +#elif defined(ARCH_NRF52) + NVIC_SystemReset(); +#elif defined(ARCH_RP2040) + rp2040.reboot(); +#elif defined(ARCH_PORTDUINO) + deInitApiServer(); + if (aLinuxInputImpl) + aLinuxInputImpl->deInit(); + SPI.end(); + Wire.end(); + Serial1.end(); + if (screen) + delete screen; + LOG_DEBUG("final reboot!"); + reboot(); +#elif defined(ARCH_STM32WL) + HAL_NVIC_SystemReset(); +#else + rebootAtMsec = -1; + LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied"); +#endif +} + void Power::shutdown() { - LOG_INFO("Shutting Down"); + +#if HAS_SCREEN + if (screen) { + screen->showSimpleBanner("Shutting Down...", 0); // stays on screen + } +#endif +#if !defined(ARCH_STM32WL) + playShutdownMelody(); +#endif + nodeDB->saveToDisk(); #if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040) #ifdef PIN_LED1 @@ -694,7 +759,11 @@ void Power::shutdown() #ifdef PIN_LED3 ledOff(PIN_LED3); #endif - doDeepSleep(DELAY_FOREVER, false, false); + doDeepSleep(DELAY_FOREVER, false, true); +#elif defined(ARCH_PORTDUINO) + exit(EXIT_SUCCESS); +#else + LOG_WARN("FIXME implement shutdown for this platform"); #endif } @@ -1161,7 +1230,7 @@ bool Power::axpChipInit() #endif } -#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#if !MESHTASTIC_EXCLUDE_I2C && __has_include() /** * Wrapper class for an I2C MAX17048 Lipo battery sensor. @@ -1238,3 +1307,144 @@ bool Power::lipoInit() return false; } #endif + +#if defined(HAS_PPM) && HAS_PPM + +/** + * Adapter class for BQ25896/BQ27220 Lipo battery charger. + */ +class LipoCharger : public HasBatteryLevel +{ + private: + XPowersPPM *ppm = nullptr; + BQ27220 *bq = nullptr; + + public: + /** + * Init the I2C BQ25896 Lipo battery charger + */ + bool runOnce() + { + if (ppm == nullptr) { + ppm = new XPowersPPM; + bool result = ppm->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR); + if (result) { + LOG_INFO("PPM BQ25896 init succeeded"); + // Set the minimum operating voltage. Below this voltage, the PPM will protect + // ppm->setSysPowerDownVoltage(3100); + + // Set input current limit, default is 500mA + // ppm->setInputCurrentLimit(800); + + // Disable current limit pin + // ppm->disableCurrentLimitPin(); + + // Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV + ppm->setChargeTargetVoltage(4288); + + // Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA + // ppm->setPrechargeCurr(64); + + // The premise is that limit pin is disabled, or it will + // only follow the maximum charging current set by limit pin. + // Set the charging current , Range:0~5056mA ,step:64mA + ppm->setChargerConstantCurr(1024); + + // To obtain voltage data, the ADC must be enabled first + ppm->enableMeasure(); + + // Turn on charging function + // If there is no battery connected, do not turn on the charging function + ppm->enableCharge(); + } else { + LOG_WARN("PPM BQ25896 init failed"); + delete ppm; + ppm = nullptr; + return false; + } + } + if (bq == nullptr) { + bq = new BQ27220; + bq->setDefaultCapacity(BQ27220_DESIGN_CAPACITY); + + bool result = bq->init(); + if (result) { + LOG_DEBUG("BQ27220 design capacity: %d", bq->getDesignCapacity()); + LOG_DEBUG("BQ27220 fullCharge capacity: %d", bq->getFullChargeCapacity()); + LOG_DEBUG("BQ27220 remaining capacity: %d", bq->getRemainingCapacity()); + return true; + } else { + LOG_WARN("BQ27220 init failed"); + delete bq; + bq = nullptr; + return false; + } + } + return false; + } + + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + */ + virtual int getBatteryPercent() override + { + return -1; + // return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated + } + + /** + * The raw voltage of the battery in millivolts, or NAN if unknown + */ + virtual uint16_t getBattVoltage() override { return bq->getVoltage(); } + + /** + * return true if there is a battery installed in this unit + */ + virtual bool isBatteryConnect() override { return ppm->getBattVoltage() > 0; } + + /** + * return true if there is an external power source detected + */ + virtual bool isVbusIn() override { return ppm->getVbusVoltage() > 0; } + + /** + * return true if the battery is currently charging + */ + virtual bool isCharging() override + { + bool isCharging = ppm->isCharging(); + if (isCharging) { + LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull()); + } else { + if (!ppm->isVbusIn()) { + LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity()); + } + } + return isCharging; + } +}; + +LipoCharger lipoCharger; + +/** + * Init the Lipo battery charger + */ +bool Power::lipoChargerInit() +{ + bool result = lipoCharger.runOnce(); + LOG_DEBUG("Power::lipoChargerInit lipo sensor is %s", result ? "ready" : "not ready yet"); + if (!result) + return false; + batteryLevel = &lipoCharger; + return true; +} + +#else +/** + * The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel + */ +bool Power::lipoChargerInit() +{ + return false; +} +#endif diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 3b3f8080d..322b877ff 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -72,7 +72,7 @@ extern Power *power; static void shutdownEnter() { LOG_DEBUG("State: SHUTDOWN"); - power->shutdown(); + shutdownAtMsec = millis(); } #include "error.h" diff --git a/src/buzz/BuzzerFeedbackThread.cpp b/src/buzz/BuzzerFeedbackThread.cpp index 2bd3158a3..ce762c764 100644 --- a/src/buzz/BuzzerFeedbackThread.cpp +++ b/src/buzz/BuzzerFeedbackThread.cpp @@ -47,10 +47,6 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event) playComboTune(); // Ping sent feedback break; - case INPUT_BROKER_SHUTDOWN: - playShutdownMelody(); // Shutdown feedback - break; - default: // For other events, check if it's a printable character if (event->kbchar >= 32 && event->kbchar <= 126) { @@ -69,10 +65,7 @@ int32_t BuzzerFeedbackThread::runOnce() // This thread is primarily event-driven, but we can use runOnce // for any periodic tasks if needed in the future - if (needsUpdate) { - needsUpdate = false; - // Could add any periodic processing here - } + needsUpdate = false; // Run every 100ms when active, less frequently when idle return needsUpdate ? 100 : 1000; diff --git a/src/commands.h b/src/commands.h index e0bfab330..603003e5c 100644 --- a/src/commands.h +++ b/src/commands.h @@ -13,5 +13,6 @@ enum class Cmd { START_FIRMWARE_UPDATE_SCREEN, STOP_BOOT_SCREEN, SHOW_PREV_FRAME, - SHOW_NEXT_FRAME + SHOW_NEXT_FRAME, + NOOP }; \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index cddc7ba7a..0e24990b5 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -150,11 +150,12 @@ along with this program. If not, see . // Define if screen should be mirrored left to right // #define SCREEN_MIRROR -// I2C Keyboards (M5Stack, RAK14004, T-Deck) +// I2C Keyboards (M5Stack, RAK14004, T-Deck, T-Deck Pro, T-Lora Pager, CardKB, BBQ10, MPR121, TCA8418) #define CARDKB_ADDR 0x5F #define TDECK_KB_ADDR 0x55 #define BBQ10_KB_ADDR 0x1F #define MPR121_KB_ADDR 0x5A +#define TCA8418_KB_ADDR 0x34 // ----------------------------------------------------------------------------- // SENSOR @@ -193,8 +194,11 @@ along with this program. If not, see . #define MLX90614_ADDR_DEF 0x5A #define CGRADSENS_ADDR 0x66 #define LTR390UV_ADDR 0x53 -#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418 +#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418_KB #define PCT2075_ADDR 0x37 +#define BQ27220_ADDR 0x55 // same address as TDECK_KB +#define BQ25896_ADDR 0x6B +#define LTR553ALS_ADDR 0x23 // ----------------------------------------------------------------------------- // ACCELEROMETER @@ -208,6 +212,7 @@ along with this program. If not, see . #define BMX160_ADDR 0x69 #define ICM20948_ADDR 0x69 #define ICM20948_ADDR_ALT 0x68 +#define BHI260AP_ADDR 0x28 #define BMM150_ADDR 0x13 // ----------------------------------------------------------------------------- @@ -230,6 +235,7 @@ along with this program. If not, see . // Touchscreen // ----------------------------------------------------------------------------- #define FT6336U_ADDR 0x48 +#define CST328_ADDR 0x1A // ----------------------------------------------------------------------------- // RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index dd290db98..c1358861b 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -74,7 +74,12 @@ class ScanI2C RAK12035, TCA8418KB, PCT2075, - BMM150, + CST328, + BQ25896, + BQ27220, + LTR553ALS, + BHI260AP, + BMM150 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 9e9441123..652d50d51 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -206,7 +206,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) } break; - SCAN_SIMPLE_CASE(TDECK_KB_ADDR, TDECKKB, "T-Deck keyboard", (uint8_t)addr.address); + case TDECK_KB_ADDR: + // Do we have the T-Deck keyboard or the T-Deck Pro battery sensor? + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x04), 1); + if (registerValue != 0) { + logFoundDevice("BQ27220", (uint8_t)addr.address); + type = BQ27220; + } else { + logFoundDevice("TDECKKB", (uint8_t)addr.address); + type = TDECKKB; + } + break; SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10", (uint8_t)addr.address); SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address); @@ -396,6 +406,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) logFoundDevice("BQ24295", (uint8_t)addr.address); break; } + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x14), 1); // get ID + if ((registerValue & 0b00000011) == 0b00000010) { + type = BQ25896; + logFoundDevice("BQ25896", (uint8_t)addr.address); + break; + } registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID if (registerValue == 0x6A) { type = LSM6DS3; @@ -447,6 +463,9 @@ 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); + SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address); + SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address); SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address); SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address); #ifdef HAS_TPS65233 diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 142241c43..f3624c627 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -39,9 +39,9 @@ template std::size_t array_count(const T (&)[N]) return N; } -#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) -#if defined(RAK2560) -HardwareSerial *GPS::_serial_gps = &Serial2; +#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; #endif @@ -643,8 +643,16 @@ bool GPS::setup() delay(250); } else if (IS_ONE_OF(gnssModel, GNSS_MODEL_AG3335, GNSS_MODEL_AG3352)) { - _serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC - + if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_IN || + config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_NP_865) { + _serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC + // GPS GLONASS GALILEO BDS QZSS NAVIC + // 1 0 1 0 0 1 + } else { + _serial_gps->write("$PAIR066,1,1,1,1,0,0*3A\r\n"); // Enable GPS+GLONASS+GALILEO+BDS + // GPS GLONASS GALILEO BDS QZSS NAVIC + // 1 1 1 1 0 0 + } // Configure NMEA (sentences will output once per fix) _serial_gps->write("$PAIR062,0,1*3F\r\n"); // GGA ON _serial_gps->write("$PAIR062,1,0*3F\r\n"); // GLL OFF @@ -1536,7 +1544,10 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s if (t.tm_mon > -1) { LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, ti.age()); - perhapsSetRTC(RTCQualityGPS, t); + if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) { + // Clear the GPS buffer if we got an invalid time + clearBuffer(); + } return true; } else return false; diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index af964eab5..d574c9ad0 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -105,7 +105,7 @@ void readFromRTC() * * If we haven't yet set our RTC this boot, set it from a GPS derived time */ -bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) +RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) { static uint32_t lastSetMsec = 0; uint32_t now = millis(); @@ -113,7 +113,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) #ifdef BUILD_EPOCH if (tv->tv_sec < BUILD_EPOCH) { LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); - return false; + return RTCSetResultInvalidTime; } #endif @@ -184,9 +184,9 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) readFromRTC(); #endif - return true; + return RTCSetResultSuccess; } else { - return false; + return RTCSetResultNotSet; // RTC was already set with a higher quality time } } @@ -215,7 +215,7 @@ const char *RtcName(RTCQuality quality) * @param t The time to potentially set the RTC to. * @return True if the RTC was set to the provided time, false otherwise. */ -bool perhapsSetRTC(RTCQuality q, struct tm &t) +RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t) { /* Convert to unix time The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 @@ -226,12 +226,19 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t) time_t res = gm_mktime(&t); struct timeval tv; tv.tv_sec = res; - tv.tv_usec = 0; // time.centisecond() * (10 / 1000); + tv.tv_usec = 0; // time.centisecond() * (10 / 1000); + uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms +#ifdef BUILD_EPOCH + if (tv.tv_sec < BUILD_EPOCH) { + LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + return RTCSetResultInvalidTime; + } +#endif // LOG_DEBUG("Got time from GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec); if (t.tm_year < 0 || t.tm_year >= 300) { // LOG_DEBUG("Ignore invalid GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec); - return false; + return RTCSetResultInvalidTime; } else { return perhapsSetRTC(q, &tv); } @@ -244,11 +251,15 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t) */ int32_t getTZOffset() { +#if MESHTASTIC_EXCLUDE_TZ + return 0; +#else time_t now = getTime(false); struct tm *gmt; gmt = gmtime(&now); gmt->tm_isdst = -1; return (int32_t)difftime(now, mktime(gmt)); +#endif } /** diff --git a/src/gps/RTC.h b/src/gps/RTC.h index caa48dc06..96dec575b 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -22,13 +22,22 @@ enum RTCQuality { RTCQualityGPS = 4 }; +/// The RTC set result codes +/// Used to indicate the result of an attempt to set the RTC. +enum RTCSetResult { + RTCSetResultNotSet = 0, ///< RTC was set successfully + RTCSetResultSuccess = 1, ///< RTC was set successfully + RTCSetResultInvalidTime = 3, ///< The provided time was invalid (e.g., before the build epoch) + RTCSetResultError = 4 ///< An error occurred while setting the RTC +}; + RTCQuality getRTCQuality(); extern uint32_t lastSetFromPhoneNtpOrGps; /// If we haven't yet set our RTC this boot, set it from a GPS derived time -bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false); -bool perhapsSetRTC(RTCQuality q, struct tm &t); +RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false); +RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t); /// Return a string name for the quality const char *RtcName(RTCQuality quality); diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 5a2749482..3bd20feec 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -6,6 +6,10 @@ #include "main.h" #include +#ifdef GXEPD2_DRIVER_0 +#include "einkDetect.h" +#endif + /* The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini Previously, these macros were defined at the top of this file. @@ -174,9 +178,8 @@ bool EInkDisplay::connect() } } -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \ - defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \ - defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || \ + defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) { // Start HSPI hspi = new SPIClass(HSPI); @@ -203,7 +206,7 @@ bool EInkDisplay::connect() adafruitDisplay->setRotation(0); adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } -#elif defined(M5_COREINK) +#elif defined(M5_COREINK) || defined(T_DECK_PRO) auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); adafruitDisplay = new GxEPD2_BW(*lowLevel); adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0)); @@ -232,6 +235,23 @@ bool EInkDisplay::connect() adafruitDisplay->init(); adafruitDisplay->setRotation(3); } +#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) + + // Detect display model, before starting SPI + EInkDetectionResult displayModel = detectEInk(); + + // Start HSPI + hspi = new SPIClass(HSPI); + hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS + + // Create GxEPD2 object + adafruitDisplay = new GxEPD2_Multi((uint8_t)displayModel, PIN_EINK_CS, PIN_EINK_DC, + PIN_EINK_RES, PIN_EINK_BUSY, *hspi); + + // Init GxEPD2 + adafruitDisplay->init(); + adafruitDisplay->setRotation(3); + #endif return true; diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 93be197b0..284337627 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -5,6 +5,10 @@ #include "GxEPD2_BW.h" #include +#ifdef GXEPD2_DRIVER_0 // If variant has multiple possible display models +#include "GxEPD2Multi.h" +#endif + /** * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. * @@ -63,8 +67,15 @@ class EInkDisplay : public OLEDDisplay // Connect to the display virtual bool connect() override; - // AdafruitGFX display object - instantiated in connect(), variant specific +#ifdef GXEPD2_DRIVER_0 + // AdafruitGFX display object - wrapper for multiple drivers + // Allows runtime detection of multiple displays + // Avoid this situation if possible! + GxEPD2_Multi *adafruitDisplay = NULL; +#else + // AdafruitGFX display object (for single display model) - instantiated in connect(), variant specific GxEPD2_BW *adafruitDisplay = NULL; +#endif // If display uses HSPI #if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ diff --git a/src/graphics/GxEPD2Multi.h b/src/graphics/GxEPD2Multi.h new file mode 100644 index 000000000..f3807c9de --- /dev/null +++ b/src/graphics/GxEPD2Multi.h @@ -0,0 +1,135 @@ +// Wrapper class for GxEPD2_BW + +// Generic signature at build-time, so that we can detect display model at run-time +// Workaround for issue of GxEPD2_BW objects not having a shared base class +// Only exposes methods which we are actually using + +template class GxEPD2_Multi +{ + public: + void drawPixel(int16_t x, int16_t y, uint16_t color) + { + if (which == 0) + driver0->drawPixel(x, y, color); + else + driver1->drawPixel(x, y, color); + } + + bool nextPage() + { + if (which == 0) + return driver0->nextPage(); + else + return driver1->nextPage(); + } + + void hibernate() + { + if (which == 0) + driver0->hibernate(); + else + driver1->hibernate(); + } + + void init(uint32_t serial_diag_bitrate = 0) + { + if (which == 0) + driver0->init(serial_diag_bitrate); + else + driver1->init(serial_diag_bitrate); + } + + void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false) + { + if (which == 0) + driver0->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode); + else + driver1->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode); + } + + void setRotation(uint8_t x) + { + if (which == 0) + driver0->setRotation(x); + else + driver1->setRotation(x); + } + + void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) + { + if (which == 0) + driver0->setPartialWindow(x, y, w, h); + else + driver1->setPartialWindow(x, y, w, h); + } + + void setFullWindow() + { + if (which == 0) + driver0->setFullWindow(); + else + driver1->setFullWindow(); + } + + int16_t width() + { + if (which == 0) + return driver0->width(); + else + return driver1->width(); + } + + int16_t height() + { + if (which == 0) + return driver0->height(); + else + return driver1->height(); + } + + void clearScreen(uint8_t value = 0xFF) + { + if (which == 0) + driver0->clearScreen(); + else + driver1->clearScreen(); + } + + void endAsyncFull() + { + if (which == 0) + driver0->endAsyncFull(); + else + driver1->endAsyncFull(); + } + + // Exposes methods of the GxEPD2_EPD object which is usually available as GxEPD2_BW::epd + class Epd2Wrapper + { + public: + bool isBusy() { return m_epd2->isBusy(); } + GxEPD2_EPD *m_epd2; + } epd2; + + // Constructor + // Select driver by passing whichDriver as 0 or 1 + GxEPD2_Multi(uint8_t whichDriver, int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi) + { + assert(whichDriver == 0 || whichDriver == 1); + which = whichDriver; + LOG_DEBUG("GxEPD2_Multi driver: %d", which); + + if (which == 0) { + driver0 = new GxEPD2_BW(Driver0(cs, dc, rst, busy, spi)); + epd2.m_epd2 = &(driver0->epd2); + } else if (which == 1) { + driver1 = new GxEPD2_BW(Driver1(cs, dc, rst, busy, spi)); + epd2.m_epd2 = &(driver1->epd2); + } + } + + private: + uint8_t which; + GxEPD2_BW *driver0; + GxEPD2_BW *driver1; +}; \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index c8c9d8b74..f22a0d8a8 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . */ #include "Screen.h" +#include "NodeDB.h" #include "PowerMon.h" #include "Throttle.h" #include "configuration.h" @@ -44,7 +45,6 @@ along with this program. If not, see . #endif #include "FSCommon.h" #include "MeshService.h" -#include "NodeDB.h" #include "RadioLibInterface.h" #include "error.h" #include "gps/GeoCoord.h" @@ -69,6 +69,8 @@ using graphics::Emote; using graphics::emotes; using graphics::numEmotes; +extern uint16_t TFT_MESH; + #if HAS_WIFI && !defined(ARCH_PORTDUINO) #include "mesh/wifi/WiFiAPClient.h" #endif @@ -135,9 +137,64 @@ extern bool hasUnreadMessage; // Displays a temporary centered banner message (e.g., warning, status, etc.) // The banner appears in the center of the screen and disappears after the specified duration +void Screen::showSimpleBanner(const char *message, uint32_t durationMs) +{ + BannerOverlayOptions options; + options.message = message; + options.durationMs = durationMs; + options.notificationType = notificationTypeEnum::text_banner; + showOverlayBanner(options); +} + // Called to trigger a banner with custom message and duration -void Screen::showOverlayBanner(const char *message, uint32_t durationMs, const char **optionsArrayPtr, uint8_t options, - std::function bannerCallback, int8_t InitialSelected) +void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options) +{ +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus +#endif + // Store the message and set the expiration timestamp + strncpy(NotificationRenderer::alertBannerMessage, banner_overlay_options.message, 255); + NotificationRenderer::alertBannerMessage[255] = '\0'; // Ensure null termination + NotificationRenderer::alertBannerUntil = + (banner_overlay_options.durationMs == 0) ? 0 : millis() + banner_overlay_options.durationMs; + NotificationRenderer::optionsArrayPtr = banner_overlay_options.optionsArrayPtr; + NotificationRenderer::optionsEnumPtr = banner_overlay_options.optionsEnumPtr; + NotificationRenderer::alertBannerOptions = banner_overlay_options.optionsCount; + NotificationRenderer::alertBannerCallback = banner_overlay_options.bannerCallback; + NotificationRenderer::curSelected = banner_overlay_options.InitialSelected; + NotificationRenderer::pauseBanner = false; + NotificationRenderer::current_notification_type = notificationTypeEnum::selection_picker; + static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; + ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); + ui->setTargetFPS(60); + ui->update(); +} + +// Called to trigger a banner with custom message and duration +void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function bannerCallback) +{ +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus +#endif + nodeDB->pause_sort(true); + // Store the message and set the expiration timestamp + strncpy(NotificationRenderer::alertBannerMessage, message, 255); + NotificationRenderer::alertBannerMessage[255] = '\0'; // Ensure null termination + NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs; + NotificationRenderer::alertBannerCallback = bannerCallback; + NotificationRenderer::pauseBanner = false; + NotificationRenderer::curSelected = 0; + NotificationRenderer::current_notification_type = notificationTypeEnum::node_picker; + + static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; + ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); + ui->setTargetFPS(60); + ui->update(); +} + +// Called to trigger a banner with custom message and duration +void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, + std::function bannerCallback) { #ifdef USE_EINK EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus @@ -146,14 +203,16 @@ void Screen::showOverlayBanner(const char *message, uint32_t durationMs, const c strncpy(NotificationRenderer::alertBannerMessage, message, 255); NotificationRenderer::alertBannerMessage[255] = '\0'; // Ensure null termination NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs; - NotificationRenderer::optionsArrayPtr = optionsArrayPtr; - NotificationRenderer::alertBannerOptions = options; NotificationRenderer::alertBannerCallback = bannerCallback; - NotificationRenderer::curSelected = InitialSelected; NotificationRenderer::pauseBanner = false; - static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawAlertBannerOverlay}; + NotificationRenderer::curSelected = 0; + NotificationRenderer::current_notification_type = notificationTypeEnum::number_picker; + NotificationRenderer::numDigits = digits; + NotificationRenderer::currentNumber = 0; + + static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); - setFastFramerate(); // Draw ASAP + ui->setTargetFPS(60); ui->update(); } @@ -230,6 +289,20 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32) { graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES]; + + LOG_INFO("Protobuf Value uiconfig.screen_rgb_color: %d", uiconfig.screen_rgb_color); + int32_t rawRGB = uiconfig.screen_rgb_color; + if (rawRGB > 0 && rawRGB <= 255255255) { + uint8_t TFT_MESH_r = (rawRGB >> 16) & 0xFF; + uint8_t TFT_MESH_g = (rawRGB >> 8) & 0xFF; + uint8_t TFT_MESH_b = rawRGB & 0xFF; + LOG_INFO("Values of r,g,b: %d, %d, %d", TFT_MESH_r, TFT_MESH_g, TFT_MESH_b); + + if (TFT_MESH_r <= 255 && TFT_MESH_g <= 255 && TFT_MESH_b <= 255) { + TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b); + } + } + #if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64) dispdev = new SH1106Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); @@ -239,8 +312,8 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O ST7789_MISO, ST7789_SCK); #else dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); - static_cast(dispdev)->setRGB(COLOR565(255, 255, 128)); #endif + static_cast(dispdev)->setRGB(TFT_MESH); #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); @@ -313,9 +386,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #ifdef T_WATCH_S3 PMU->enablePowerOutput(XPOWERS_ALDO2); #endif -#ifdef HELTEC_TRACKER_V1_X - uint8_t tft_vext_enabled = digitalRead(VEXT_ENABLE); -#endif + #if !ARCH_PORTDUINO dispdev->displayOn(); #endif @@ -327,10 +398,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) dispdev->displayOn(); #ifdef HELTEC_TRACKER_V1_X - // If the TFT VEXT power is not enabled, initialize the UI. - if (!tft_vext_enabled) { - ui->init(); - } + ui->init(); #endif #ifdef USE_ST7789 pinMode(VTFT_CTRL, OUTPUT); @@ -386,9 +454,22 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) void Screen::setup() { + // === Enable display rendering === useDisplay = true; + // === Load saved brightness from UI config === + // For OLED displays (SSD1306), default brightness is 255 if not set + if (uiconfig.screen_brightness == 0) { +#if defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) + brightness = 255; // Default for OLED +#else + brightness = BRIGHTNESS_DEFAULT; +#endif + } else { + brightness = uiconfig.screen_brightness; + } + // === Detect OLED subtype (if supported by board variant) === #ifdef AutoOLEDWire_h if (isAUTOOled) @@ -416,6 +497,14 @@ void Screen::setup() ui->disableAllIndicators(); // Disable page indicator dots ui->getUiState()->userData = this; // Allow static callbacks to access Screen instance + // === Apply loaded brightness === +#if defined(ST7789_CS) + static_cast(dispdev)->setDisplayBrightness(brightness); +#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) + dispdev->setBrightness(brightness); +#endif + LOG_INFO("Applied screen brightness: %d", brightness); + // === Set custom overlay callbacks === static OverlayCallback overlays[] = { graphics::UIRenderer::drawNavigationBar // Custom indicator icons for each frame @@ -490,7 +579,7 @@ void Screen::setup() touchScreenImpl1->init(); } } -#elif HAS_TOUCHSCREEN +#elif HAS_TOUCHSCREEN && !defined(USE_EINK) touchScreenImpl1 = new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); touchScreenImpl1->init(); @@ -546,6 +635,11 @@ void Screen::forceDisplay(bool forceUiUpdate) // Tell EInk class to update the display static_cast(dispdev)->forceDisplay(); +#else + // No delay between UI frame rendering + if (forceUiUpdate) { + setFastFramerate(); + } #endif } @@ -562,7 +656,7 @@ int32_t Screen::runOnce() if (displayHeight == 0) { displayHeight = dispdev->getHeight(); } - menuHandler::handleMenuSwitch(); + menuHandler::handleMenuSwitch(dispdev); // Show boot screen for first logo_timeout seconds, then switch to normal operation. // serialSinceMsec adjusts for additional serial wait time during nRF52 bootup @@ -595,7 +689,7 @@ int32_t Screen::runOnce() } #endif if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) { - showOverlayBanner("Rebooting...", 0); + showSimpleBanner("Rebooting...", 0); } // Process incoming commands. @@ -642,6 +736,8 @@ int32_t Screen::runOnce() EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame setFrames(); break; + case Cmd::NOOP: + break; default: LOG_ERROR("Invalid screen cmd"); } @@ -768,6 +864,8 @@ void Screen::setFrames(FrameFocus focus) uint8_t previousFrameCount = framesetInfo.frameCount; FramesetInfo fsi; // Location of specific frames, for applying focus parameter + graphics::UIRenderer::rebuildFavoritedNodes(); + LOG_DEBUG("Show standard frames"); showingNormalScreen = true; @@ -785,8 +883,8 @@ void Screen::setFrames(FrameFocus focus) #if defined(DISPLAY_CLOCK_FRAME) fsi.positions.clock = numframes; - normalFrames[numframes++] = graphics::ClockRenderer::digitalWatchFace ? graphics::ClockRenderer::drawDigitalClockFrame - : &graphics::ClockRenderer::drawAnalogClockFrame; + normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame + : graphics::ClockRenderer::drawDigitalClockFrame; indicatorIcons.push_back(digital_icon_clock); #endif @@ -842,27 +940,11 @@ void Screen::setFrames(FrameFocus focus) } #if !defined(DISPLAY_CLOCK_FRAME) fsi.positions.clock = numframes; - normalFrames[numframes++] = graphics::ClockRenderer::digitalWatchFace ? graphics::ClockRenderer::drawDigitalClockFrame - : graphics::ClockRenderer::drawAnalogClockFrame; + normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame + : graphics::ClockRenderer::drawDigitalClockFrame; indicatorIcons.push_back(digital_icon_clock); #endif - // We don't show the node info of our node (if we have it yet - we should) - size_t numMeshNodes = nodeDB->getNumMeshNodes(); - if (numMeshNodes > 0) - numMeshNodes--; - - for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { - const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); - if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) { - if (fsi.positions.firstFavorite == 255) - fsi.positions.firstFavorite = numframes; - fsi.positions.lastFavorite = numframes; - normalFrames[numframes++] = graphics::UIRenderer::drawNodeInfo; - indicatorIcons.push_back(icon_node); - } - } - #if HAS_WIFI && !defined(ARCH_PORTDUINO) if (!dismissedFrames.wifi && isWifiAvailable()) { fsi.positions.wifi = numframes; @@ -872,7 +954,7 @@ void Screen::setFrames(FrameFocus focus) #endif // Beware of what changes you make in this code! - // We pass numfames into GetMeshModulesWithUIFrames() which is highly important! + // We pass numframes into GetMeshModulesWithUIFrames() which is highly important! // Inside of that callback, goes over to MeshModule.cpp and we run // modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr // entries until we're ready to start building the matching entries. @@ -901,6 +983,34 @@ void Screen::setFrames(FrameFocus focus) LOG_DEBUG("Added modules. numframes: %d", numframes); + // We don't show the node info of our node (if we have it yet - we should) + size_t numMeshNodes = nodeDB->getNumMeshNodes(); + if (numMeshNodes > 0) + numMeshNodes--; + + // Temporary array to hold favorite node frames + std::vector favoriteFrames; + + for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { + const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); + if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) { + favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo); + } + } + + // Insert favorite frames *after* collecting them all + if (!favoriteFrames.empty()) { + fsi.positions.firstFavorite = numframes; + for (const auto &f : favoriteFrames) { + normalFrames[numframes++] = f; + indicatorIcons.push_back(icon_node); + } + fsi.positions.lastFavorite = numframes - 1; + } else { + fsi.positions.firstFavorite = 255; + fsi.positions.lastFavorite = 255; + } + fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE this->frameCount = numframes; // ✅ Save frame count for use in custom overlay LOG_DEBUG("Finished build frames. numframes: %d", numframes); @@ -909,11 +1019,10 @@ void Screen::setFrames(FrameFocus focus) ui->disableAllIndicators(); // Add overlays: frame icons and alert banner) - static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawAlertBannerOverlay}; + static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); - prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list - // just changed) + prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed) // Focus on a specific frame, in the frame set we just created switch (focus) { @@ -937,6 +1046,9 @@ void Screen::setFrames(FrameFocus focus) // If no module requested focus, will show the first frame instead ui->switchToFrame(fsi.positions.clock); break; + case FOCUS_SYSTEM: + ui->switchToFrame(fsi.positions.memory); + break; case FOCUS_PRESERVE: // No more adjustment — force stay on same index @@ -1147,40 +1259,45 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) devicestate.has_rx_text_message = true; // Needed to include the message frame hasUnreadMessage = true; // Enables mail icon in the header setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view - forceDisplay(); // Forces screen redraw - // === Prepare banner content === - const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); - const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; + // Only wake/force display if the configuration allows it + if (shouldWakeOnReceivedMessage()) { + setOn(true); // Wake up the screen first + forceDisplay(); // Forces screen redraw - const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes); + // === Prepare banner content === + const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); + const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; - char banner[256]; + const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes); - // Check for bell character in message to determine alert type - bool isAlert = false; - for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) { - if (msgRaw[i] == '\x07') { - isAlert = true; - break; + char banner[256]; + + // Check for bell character in message to determine alert type + bool isAlert = false; + for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) { + if (msgRaw[i] == '\x07') { + isAlert = true; + break; + } } - } - if (isAlert) { - if (longName && longName[0]) { - snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); + if (isAlert) { + if (longName && longName[0]) { + snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); + } else { + strcpy(banner, "Alert Received"); + } } else { - strcpy(banner, "Alert Received"); + if (longName && longName[0]) { + snprintf(banner, sizeof(banner), "New Message from\n%s", longName); + } else { + strcpy(banner, "New Message"); + } } - } else { - if (longName && longName[0]) { - snprintf(banner, sizeof(banner), "New Message from\n%s", longName); - } else { - strcpy(banner, "New Message"); - } - } - screen->showOverlayBanner(banner, 3000); + screen->showSimpleBanner(banner, 3000); + } } } @@ -1219,31 +1336,15 @@ int Screen::handleInputEvent(const InputEvent *event) setFastFramerate(); // Draw ASAP #endif if (NotificationRenderer::isOverlayBannerShowing()) { - NotificationRenderer::inEvent = event->inputEvent; - static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, - NotificationRenderer::drawAlertBannerOverlay}; + NotificationRenderer::inEvent = *event; + static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); setFastFramerate(); // Draw ASAP ui->update(); - menuHandler::handleMenuSwitch(); + menuHandler::handleMenuSwitch(dispdev); return 0; } - /* - #if defined(DISPLAY_CLOCK_FRAME) - // For the T-Watch, intercept touches to the 'toggle digital/analog watch face' button - uint8_t watchFaceFrame = error_code ? 1 : 0; - - if (this->ui->getUiState()->currentFrame == watchFaceFrame && event->touchX >= 204 && event->touchX <= 240 && - event->touchY >= 204 && event->touchY <= 240) { - screen->digitalWatchFace = !screen->digitalWatchFace; - - setFrames(); - - return 0; - } - #endif - */ // Use left or right input from a keyboard to move between frames, // so long as a mesh module isn't using these events for some other purpose @@ -1265,13 +1366,8 @@ int Screen::handleInputEvent(const InputEvent *event) } else if (event->inputEvent == INPUT_BROKER_SELECT) { if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) { menuHandler::homeBaseMenu(); -#if HAS_TFT } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.memory) { - menuHandler::switchToMUIMenu(); -#else - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.memory) { - menuHandler::BuzzerModeMenu(); -#endif + menuHandler::systemBaseMenu(); #if HAS_GPS } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) { menuHandler::positionBaseMenu(); @@ -1280,9 +1376,12 @@ int Screen::handleInputEvent(const InputEvent *event) menuHandler::clockMenu(); } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) { menuHandler::LoraRegionPicker(); - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage && - devicestate.rx_text_message.from) { - menuHandler::messageResponseMenu(); + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) { + if (devicestate.rx_text_message.from) { + menuHandler::messageResponseMenu(); + } else { + menuHandler::textMessageBaseMenu(); + } } else if (framesetInfo.positions.firstFavorite != 255 && this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite && this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) { @@ -1294,6 +1393,8 @@ int Screen::handleInputEvent(const InputEvent *event) this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal || this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) { menuHandler::nodeListMenu(); + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.wifi) { + menuHandler::wifiBaseMenu(); } } else if (event->inputEvent == INPUT_BROKER_BACK) { showPrevFrame(); @@ -1332,3 +1433,23 @@ bool Screen::isOverlayBannerShowing() #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} #endif // HAS_SCREEN + +bool shouldWakeOnReceivedMessage() +{ + /* + The goal here is to determine when we do NOT wake up the screen on message received: + - Any ext. notifications are turned on + - If role is not client / client_mute + - If the battery level is very low + */ + if (moduleConfig.external_notification.enabled) { + return false; + } + if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) { + return false; + } + if (powerStatus && powerStatus->getBatteryChargePercent() < 10) { + return false; + } + return true; +} diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index ac7d9aa69..265900131 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -5,10 +5,28 @@ #include "detect/ScanI2C.h" #include "mesh/generated/meshtastic/config.pb.h" #include +#include #include #include #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) +namespace graphics +{ +enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker }; + +struct BannerOverlayOptions { + const char *message; + uint32_t durationMs = 30000; + const char **optionsArrayPtr = nullptr; + const int *optionsEnumPtr = nullptr; + uint8_t optionsCount = 0; + std::function bannerCallback = nullptr; + int8_t InitialSelected = 0; + notificationTypeEnum notificationType = notificationTypeEnum::text_banner; +}; +} // namespace graphics + +bool shouldWakeOnReceivedMessage(); #if !HAS_SCREEN #include "power.h" @@ -25,6 +43,7 @@ class Screen FOCUS_TEXTMESSAGE, FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus FOCUS_CLOCK, + FOCUS_SYSTEM, }; explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY); @@ -39,10 +58,8 @@ class Screen void setFunctionSymbol(std::string) {} void removeFunctionSymbol(std::string) {} void startAlert(const char *) {} - void showOverlayBanner(const char *message, uint32_t durationMs = 3000, const char **optionsArrayPtr = nullptr, - uint8_t options = 0, std::function bannerCallback = NULL, int8_t InitialSelected = 0) - { - } + void showSimpleBanner(const char *message, uint32_t durationMs = 0) {} + void showOverlayBanner(BannerOverlayOptions) {} void setFrames(FrameFocus focus) {} void endAlert() {} }; @@ -77,6 +94,7 @@ class Screen #include "commands.h" #include "concurrency/LockGuard.h" #include "concurrency/OSThread.h" +#include "graphics/draw/MenuHandler.h" #include "input/InputBroker.h" #include "mesh/MeshModule.h" #include "modules/AdminModule.h" @@ -199,6 +217,7 @@ class Screen : public concurrency::OSThread CallbackObserver(this, &Screen::handleAdminMessage); public: + OLEDDisplay *getDisplayDevice() { return dispdev; } explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY); size_t frameCount = 0; // Total number of active frames ~Screen(); @@ -211,6 +230,7 @@ class Screen : public concurrency::OSThread FOCUS_TEXTMESSAGE, FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus FOCUS_CLOCK, + FOCUS_SYSTEM, }; // Regenerate the normal set of frames, focusing a specific frame if requested @@ -225,8 +245,6 @@ class Screen : public concurrency::OSThread meshtastic_Config_DisplayConfig_OledType model; OLEDDISPLAY_GEOMETRY geometry; - bool ignoreCompass = false; - bool isOverlayBannerShowing(); // Stores the last 4 of our hardware ID, to make finding the device for pairing easier @@ -290,8 +308,17 @@ class Screen : public concurrency::OSThread enqueueCmd(cmd); } - void showOverlayBanner(const char *message, uint32_t durationMs = 3000, const char **optionsArrayPtr = nullptr, - uint8_t options = 0, std::function bannerCallback = NULL, int8_t InitialSelected = 0); + void showSimpleBanner(const char *message, uint32_t durationMs = 0); + void showOverlayBanner(BannerOverlayOptions); + + void showNodePicker(const char *message, uint32_t durationMs, std::function bannerCallback); + void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function bannerCallback); + + void requestMenu(graphics::menuHandler::screenMenus menuToShow) + { + graphics::menuHandler::menuQueue = menuToShow; + runNow(); + } void startFirmwareUpdateScreen() { @@ -325,6 +352,12 @@ class Screen : public concurrency::OSThread /// Stops showing the boot screen. void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); } + void runNow() + { + setFastFramerate(); + enqueueCmd(ScreenCmd{.cmd = Cmd::NOOP}); + } + /// Overrides the default utf8 character conversion, to replace empty space with question marks static char customFontTableLookup(const uint8_t ch) { diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 07f2e5cde..b458e54e4 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -99,10 +99,17 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti // === Battery State === int chargePercent = powerStatus->getBatteryChargePercent(); - bool isCharging = powerStatus->getIsCharging() == meshtastic::OptionalBool::OptTrue; - if (chargePercent == 100) { + bool isCharging = powerStatus->getIsCharging(); + bool usbPowered = powerStatus->getHasUSB(); + + if (chargePercent >= 100) { isCharging = false; } + if (chargePercent == 101) { + usbPowered = true; // Forcing this flag on for the express purpose that some devices have no concept of having a USB cable + // plugged in + } + uint32_t now = millis(); #ifndef USE_EINK @@ -115,48 +122,63 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti bool useHorizontalBattery = (isHighResolution && screenW >= screenH); const int textY = y + (highlightHeight - FONT_HEIGHT_SMALL) / 2; + int batteryX = 1; + int batteryY = HEADER_OFFSET_Y + 1; + // === Battery Icons === - if (useHorizontalBattery) { - int batteryX = 2; - int batteryY = HEADER_OFFSET_Y + 3; - display->drawXbm(batteryX, batteryY, 9, 13, batteryBitmap_h_bottom); - display->drawXbm(batteryX + 9, batteryY, 9, 13, batteryBitmap_h_top); - if (isCharging && isBoltVisibleShared) - display->drawXbm(batteryX + 4, batteryY, 9, 13, lightning_bolt_h); - else { - display->drawLine(batteryX + 5, batteryY, batteryX + 10, batteryY); - display->drawLine(batteryX + 5, batteryY + 12, batteryX + 10, batteryY + 12); - int fillWidth = 14 * chargePercent / 100; - display->fillRect(batteryX + 1, batteryY + 1, fillWidth, 11); + if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging + batteryX += 1; + batteryY += 2; + if (isHighResolution) { + display->drawXbm(batteryX, batteryY, 19, 12, imgUSB_HighResolution); + batteryX += 20; // Icon + 1 pixel + } else { + display->drawXbm(batteryX, batteryY, 10, 8, imgUSB); + batteryX += 11; // Icon + 1 pixel } } else { - int batteryX = 1; - int batteryY = HEADER_OFFSET_Y + 1; + if (useHorizontalBattery) { + batteryX += 1; + batteryY += 2; + display->drawXbm(batteryX, batteryY, 9, 13, batteryBitmap_h_bottom); + display->drawXbm(batteryX + 9, batteryY, 9, 13, batteryBitmap_h_top); + if (isCharging && isBoltVisibleShared) + display->drawXbm(batteryX + 4, batteryY, 9, 13, lightning_bolt_h); + else { + display->drawLine(batteryX + 5, batteryY, batteryX + 10, batteryY); + display->drawLine(batteryX + 5, batteryY + 12, batteryX + 10, batteryY + 12); + int fillWidth = 14 * chargePercent / 100; + display->fillRect(batteryX + 1, batteryY + 1, fillWidth, 11); + } + batteryX += 18; // Icon + 2 pixels + } else { #ifdef USE_EINK - batteryY += 2; + batteryY += 2; #endif - display->drawXbm(batteryX, batteryY, 7, 11, batteryBitmap_v); - if (isCharging && isBoltVisibleShared) - display->drawXbm(batteryX + 1, batteryY + 3, 5, 5, lightning_bolt_v); - else { - display->drawXbm(batteryX - 1, batteryY + 4, 8, 3, batteryBitmap_sidegaps_v); - int fillHeight = 8 * chargePercent / 100; - int fillY = batteryY - fillHeight; - display->fillRect(batteryX + 1, fillY + 10, 5, fillHeight); + display->drawXbm(batteryX, batteryY, 7, 11, batteryBitmap_v); + if (isCharging && isBoltVisibleShared) + display->drawXbm(batteryX + 1, batteryY + 3, 5, 5, lightning_bolt_v); + else { + display->drawXbm(batteryX - 1, batteryY + 4, 8, 3, batteryBitmap_sidegaps_v); + int fillHeight = 8 * chargePercent / 100; + int fillY = batteryY - fillHeight; + display->fillRect(batteryX + 1, fillY + 10, 5, fillHeight); + } + batteryX += 9; // Icon + 2 pixels } } - // === Battery % Display === - char chargeStr[4]; - snprintf(chargeStr, sizeof(chargeStr), "%d", chargePercent); - int chargeNumWidth = display->getStringWidth(chargeStr); - const int batteryOffset = useHorizontalBattery ? 19 : 9; - const int percentX = x + batteryOffset; - display->drawString(percentX, textY, chargeStr); - display->drawString(percentX + chargeNumWidth - 1, textY, "%"); - if (isBold) { - display->drawString(percentX + 1, textY, chargeStr); - display->drawString(percentX + chargeNumWidth, textY, "%"); + if (chargePercent != 101) { + // === Battery % Display === + char chargeStr[4]; + snprintf(chargeStr, sizeof(chargeStr), "%d", chargePercent); + int chargeNumWidth = display->getStringWidth(chargeStr); + display->drawString(batteryX, textY, chargeStr); + display->drawString(batteryX + chargeNumWidth - 1, textY, "%"); + if (isBold) { + display->drawString(batteryX + 1, textY, chargeStr); + display->drawString(batteryX + chargeNumWidth, textY, "%"); + } } // === Time and Right-aligned Icons === @@ -184,7 +206,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti timeX = screenW - xOffset - timeStrWidth + 3; // === Show Mail or Mute Icon to the Left of Time === - int iconRightEdge = timeX - 1; + int iconRightEdge = timeX - 2; bool showMail = false; @@ -343,4 +365,30 @@ const int *getTextPositions(OLEDDisplay *display) return textPositions; } +bool isAllowedPunctuation(char c) +{ + const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ "; + return allowed.find(c) != std::string::npos; +} + +std::string sanitizeString(const std::string &input) +{ + std::string output; + bool inReplacement = false; + + for (char c : input) { + if (std::isalnum(static_cast(c)) || isAllowedPunctuation(c)) { + output += c; + inReplacement = false; + } else { + if (!inReplacement) { + output += 0xbf; // ISO-8859-1 for inverted question mark + inReplacement = true; + } + } + } + + return output; +} + } // namespace graphics diff --git a/src/graphics/SharedUIDisplay.h b/src/graphics/SharedUIDisplay.h index 2e97052a8..b8d82795e 100644 --- a/src/graphics/SharedUIDisplay.h +++ b/src/graphics/SharedUIDisplay.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace graphics { @@ -52,4 +53,8 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti const int *getTextPositions(OLEDDisplay *display); +bool isAllowedPunctuation(char c); + +std::string sanitizeString(const std::string &input); + } // namespace graphics diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 92b2c3d02..3e9bafc6c 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -1,5 +1,6 @@ #include "configuration.h" #include "main.h" + #if ARCH_PORTDUINO #include "platform/portduino/PortduinoGlue.h" #endif @@ -14,8 +15,10 @@ extern SX1509 gpioExtender; #endif -#ifndef TFT_MESH -#define TFT_MESH COLOR565(0x67, 0xEA, 0x94) +#ifdef TFT_MESH_OVERRIDE +uint16_t TFT_MESH = TFT_MESH_OVERRIDE; +#else +uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94); #endif #if defined(ST7735S) diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp index aa177078b..08466662c 100644 --- a/src/graphics/draw/ClockRenderer.cpp +++ b/src/graphics/draw/ClockRenderer.cpp @@ -21,7 +21,6 @@ namespace graphics namespace ClockRenderer { -bool digitalWatchFace = true; void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale) { @@ -187,7 +186,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 { display->clear(); display->setTextAlignment(TEXT_ALIGN_LEFT); - int line = 1; + // === Set Title, Blank for Clock const char *titleStr = ""; // === Header === @@ -219,7 +218,6 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 hour %= 12; if (hour == 0) hour = 12; - bool isPM = hour >= 12; snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute); } else { snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute); @@ -231,6 +229,8 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 #ifdef T_WATCH_S3 float scale = 1.5; +#elif defined(CHATTER_2) + float scale = 1.1; #else float scale = 0.75; if (isHighResolution) { @@ -284,6 +284,12 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 xOffset += (isHighResolution) ? 32 : 18; } int yOffset = (isHighResolution) ? 3 : 1; +#ifdef SENSECAP_INDICATOR + yOffset -= 3; +#endif +#ifdef T_DECK + yOffset -= 5; +#endif if (config.display.use_12h_clock) { display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2, isPM ? "pm" : "am"); @@ -360,7 +366,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // hour hand radius and y coordinate int16_t hourHandRadius = radius * 0.35; if (isHighResolution) { - int16_t hourHandRadius = radius * 0.55; + hourHandRadius = radius * 0.55; } int16_t hourHandNoonY = centerY - hourHandRadius; @@ -379,7 +385,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 bool isPM = hour >= 12; if (config.display.use_12h_clock) { - bool isPM = hour >= 12; + isPM = hour >= 12; display->setFont(FONT_SMALL); int yOffset = isHighResolution ? 1 : 0; #ifdef USE_EINK diff --git a/src/graphics/draw/ClockRenderer.h b/src/graphics/draw/ClockRenderer.h index 9c3238b14..c8ba62868 100644 --- a/src/graphics/draw/ClockRenderer.h +++ b/src/graphics/draw/ClockRenderer.h @@ -11,8 +11,6 @@ class Screen; namespace ClockRenderer { -// Whether we are showing the digital watch face or the analog one -extern bool digitalWatchFace; // Clock frame functions void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); diff --git a/src/graphics/draw/CompassRenderer.cpp b/src/graphics/draw/CompassRenderer.cpp index 6d8051546..0e5a1d727 100644 --- a/src/graphics/draw/CompassRenderer.cpp +++ b/src/graphics/draw/CompassRenderer.cpp @@ -50,7 +50,7 @@ void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, radius += 4; } Point north(0, -radius); - if (!config.display.compass_north_top) + if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) north.rotate(-myHeading); north.translate(compassX, compassY); diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 92cf49610..5d9b5a33b 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -412,9 +412,9 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, float freq = RadioLibInterface::instance->getFreq(); snprintf(freqStr, sizeof(freqStr), "%.3f", freq); if (config.lora.channel_num == 0) { - snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %smhz", freqStr); + snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr); } else { - snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %smhz (%d)", freqStr, config.lora.channel_num); + snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num); } size_t len = strlen(frequencyslot); if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) { @@ -483,7 +483,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, } // **************************** -// * Memory Screen * +// * System Screen * // **************************** void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -501,7 +501,10 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int line = 1; const int barHeight = 6; const int labelX = x; - const int barsOffset = (isHighResolution) ? 24 : 0; + int barsOffset = (isHighResolution) ? 24 : 0; +#ifdef USE_EINK + barsOffset -= 12; +#endif const int barX = x + 40 + barsOffset; auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) { @@ -590,7 +593,19 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, } line += 1; char appversionstr[35]; - snprintf(appversionstr, sizeof(appversionstr), "Ver.: %s", optstr(APP_VERSION)); + snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION)); + char appversionstr_formatted[40]; + char *lastDot = strrchr(appversionstr, '.'); + if (lastDot) { + size_t prefixLen = lastDot - appversionstr; + strncpy(appversionstr_formatted, appversionstr, prefixLen); + appversionstr_formatted[prefixLen] = '\0'; + strncat(appversionstr_formatted, " (", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1); + strncat(appversionstr_formatted, lastDot + 1, sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1); + strncat(appversionstr_formatted, ")", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1); + strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1); + appversionstr[sizeof(appversionstr) - 1] = '\0'; + } int textWidth = display->getStringWidth(appversionstr); int nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line], appversionstr); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 856f56de2..99989c235 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -8,14 +8,21 @@ #include "NodeDB.h" #include "buzz.h" #include "graphics/Screen.h" +#include "graphics/SharedUIDisplay.h" #include "graphics/draw/UIRenderer.h" #include "main.h" #include "modules/AdminModule.h" #include "modules/CannedMessageModule.h" +#include "modules/KeyVerificationModule.h" +#include "modules/TraceRouteModule.h" + +extern uint16_t TFT_MESH; namespace graphics { menuHandler::screenMenus menuHandler::menuQueue = menu_none; +bool test_enabled = false; +uint8_t test_count = 0; void menuHandler::LoraRegionPicker(uint32_t duration) { @@ -43,73 +50,98 @@ void menuHandler::LoraRegionPicker(uint32_t duration) "PH_433", "PH_868", "PH_915", - "ANZ_433"}; - screen->showOverlayBanner( - "Set the LoRa region", duration, optionsArray, 23, - [](int selected) -> void { - if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) { - config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected); - // This is needed as we wait til picking the LoRa region to generate keys for the first time. - if (!owner.is_licensed) { - bool keygenSuccess = false; - if (config.security.private_key.size == 32) { - // public key is derived from private, so this will always have the same result. - if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { - keygenSuccess = true; - } - } else { - LOG_INFO("Generate new PKI keys"); - crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + "ANZ_433", + "KZ_433", + "KZ_863", + "NP_865", + "BR_902"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Set the LoRa region"; + bannerOptions.durationMs = duration; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 27; + bannerOptions.InitialSelected = 0; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) { + config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected); + // This is needed as we wait til picking the LoRa region to generate keys for the first time. + if (!owner.is_licensed) { + bool keygenSuccess = false; + if (config.security.private_key.size == 32) { + // public key is derived from private, so this will always have the same result. + if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { keygenSuccess = true; } - if (keygenSuccess) { - config.security.public_key.size = 32; - config.security.private_key.size = 32; - owner.public_key.size = 32; - memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); - } + } else { + LOG_INFO("Generate new PKI keys"); + crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + keygenSuccess = true; } - config.lora.tx_enabled = true; - initRegion(); - if (myRegion->dutyCycle < 100) { - config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit + if (keygenSuccess) { + config.security.public_key.size = 32; + config.security.private_key.size = 32; + owner.public_key.size = 32; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); } - service->reloadConfig(SEGMENT_CONFIG); - rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); } - }, - 0); + config.lora.tx_enabled = true; + initRegion(); + if (myRegion->dutyCycle < 100) { + config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit + } + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::TwelveHourPicker() { static const char *optionsArray[] = {"Back", "12-hour", "24-hour"}; - screen->showOverlayBanner("Time Format", 30000, optionsArray, 3, [](int selected) -> void { - if (selected == 0) { + enum optionsNumbers { Back = 0, twelve = 1, twentyfour = 2 }; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Time Format"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 3; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Back) { menuHandler::menuQueue = menuHandler::clock_menu; - } else if (selected == 1) { + screen->runNow(); + } else if (selected == twelve) { config.display.use_12h_clock = true; } else { config.display.use_12h_clock = false; } service->reloadConfig(SEGMENT_CONFIG); - }); + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::ClockFacePicker() { static const char *optionsArray[] = {"Back", "Digital", "Analog"}; - screen->showOverlayBanner("Which Face?", 30000, optionsArray, 3, [](int selected) -> void { - if (selected == 0) { + enum optionsNumbers { Back = 0, Digital = 1, Analog = 2 }; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Which Face?"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 3; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Back) { menuHandler::menuQueue = menuHandler::clock_menu; - } else if (selected == 1) { - graphics::ClockRenderer::digitalWatchFace = true; + screen->runNow(); + } else if (selected == Digital) { + uiconfig.is_clockface_analog = false; + saveUIConfig(); screen->setFrames(Screen::FOCUS_CLOCK); } else { - graphics::ClockRenderer::digitalWatchFace = false; + uiconfig.is_clockface_analog = true; + saveUIConfig(); screen->setFrames(Screen::FOCUS_CLOCK); } - }); + }; + bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1; + screen->showOverlayBanner(bannerOptions); } void menuHandler::TZPicker() @@ -122,6 +154,7 @@ void menuHandler::TZPicker() "US/Mountain", "US/Central", "US/Eastern", + "BR/Brasilia", "UTC", "EU/Western", "EU/" @@ -133,9 +166,14 @@ void menuHandler::TZPicker() "AU/ACST", "AU/AEST", "Pacific/NZ"}; - screen->showOverlayBanner("Pick Timezone", 30000, optionsArray, 17, [](int selected) -> void { + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Pick Timezone"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 19; + bannerOptions.bannerCallback = [](int selected) -> void { if (selected == 0) { menuHandler::menuQueue = menuHandler::clock_menu; + screen->runNow(); } else if (selected == 1) { // Hawaii strncpy(config.device.tzdef, "HST10", sizeof(config.device.tzdef)); } else if (selected == 2) { // Alaska @@ -150,83 +188,92 @@ void menuHandler::TZPicker() strncpy(config.device.tzdef, "CST6CDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef)); } else if (selected == 7) { // Eastern strncpy(config.device.tzdef, "EST5EDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef)); - } else if (selected == 8) { // UTC - strncpy(config.device.tzdef, "UTC", sizeof(config.device.tzdef)); - } else if (selected == 9) { // EU/Western + } else if (selected == 8) { // Brazil + strncpy(config.device.tzdef, "BRT3", sizeof(config.device.tzdef)); + } else if (selected == 9) { // UTC + strncpy(config.device.tzdef, "UTC0", sizeof(config.device.tzdef)); + } else if (selected == 10) { // EU/Western strncpy(config.device.tzdef, "GMT0BST,M3.5.0/1,M10.5.0", sizeof(config.device.tzdef)); - } else if (selected == 10) { // EU/Central + } else if (selected == 11) { // EU/Central strncpy(config.device.tzdef, "CET-1CEST,M3.5.0,M10.5.0/3", sizeof(config.device.tzdef)); - } else if (selected == 11) { // EU/Eastern + } else if (selected == 12) { // EU/Eastern strncpy(config.device.tzdef, "EET-2EEST,M3.5.0/3,M10.5.0/4", sizeof(config.device.tzdef)); - } else if (selected == 12) { // Asia/Kolkata + } else if (selected == 13) { // Asia/Kolkata strncpy(config.device.tzdef, "IST-5:30", sizeof(config.device.tzdef)); - } else if (selected == 13) { // China + } else if (selected == 14) { // China strncpy(config.device.tzdef, "HKT-8", sizeof(config.device.tzdef)); - } else if (selected == 14) { // AU/AWST + } else if (selected == 15) { // AU/AWST strncpy(config.device.tzdef, "AWST-8", sizeof(config.device.tzdef)); - } else if (selected == 15) { // AU/ACST + } else if (selected == 16) { // AU/ACST strncpy(config.device.tzdef, "ACST-9:30ACDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef)); - } else if (selected == 16) { // AU/AEST + } else if (selected == 17) { // AU/AEST strncpy(config.device.tzdef, "AEST-10AEDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef)); - } else if (selected == 17) { // NZ + } else if (selected == 18) { // NZ strncpy(config.device.tzdef, "NZST-12NZDT,M9.5.0,M4.1.0/3", sizeof(config.device.tzdef)); } if (selected != 0) { setenv("TZ", config.device.tzdef, 1); service->reloadConfig(SEGMENT_CONFIG); } - }); + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::clockMenu() { static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"}; - screen->showOverlayBanner("Clock Action", 30000, optionsArray, 4, [](int selected) -> void { - if (selected == 1) { + enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 }; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Clock Action"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 4; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Clock) { menuHandler::menuQueue = menuHandler::clock_face_picker; - screen->setInterval(0); - runASAP = true; - } else if (selected == 2) { + screen->runNow(); + } else if (selected == Time) { menuHandler::menuQueue = menuHandler::twelve_hour_picker; - screen->setInterval(0); - runASAP = true; - } else if (selected == 3) { + screen->runNow(); + } else if (selected == Timezone) { menuHandler::menuQueue = menuHandler::TZ_picker; - screen->setInterval(0); - runASAP = true; + screen->runNow(); } - }); + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::messageResponseMenu() { + enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 }; + + static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"}; + static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset}; + int options = 3; - static const char **optionsArrayPtr; - int options; if (kb_found) { - static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext"}; - optionsArrayPtr = optionsArray; - options = 4; - } else { - static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset"}; - optionsArrayPtr = optionsArray; - options = 3; + optionsArray[options] = "Reply via Freetext"; + optionsEnumArray[options++] = Freetext; } + #ifdef HAS_I2S - static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext", "Read Aloud"}; - optionsArrayPtr = optionsArray; - options = 5; + optionsArray[options] = "Read Aloud"; + optionsEnumArray[options++] = Aloud; #endif - screen->showOverlayBanner("Message Action", 30000, optionsArrayPtr, options, [](int selected) -> void { - if (selected == 1) { + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Message Action"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.optionsCount = options; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Dismiss) { screen->dismissCurrentFrame(); - } else if (selected == 2) { + } else if (selected == Preset) { if (devicestate.rx_text_message.to == NODENUM_BROADCAST) { cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel); } else { cannedMessageModule->LaunchWithDestination(devicestate.rx_text_message.from); } - } else if (selected == 3) { + } else if (selected == Freetext) { if (devicestate.rx_text_message.to == NODENUM_BROADCAST) { cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel); } else { @@ -234,227 +281,830 @@ void menuHandler::messageResponseMenu() } } #ifdef HAS_I2S - else if (selected == 4) { + else if (selected == Aloud) { const meshtastic_MeshPacket &mp = devicestate.rx_text_message; const char *msg = reinterpret_cast(mp.decoded.payload.bytes); audioThread->readAloud(msg); } #endif - }); + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::homeBaseMenu() { - int options; - static const char **optionsArrayPtr; + enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep, enumEnd }; + static const char *optionsArray[enumEnd] = {"Back"}; + static int optionsEnumArray[enumEnd] = {Back}; + int options = 1; + +#ifdef PIN_EINK_EN + optionsArray[options] = "Toggle Backlight"; + optionsEnumArray[options++] = Backlight; +#else + optionsArray[options] = "Sleep Screen"; + optionsEnumArray[options++] = Sleep; +#endif + + optionsArray[options] = "Send Position"; + optionsEnumArray[options++] = Position; + optionsArray[options] = "New Preset Msg"; + optionsEnumArray[options++] = Preset; if (kb_found) { -#ifdef PIN_EINK_EN - static const char *optionsArray[] = {"Back", "Toggle Backlight", "Send Position", "New Preset Msg", "New Freetext Msg"}; -#else - static const char *optionsArray[] = {"Back", "Sleep Screen", "Send Position", "New Preset Msg", "New Freetext Msg"}; -#endif - optionsArrayPtr = optionsArray; - options = 5; - } else { -#ifdef PIN_EINK_EN - static const char *optionsArray[] = {"Back", "Toggle Backlight", "Send Position", "New Preset Msg"}; -#else - static const char *optionsArray[] = {"Back", "Sleep Screen", "Send Position", "New Preset Msg"}; -#endif - optionsArrayPtr = optionsArray; - options = 4; + optionsArray[options] = "New Freetext Msg"; + optionsEnumArray[options++] = Freetext; } - screen->showOverlayBanner("Home Action", 30000, optionsArrayPtr, options, [](int selected) -> void { - if (selected == 1) { + optionsArray[options] = "Bluetooth Toggle"; + optionsEnumArray[options++] = Bluetooth; + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Home Action"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.optionsCount = options; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Backlight) { #ifdef PIN_EINK_EN if (digitalRead(PIN_EINK_EN) == HIGH) { digitalWrite(PIN_EINK_EN, LOW); } else { digitalWrite(PIN_EINK_EN, HIGH); } -#else - screen->setOn(false); #endif - } else if (selected == 2) { - InputEvent event = {.inputEvent = (input_broker_event)175, .kbchar = 175, .touchX = 0, .touchY = 0}; + } else if (selected == Sleep) { + screen->setOn(false); + } else if (selected == Position) { + InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SEND_PING, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); - } else if (selected == 3) { + } else if (selected == Preset) { cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST); - } else if (selected == 4) { + } else if (selected == Freetext) { + cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST); + } else if (selected == Bluetooth) { + menuQueue = bluetooth_toggle_menu; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::textMessageBaseMenu() +{ + enum optionsNumbers { Back, Preset, Freetext, enumEnd }; + + static const char *optionsArray[enumEnd] = {"Back"}; + static int optionsEnumArray[enumEnd] = {Back}; + int options = 1; + optionsArray[options] = "New Preset Msg"; + optionsEnumArray[options++] = Preset; + if (kb_found) { + optionsArray[options] = "New Freetext Msg"; + optionsEnumArray[options++] = Freetext; + } + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Message Action"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.optionsCount = options; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Preset) { + cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST); + } else if (selected == Freetext) { cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST); } - }); + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::systemBaseMenu() +{ + enum optionsNumbers { Back, Notifications, ScreenOptions, PowerMenu, Test, enumEnd }; + static const char *optionsArray[enumEnd] = {"Back"}; + static int optionsEnumArray[enumEnd] = {Back}; + int options = 1; + + optionsArray[options] = "Notifications"; + optionsEnumArray[options++] = Notifications; +#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \ + defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT + optionsArray[options] = "Screen Options"; + optionsEnumArray[options++] = ScreenOptions; +#endif + + optionsArray[options] = "Reboot/Shutdown"; + optionsEnumArray[options++] = PowerMenu; + + if (test_enabled) { + optionsArray[options] = "Test Menu"; + optionsEnumArray[options++] = Test; + } + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "System Action"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = options; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Notifications) { + menuHandler::menuQueue = menuHandler::notifications_menu; + screen->runNow(); + } else if (selected == ScreenOptions) { + menuHandler::menuQueue = menuHandler::screen_options_menu; + screen->runNow(); + } else if (selected == PowerMenu) { + menuHandler::menuQueue = menuHandler::power_menu; + screen->runNow(); + } else if (selected == Test) { + menuHandler::menuQueue = menuHandler::test_menu; + screen->runNow(); + } else if (selected == Back && !test_enabled) { + test_count++; + if (test_count > 4) { + test_enabled = true; + } + } + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::favoriteBaseMenu() { - int options; - static const char **optionsArrayPtr; + enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd }; + static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"}; + static int optionsEnumArray[enumEnd] = {Back, Preset}; + int options = 2; if (kb_found) { - static const char *optionsArray[] = {"Back", "New Preset Msg", "New Freetext Msg"}; - optionsArrayPtr = optionsArray; - options = 3; - } else { - static const char *optionsArray[] = {"Back", "New Preset Msg"}; - optionsArrayPtr = optionsArray; - options = 2; + optionsArray[options] = "New Freetext Msg"; + optionsEnumArray[options++] = Freetext; } - screen->showOverlayBanner("Favorites Action", 30000, optionsArrayPtr, options, [](int selected) -> void { - if (selected == 1) { + optionsArray[options] = "Trace Route"; + optionsEnumArray[options++] = TraceRoute; + optionsArray[options] = "Remove Favorite"; + optionsEnumArray[options++] = Remove; + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Favorites Action"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.optionsCount = options; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Preset) { cannedMessageModule->LaunchWithDestination(graphics::UIRenderer::currentFavoriteNodeNum); - } else if (selected == 2) { + } else if (selected == Freetext) { cannedMessageModule->LaunchFreetextWithDestination(graphics::UIRenderer::currentFavoriteNodeNum); + } else if (selected == Remove) { + menuHandler::menuQueue = menuHandler::remove_favorite; + screen->runNow(); + } else if (selected == TraceRoute) { + if (traceRouteModule) { + traceRouteModule->launch(graphics::UIRenderer::currentFavoriteNodeNum); + } } - }); + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::positionBaseMenu() { - int options; - static const char **optionsArrayPtr; - static const char *optionsArray[] = {"Back", "GPS Toggle", "Compass"}; - static const char *optionsArrayCalibrate[] = {"Back", "GPS Toggle", "Compass", "Compass Calibrate"}; + enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd }; + + static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"}; + static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu}; + int options = 3; #if !MESHTASTIC_EXCLUDE_I2C if (accelerometerThread) { - optionsArrayPtr = optionsArrayCalibrate; - options = 4; + optionsArray[options] = "Compass Calibrate"; + optionsEnumArray[options++] = CompassCalibrate; } else #endif { optionsArrayPtr = optionsArray; options = 3; } - screen->showOverlayBanner("Position Action", 30000, optionsArrayPtr, options, [](int selected) -> void { - if (selected == 1) { -#if MESHTASTIC_EXCLUDE_GPS - menuQueue = menu_none; -#else + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Position Action"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.optionsCount = options; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == GPSToggle) { menuQueue = gps_toggle_menu; -#endif - } else if (selected == 2) { + screen->runNow(); + } else if (selected == CompassMenu) { menuQueue = compass_point_north_menu; - } + screen->runNow(); #if !MESHTASTIC_EXCLUDE_I2C - else if (selected == 3) { + } else if (selected == CompassCalibrate) { accelerometerThread->calibrate(30); - } #endif else { menuQueue = menu_none; } - }); + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::nodeListMenu() { - static const char *optionsArray[] = {"Back", "Reset NodeDB"}; - screen->showOverlayBanner("Node Action", 30000, optionsArray, 2, [](int selected) -> void { - if (selected == 1) { + enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd }; + static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Node Action"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 5; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Favorite) { + menuQueue = add_favorite; + screen->runNow(); + } else if (selected == Verify) { + menuQueue = key_verification_init; + screen->runNow(); + } else if (selected == Reset) { menuQueue = reset_node_db_menu; + screen->runNow(); + } else if (selected == TraceRoute) { + menuQueue = trace_route_menu; + screen->runNow(); } - }); + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::resetNodeDBMenu() { static const char *optionsArray[] = {"Back", "Confirm"}; - screen->showOverlayBanner("Confirm Reset NodeDB", 30000, optionsArray, 2, [](int selected) -> void { + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Confirm Reset NodeDB"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 2; + bannerOptions.bannerCallback = [](int selected) -> void { if (selected == 1) { disableBluetooth(); LOG_INFO("Initiate node-db reset"); nodeDB->resetNodes(); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); } - }); + }; + screen->showOverlayBanner(bannerOptions); } void menuHandler::compassNorthMenu() { + enum optionsNumbers { Back, Dynamic, Fixed, Freeze }; static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"}; - screen->showOverlayBanner("North Directions?", 30000, optionsArray, 4, [](int selected) -> void { - if (selected == 1) { - if (config.display.compass_north_top != false) { - config.display.compass_north_top = false; - service->reloadConfig(SEGMENT_CONFIG); + BannerOverlayOptions bannerOptions; + bannerOptions.message = "North Directions?"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 4; + bannerOptions.InitialSelected = uiconfig.compass_mode + 1; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Dynamic) { + if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) { + uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC; + saveUIConfig(); + screen->setFrames(graphics::Screen::FOCUS_PRESERVE); } - screen->ignoreCompass = false; - screen->setFrames(graphics::Screen::FOCUS_PRESERVE); - } else if (selected == 2) { - if (config.display.compass_north_top != true) { - config.display.compass_north_top = true; - service->reloadConfig(SEGMENT_CONFIG); + } else if (selected == Fixed) { + if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) { + uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING; + saveUIConfig(); + screen->setFrames(graphics::Screen::FOCUS_PRESERVE); } - screen->ignoreCompass = false; - screen->setFrames(graphics::Screen::FOCUS_PRESERVE); - } else if (selected == 3) { - if (config.display.compass_north_top != true) { - config.display.compass_north_top = true; - service->reloadConfig(SEGMENT_CONFIG); + } else if (selected == Freeze) { + if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { + uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING; + saveUIConfig(); + screen->setFrames(graphics::Screen::FOCUS_PRESERVE); } - screen->ignoreCompass = true; - screen->setFrames(graphics::Screen::FOCUS_PRESERVE); - } else if (selected == 0) { + } else if (selected == Back) { menuQueue = position_base_menu; + screen->runNow(); } - }); + }; + screen->showOverlayBanner(bannerOptions); } #if !MESHTASTIC_EXCLUDE_GPS void menuHandler::GPSToggleMenu() { + static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; - screen->showOverlayBanner( - "Toggle GPS", 30000, optionsArray, 3, - [](int selected) -> void { - if (selected == 1) { - config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; - playGPSEnableBeep(); - gps->enable(); - service->reloadConfig(SEGMENT_CONFIG); - } else if (selected == 2) { - config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; - playGPSDisableBeep(); - gps->disable(); - service->reloadConfig(SEGMENT_CONFIG); - } else { - menuQueue = position_base_menu; - } - }, - config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED ? 1 : 2); // set inital selection + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Toggle GPS"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 3; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 1) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; + playGPSEnableBeep(); + gps->enable(); + service->reloadConfig(SEGMENT_CONFIG); + } else if (selected == 2) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; + playGPSDisableBeep(); + gps->disable(); + service->reloadConfig(SEGMENT_CONFIG); + } else { + menuQueue = position_base_menu; + screen->runNow(); + } + }; + bannerOptions.InitialSelected = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED ? 1 : 2; + screen->showOverlayBanner(bannerOptions); } #endif +void menuHandler::BluetoothToggleMenu() +{ + static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Toggle Bluetooth"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 3; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 1 || selected == 2) { + InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0}; + inputBroker->injectInputEvent(&event); + } + }; + bannerOptions.InitialSelected = config.bluetooth.enabled ? 1 : 2; + screen->showOverlayBanner(bannerOptions); +} + void menuHandler::BuzzerModeMenu() { static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"}; - screen->showOverlayBanner( - "Beep Action", 30000, optionsArray, 4, - [](int selected) -> void { - config.device.buzzer_mode = (meshtastic_Config_DeviceConfig_BuzzerMode)selected; - service->reloadConfig(SEGMENT_CONFIG); - }, - config.device.buzzer_mode); + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Buzzer Mode"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 4; + bannerOptions.bannerCallback = [](int selected) -> void { + config.device.buzzer_mode = (meshtastic_Config_DeviceConfig_BuzzerMode)selected; + service->reloadConfig(SEGMENT_CONFIG); + }; + bannerOptions.InitialSelected = config.device.buzzer_mode; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::BrightnessPickerMenu() +{ + static const char *optionsArray[] = {"Back", "Low", "Medium", "High"}; + + // Get current brightness level to set initial selection + int currentSelection = 1; // Default to Medium + if (uiconfig.screen_brightness >= 255) { + currentSelection = 3; // Very High + } else if (uiconfig.screen_brightness >= 128) { + currentSelection = 2; // High + } else { + currentSelection = 1; // Medium + } + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Brightness"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 4; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 1) { // Medium + uiconfig.screen_brightness = 64; + } else if (selected == 2) { // High + uiconfig.screen_brightness = 128; + } else if (selected == 3) { // Very High + uiconfig.screen_brightness = 255; + } + + if (selected != 0) { // Not "Back" + // Apply brightness immediately +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) + // For HELTEC devices, use analogWrite to control backlight + analogWrite(VTFT_LEDA, uiconfig.screen_brightness); +#elif defined(ST7789_CS) + static_cast(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness); +#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) + screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness); +#endif + + // Save to device + saveUIConfig(); + + LOG_INFO("Screen brightness set to %d", uiconfig.screen_brightness); + } + }; + bannerOptions.InitialSelected = currentSelection; + screen->showOverlayBanner(bannerOptions); } void menuHandler::switchToMUIMenu() { - static const char *optionsArray[] = {"Yes", "No"}; - screen->showOverlayBanner("Switch to MUI?", 30000, optionsArray, 2, [](int selected) -> void { - if (selected == 0) { + static const char *optionsArray[] = {"No", "Yes"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Switch to MUI?"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 2; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 1) { config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR; config.bluetooth.enabled = false; service->reloadConfig(SEGMENT_CONFIG); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::TFTColorPickerMenu(OLEDDisplay *display) +{ + static const char *optionsArray[] = {"Back", "Default", "Meshtastic Green", "Yellow", "Red", "Orange", "Purple", "Teal", + "Pink", "White"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Select Screen Color"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 10; + bannerOptions.bannerCallback = [display](int selected) -> void { +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT + uint8_t TFT_MESH_r = 0; + uint8_t TFT_MESH_g = 0; + uint8_t TFT_MESH_b = 0; + if (selected == 1) { + LOG_INFO("Setting color to system default or defined variant"); + // Given just before we set all these to zero, we will allow this to go through + } else if (selected == 2) { + LOG_INFO("Setting color to Meshtastic Green"); + TFT_MESH_r = 103; + TFT_MESH_g = 234; + TFT_MESH_b = 148; + } else if (selected == 3) { + LOG_INFO("Setting color to Yellow"); + TFT_MESH_r = 255; + TFT_MESH_g = 255; + TFT_MESH_b = 128; + } else if (selected == 4) { + LOG_INFO("Setting color to Red"); + TFT_MESH_r = 255; + TFT_MESH_g = 64; + TFT_MESH_b = 64; + } else if (selected == 5) { + LOG_INFO("Setting color to Orange"); + TFT_MESH_r = 255; + TFT_MESH_g = 160; + TFT_MESH_b = 20; + } else if (selected == 6) { + LOG_INFO("Setting color to Purple"); + TFT_MESH_r = 204; + TFT_MESH_g = 153; + TFT_MESH_b = 255; + } else if (selected == 7) { + LOG_INFO("Setting color to Teal"); + TFT_MESH_r = 64; + TFT_MESH_g = 224; + TFT_MESH_b = 208; + } else if (selected == 8) { + LOG_INFO("Setting color to Pink"); + TFT_MESH_r = 255; + TFT_MESH_g = 105; + TFT_MESH_b = 180; + } else if (selected == 9) { + LOG_INFO("Setting color to White"); + TFT_MESH_r = 255; + TFT_MESH_g = 255; + TFT_MESH_b = 255; + } else { + menuQueue = system_base_menu; + screen->runNow(); + } + + if (selected != 0) { + display->setColor(BLACK); + display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + display->setColor(WHITE); + + if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) { +#ifdef TFT_MESH_OVERRIDE + TFT_MESH = TFT_MESH_OVERRIDE; +#else + TFT_MESH = COLOR565(0x67, 0xEA, 0x94); +#endif + } else { + TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b); + } + +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) + static_cast(screen->getDisplayDevice())->setRGB(TFT_MESH); +#endif + + screen->setFrames(graphics::Screen::FOCUS_SYSTEM); + if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) { + uiconfig.screen_rgb_color = 0; + } else { + uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b; + } + LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color); + saveUIConfig(); + } +#endif + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::rebootMenu() +{ + static const char *optionsArray[] = {"Back", "Confirm"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Reboot Device?"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 2; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 1) { + IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0)); + nodeDB->saveToDisk(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + } else { + menuQueue = power_menu; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::shutdownMenu() +{ + static const char *optionsArray[] = {"Back", "Confirm"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Shutdown Device?"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 2; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 1) { + InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0}; + inputBroker->injectInputEvent(&event); + } else { + menuQueue = power_menu; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::addFavoriteMenu() +{ + screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void { + LOG_WARN("Nodenum: %u", nodenum); + nodeDB->set_favorite(true, nodenum); + screen->setFrames(graphics::Screen::FOCUS_PRESERVE); }); } -void menuHandler::handleMenuSwitch() +void menuHandler::removeFavoriteMenu() { + + static const char *optionsArray[] = {"Back", "Yes"}; + BannerOverlayOptions bannerOptions; + std::string message = "Unfavorite This Node?\n"; + auto node = nodeDB->getMeshNode(graphics::UIRenderer::currentFavoriteNodeNum); + if (node && node->has_user) { + message += sanitizeString(node->user.long_name).substr(0, 15); + } + bannerOptions.message = message.c_str(); + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 2; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 1) { + LOG_INFO("Removing %x as favorite node", graphics::UIRenderer::currentFavoriteNodeNum); + nodeDB->set_favorite(false, graphics::UIRenderer::currentFavoriteNodeNum); + screen->setFrames(graphics::Screen::FOCUS_DEFAULT); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::traceRouteMenu() +{ + screen->showNodePicker("Node to Trace", 30000, [](uint32_t nodenum) -> void { + LOG_INFO("Menu: Node picker selected node 0x%08x, traceRouteModule=%p", nodenum, traceRouteModule); + if (traceRouteModule) { + traceRouteModule->startTraceRoute(nodenum); + } + }); +} + +void menuHandler::testMenu() +{ + + static const char *optionsArray[] = {"Back", "Number Picker"}; + BannerOverlayOptions bannerOptions; + std::string message = "Test to Run?\n"; + bannerOptions.message = message.c_str(); + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 2; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 1) { + menuQueue = number_test; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::numberTest() +{ + screen->showNumberPicker("Pick a number\n ", 30000, 4, + [](int number_picked) -> void { LOG_WARN("Nodenum: %u", number_picked); }); +} + +void menuHandler::wifiBaseMenu() +{ + enum optionsNumbers { Back, Wifi_toggle }; + + static const char *optionsArray[] = {"Back", "WiFi Toggle"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "WiFi Menu"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 2; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Wifi_toggle) { + menuQueue = wifi_toggle_menu; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::wifiToggleMenu() +{ + enum optionsNumbers { Back, Wifi_toggle }; + + static const char *optionsArray[] = {"Back", "Disable"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Disable Wifi and\nEnable Bluetooth?"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 2; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Wifi_toggle) { + config.network.wifi_enabled = false; + config.bluetooth.enabled = true; + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::notificationsMenu() +{ + enum optionsNumbers { Back, BuzzerActions }; + static const char *optionsArray[] = {"Back", "Buzzer Actions"}; + static int optionsEnumArray[] = {Back, BuzzerActions}; + int options = 2; + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Notifications"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = options; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == BuzzerActions) { + menuHandler::menuQueue = menuHandler::buzzermodemenupicker; + screen->runNow(); + } else { + menuQueue = system_base_menu; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::screenOptionsMenu() +{ + // Check if brightness is supported + bool hasSupportBrightness = false; +#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) + hasSupportBrightness = true; +#endif + +#if defined(T_DECK) + // TDeck Doesn't seem to support brightness at all, at least not reliably + hasSupportBrightness = false; +#endif + + enum optionsNumbers { Back, Brightness, ScreenColor }; + static const char *optionsArray[4] = {"Back"}; + static int optionsEnumArray[4] = {Back}; + int options = 1; + + // Only show brightness for B&W displays + if (hasSupportBrightness) { + optionsArray[options] = "Brightness"; + optionsEnumArray[options++] = Brightness; + } + + // Only show screen color for TFT displays +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT + optionsArray[options] = "Screen Color"; + optionsEnumArray[options++] = ScreenColor; +#endif + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Screen Options"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = options; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Brightness) { + menuHandler::menuQueue = menuHandler::brightness_picker; + screen->runNow(); + } else if (selected == ScreenColor) { + menuHandler::menuQueue = menuHandler::tftcolormenupicker; + screen->runNow(); + } else { + menuQueue = system_base_menu; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::powerMenu() +{ + + enum optionsNumbers { Back, Reboot, Shutdown, MUI }; + static const char *optionsArray[4] = {"Back"}; + static int optionsEnumArray[4] = {Back}; + int options = 1; + + optionsArray[options] = "Reboot"; + optionsEnumArray[options++] = Reboot; + + optionsArray[options] = "Shutdown"; + optionsEnumArray[options++] = Shutdown; + +#if HAS_TFT + optionsArray[options] = "Switch to MUI"; + optionsEnumArray[options++] = MUI; +#endif + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Reboot / Shutdown"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = options; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Reboot) { + menuHandler::menuQueue = menuHandler::reboot_menu; + screen->runNow(); + } else if (selected == Shutdown) { + menuHandler::menuQueue = menuHandler::shutdown_menu; + screen->runNow(); + } else if (selected == MUI) { + menuHandler::menuQueue = menuHandler::mui_picker; + screen->runNow(); + } else { + menuQueue = system_base_menu; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::keyVerificationInitMenu() +{ + screen->showNodePicker("Node to Verify", 30000, + [](uint32_t selected) -> void { keyVerificationModule->sendInitialRequest(selected); }); +} + +void menuHandler::keyVerificationFinalPrompt() +{ + char message[40] = {0}; + memset(message, 0, sizeof(message)); + sprintf(message, "Verification: \n"); + keyVerificationModule->generateVerificationCode(message + 15); // send the toPhone packet + + if (screen) { + static const char *optionsArray[] = {"Reject", "Accept"}; + graphics::BannerOverlayOptions options; + options.message = message; + options.durationMs = 30000; + options.optionsArrayPtr = optionsArray; + options.optionsCount = 2; + options.notificationType = graphics::notificationTypeEnum::selection_picker; + options.bannerCallback = [=](int selected) { + if (selected == 1) { + auto remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode()); + remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK; + } + }; + screen->showOverlayBanner(options); + } +} + +void menuHandler::handleMenuSwitch(OLEDDisplay *display) +{ + if (menuQueue != menu_none) + test_count = 0; switch (menuQueue) { case menu_none: break; @@ -473,6 +1123,9 @@ void menuHandler::handleMenuSwitch() case clock_menu: clockMenu(); break; + case system_base_menu: + systemBaseMenu(); + break; case position_base_menu: positionBaseMenu(); break; @@ -487,10 +1140,72 @@ void menuHandler::handleMenuSwitch() case reset_node_db_menu: resetNodeDBMenu(); break; + case buzzermodemenupicker: + BuzzerModeMenu(); + break; + case mui_picker: + switchToMUIMenu(); + break; + case tftcolormenupicker: + TFTColorPickerMenu(display); + break; + case brightness_picker: + BrightnessPickerMenu(); + break; + case reboot_menu: + rebootMenu(); + break; + case shutdown_menu: + shutdownMenu(); + break; + case add_favorite: + addFavoriteMenu(); + break; + case remove_favorite: + removeFavoriteMenu(); + break; + case trace_route_menu: + traceRouteMenu(); + break; + case test_menu: + testMenu(); + break; + case number_test: + numberTest(); + break; + case wifi_toggle_menu: + wifiToggleMenu(); + break; + case key_verification_init: + keyVerificationInitMenu(); + break; + case key_verification_final_prompt: + keyVerificationFinalPrompt(); + break; + case bluetooth_toggle_menu: + BluetoothToggleMenu(); + break; + case notifications_menu: + notificationsMenu(); + break; + case screen_options_menu: + screenOptionsMenu(); + break; + case power_menu: + powerMenu(); + break; + case throttle_message: + screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000); + break; } menuQueue = menu_none; } +void menuHandler::saveUIConfig() +{ + nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); +} + } // namespace graphics #endif \ No newline at end of file diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 5a5ee8bf6..2e4923241 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -1,3 +1,5 @@ +#pragma once +#if HAS_SCREEN #include "configuration.h" namespace graphics { @@ -13,30 +15,71 @@ class menuHandler clock_face_picker, clock_menu, position_base_menu, -#if !MESHTASTIC_EXCLUDE_GPS gps_toggle_menu, -#endif compass_point_north_menu, - reset_node_db_menu + reset_node_db_menu, + buzzermodemenupicker, + mui_picker, + tftcolormenupicker, + brightness_picker, + reboot_menu, + shutdown_menu, + add_favorite, + remove_favorite, + test_menu, + number_test, + wifi_toggle_menu, + bluetooth_toggle_menu, + notifications_menu, + screen_options_menu, + power_menu, + system_base_menu, + key_verification_init, + key_verification_final_prompt, + trace_route_menu, + throttle_message, }; static screenMenus menuQueue; static void LoraRegionPicker(uint32_t duration = 30000); - static void handleMenuSwitch(); + static void handleMenuSwitch(OLEDDisplay *display); static void clockMenu(); static void TZPicker(); static void TwelveHourPicker(); static void ClockFacePicker(); static void messageResponseMenu(); static void homeBaseMenu(); + static void textMessageBaseMenu(); + static void systemBaseMenu(); static void favoriteBaseMenu(); static void positionBaseMenu(); static void compassNorthMenu(); static void GPSToggleMenu(); static void BuzzerModeMenu(); static void switchToMUIMenu(); + static void TFTColorPickerMenu(OLEDDisplay *display); static void nodeListMenu(); static void resetNodeDBMenu(); + static void BrightnessPickerMenu(); + static void rebootMenu(); + static void shutdownMenu(); + static void addFavoriteMenu(); + static void removeFavoriteMenu(); + static void traceRouteMenu(); + static void testMenu(); + static void numberTest(); + static void wifiBaseMenu(); + static void wifiToggleMenu(); + static void notificationsMenu(); + static void screenOptionsMenu(); + static void powerMenu(); + + private: + static void saveUIConfig(); + static void keyVerificationInitMenu(); + static void keyVerificationFinalPrompt(); + static void BluetoothToggleMenu(); }; -} // namespace graphics \ No newline at end of file +} // namespace graphics +#endif \ No newline at end of file diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 3f47a3a09..d8746fb69 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -66,10 +66,10 @@ const char *getSafeNodeName(meshtastic_NodeInfoLite *node) strncpy(nodeName, name, sizeof(nodeName) - 1); nodeName[sizeof(nodeName) - 1] = '\0'; } else { - snprintf(nodeName, sizeof(nodeName), "%04X", (uint16_t)(node->num & 0xFFFF)); + snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF)); } } else { - strcpy(nodeName, "?"); + snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF)); } return nodeName; } @@ -522,7 +522,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, double lat = DegD(ourNode->position.latitude_i); double lon = DegD(ourNode->position.longitude_i); - if (!screen->ignoreCompass) { + if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { #if HAS_GPS if (screen->hasHeading()) { heading = screen->getHeading(); // degrees diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index 4866b4060..d9cf280ac 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -26,14 +26,27 @@ extern bool hasUnreadMessage; namespace graphics { -char NotificationRenderer::inEvent = INPUT_BROKER_NONE; +InputEvent NotificationRenderer::inEvent; int8_t NotificationRenderer::curSelected = 0; char NotificationRenderer::alertBannerMessage[256] = {0}; uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are seelctable options const char **NotificationRenderer::optionsArrayPtr = nullptr; +const int *NotificationRenderer::optionsEnumPtr = nullptr; std::function NotificationRenderer::alertBannerCallback = NULL; bool NotificationRenderer::pauseBanner = false; +notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none; +uint32_t NotificationRenderer::numDigits = 0; +uint32_t NotificationRenderer::currentNumber = 0; + +uint32_t pow_of_10(uint32_t n) +{ + uint32_t ret = 1; + for (uint32_t i = 0; i < n; i++) { + ret *= 10; + } + return ret; +} // Used on boot when a certificate is being created void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) @@ -55,29 +68,242 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat } } -void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) +void NotificationRenderer::resetBanner() { + alertBannerMessage[0] = '\0'; + current_notification_type = notificationTypeEnum::none; + + inEvent.inputEvent = INPUT_BROKER_NONE; + inEvent.kbchar = 0; + curSelected = 0; + alertBannerOptions = 0; // last x lines are seelctable options + optionsArrayPtr = nullptr; + optionsEnumPtr = nullptr; + alertBannerCallback = NULL; + pauseBanner = false; + numDigits = 0; + currentNumber = 0; + + nodeDB->pause_sort(false); +} + +void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state) +{ + if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0') + resetBanner(); if (!isOverlayBannerShowing() || pauseBanner) return; + switch (current_notification_type) { + case notificationTypeEnum::none: + // Do nothing - no notification to display + break; + case notificationTypeEnum::text_banner: + case notificationTypeEnum::selection_picker: + drawAlertBannerOverlay(display, state); + break; + case notificationTypeEnum::node_picker: + drawNodePicker(display, state); + break; + case notificationTypeEnum::number_picker: + drawNumberPicker(display, state); + break; + } +} + +void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state) +{ + const char *lineStarts[MAX_LINES + 1] = {0}; + uint16_t lineCount = 0; + + // Parse lines + char *alertEnd = alertBannerMessage + strnlen(alertBannerMessage, sizeof(alertBannerMessage)); + lineStarts[lineCount] = alertBannerMessage; + + // Find lines + while ((lineCount < MAX_LINES) && (lineStarts[lineCount] < alertEnd)) { + lineStarts[lineCount + 1] = std::find((char *)lineStarts[lineCount], alertEnd, '\n'); + if (lineStarts[lineCount + 1][0] == '\n') + lineStarts[lineCount + 1] += 1; + lineCount++; + } + // modulo to extract + uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1)); + // Handle input + if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { + if (this_digit == 9) { + currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1)); + } else { + currentNumber += (pow_of_10(numDigits - curSelected - 1)); + } + } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { + if (this_digit == 0) { + currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1)); + } else { + currentNumber -= (pow_of_10(numDigits - curSelected - 1)); + } + } else if (inEvent.inputEvent == INPUT_BROKER_ANYKEY) { + if (inEvent.kbchar > 47 && inEvent.kbchar < 58) { // have a digit + currentNumber -= this_digit * (pow_of_10(numDigits - curSelected - 1)); + currentNumber += (inEvent.kbchar - 48) * (pow_of_10(numDigits - curSelected - 1)); + curSelected++; + } + } else if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_RIGHT) { + curSelected++; + } else if (inEvent.inputEvent == INPUT_BROKER_LEFT) { + curSelected--; + } else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) && + alertBannerUntil != 0) { + resetBanner(); + return; + } + if (curSelected == static_cast(numDigits)) { + alertBannerCallback(currentNumber); + resetBanner(); + return; + } + + inEvent.inputEvent = INPUT_BROKER_NONE; + if (alertBannerMessage[0] == '\0') + return; + + uint16_t totalLines = lineCount + 2; + const char *linePointers[totalLines + 1] = {0}; // this is sort of a dynamic allocation + + // copy the linestarts to display to the linePointers holder + for (uint16_t i = 0; i < lineCount; i++) { + linePointers[i] = lineStarts[i]; + } + std::string digits = " "; + std::string arrowPointer = " "; + for (uint16_t i = 0; i < numDigits; i++) { + // Modulo minus modulo to return just the current number + digits += std::to_string((currentNumber % (pow_of_10(numDigits - i))) / (pow_of_10(numDigits - i - 1))) + " "; + if (curSelected == i) { + arrowPointer += "^ "; + } else { + arrowPointer += "_ "; + } + } + + linePointers[lineCount++] = digits.c_str(); + linePointers[lineCount++] = arrowPointer.c_str(); + + drawNotificationBox(display, state, linePointers, totalLines, 0); +} + +void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state) +{ + static uint32_t selectedNodenum = 0; // === Layout Configuration === - constexpr uint16_t hPadding = 5; constexpr uint16_t vPadding = 2; - constexpr uint8_t lineSpacing = 1; + alertBannerOptions = nodeDB->getNumMeshNodes() - 1; - bool needs_bell = (strstr(alertBannerMessage, "Alert Received") != nullptr); + // let the box drawing function calculate the widths? - // Setup font and alignment - display->setFont(FONT_SMALL); - display->setTextAlignment(TEXT_ALIGN_LEFT); + const char *lineStarts[MAX_LINES + 1] = {0}; + uint16_t lineCount = 0; + + // Parse lines + char *alertEnd = alertBannerMessage + strnlen(alertBannerMessage, sizeof(alertBannerMessage)); + lineStarts[lineCount] = alertBannerMessage; + + while ((lineCount < MAX_LINES) && (lineStarts[lineCount] < alertEnd)) { + lineStarts[lineCount + 1] = std::find((char *)lineStarts[lineCount], alertEnd, '\n'); + if (lineStarts[lineCount + 1][0] == '\n') + lineStarts[lineCount + 1] += 1; + lineCount++; + } + + // Handle input + if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { + curSelected--; + } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { + curSelected++; + } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { + alertBannerCallback(selectedNodenum); + resetBanner(); + return; + } else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) && + alertBannerUntil != 0) { + resetBanner(); + return; + } + + if (curSelected == -1) + curSelected = alertBannerOptions - 1; + if (curSelected == alertBannerOptions) + curSelected = 0; + + inEvent.inputEvent = INPUT_BROKER_NONE; + if (alertBannerMessage[0] == '\0') + return; + + uint16_t totalLines = lineCount + alertBannerOptions; + uint16_t screenHeight = display->height(); + uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3; + uint8_t visibleTotalLines = std::min(totalLines, (screenHeight - vPadding * 2) / effectiveLineHeight); + uint8_t linesShown = lineCount; + const char *linePointers[visibleTotalLines + 1] = {0}; // this is sort of a dynamic allocation + + // copy the linestarts to display to the linePointers holder + for (int i = 0; i < lineCount; i++) { + linePointers[i] = lineStarts[i]; + } + char scratchLineBuffer[visibleTotalLines - lineCount][40]; + + uint8_t firstOptionToShow = 0; + if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) { + if (curSelected > alertBannerOptions - visibleTotalLines + lineCount) + firstOptionToShow = alertBannerOptions - visibleTotalLines + lineCount; + else + firstOptionToShow = curSelected - 1; + } else { + firstOptionToShow = 0; + } + int scratchLineNum = 0; + for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) { + char temp_name[16] = {0}; + if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) { + std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name); + strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1); + + } else { + snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF)); + } + // make temp buffer for name + // fi + if (i == curSelected) { + selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num; + if (isHighResolution) { + strncpy(scratchLineBuffer[scratchLineNum], "> ", 3); + strncpy(scratchLineBuffer[scratchLineNum] + 2, temp_name, 36); + strncpy(scratchLineBuffer[scratchLineNum] + strlen(temp_name) + 2, " <", 3); + } else { + strncpy(scratchLineBuffer[scratchLineNum], ">", 2); + strncpy(scratchLineBuffer[scratchLineNum] + 1, temp_name, 37); + strncpy(scratchLineBuffer[scratchLineNum] + strlen(temp_name) + 1, "<", 2); + } + scratchLineBuffer[scratchLineNum][39] = '\0'; + } else { + strncpy(scratchLineBuffer[scratchLineNum], temp_name, 36); + } + linePointers[linesShown] = scratchLineBuffer[scratchLineNum++]; + } + drawNotificationBox(display, state, linePointers, totalLines, firstOptionToShow); +} + +void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) +{ + // === Layout Configuration === + constexpr uint16_t vPadding = 2; - constexpr int MAX_LINES = 5; uint16_t optionWidths[alertBannerOptions] = {0}; uint16_t maxWidth = 0; uint16_t arrowsWidth = display->getStringWidth("> <", 4, true); uint16_t lineWidths[MAX_LINES] = {0}; uint16_t lineLengths[MAX_LINES] = {0}; - char *lineStarts[MAX_LINES + 1]; + const char *lineStarts[MAX_LINES + 1] = {0}; uint16_t lineCount = 0; char lineBuffer[40] = {0}; @@ -86,7 +312,7 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp lineStarts[lineCount] = alertBannerMessage; while ((lineCount < MAX_LINES) && (lineStarts[lineCount] < alertEnd)) { - lineStarts[lineCount + 1] = std::find(lineStarts[lineCount], alertEnd, '\n'); + lineStarts[lineCount + 1] = std::find((char *)lineStarts[lineCount], alertEnd, '\n'); lineLengths[lineCount] = lineStarts[lineCount + 1] - lineStarts[lineCount]; if (lineStarts[lineCount + 1][0] == '\n') lineStarts[lineCount + 1] += 1; @@ -107,15 +333,23 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp // Handle input if (alertBannerOptions > 0) { - if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { + if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { curSelected--; - } else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { + } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { curSelected++; - } else if (inEvent == INPUT_BROKER_SELECT) { - alertBannerCallback(curSelected); - alertBannerMessage[0] = '\0'; - } else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) { - alertBannerMessage[0] = '\0'; + } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { + if (optionsEnumPtr != nullptr) { + alertBannerCallback(optionsEnumPtr[curSelected]); + optionsEnumPtr = nullptr; + } else { + alertBannerCallback(curSelected); + } + resetBanner(); + return; + } else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) && + alertBannerUntil != 0) { + resetBanner(); + return; } if (curSelected == -1) @@ -123,16 +357,102 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp if (curSelected == alertBannerOptions) curSelected = 0; } else { - if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_ALT_LONG || inEvent == INPUT_BROKER_CANCEL) { - alertBannerMessage[0] = '\0'; + if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_ALT_LONG || + inEvent.inputEvent == INPUT_BROKER_CANCEL) { + resetBanner(); + return; } } - inEvent = INPUT_BROKER_NONE; + inEvent.inputEvent = INPUT_BROKER_NONE; if (alertBannerMessage[0] == '\0') return; - // === Box Size Calculation === + uint16_t totalLines = lineCount + alertBannerOptions; + + uint16_t screenHeight = display->height(); + uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3; + uint8_t visibleTotalLines = std::min(totalLines, (screenHeight - vPadding * 2) / effectiveLineHeight); + uint8_t linesShown = lineCount; + const char *linePointers[visibleTotalLines + 1] = {0}; // this is sort of a dynamic allocation + + // copy the linestarts to display to the linePointers holder + for (int i = 0; i < lineCount; i++) { + linePointers[i] = lineStarts[i]; + } + + uint8_t firstOptionToShow = 0; + if (alertBannerOptions > 0) { + if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) { + if (curSelected > alertBannerOptions - visibleTotalLines + lineCount) + firstOptionToShow = alertBannerOptions - visibleTotalLines + lineCount; + else + firstOptionToShow = curSelected - 1; + } else { + firstOptionToShow = 0; + } + } + + for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) { + if (i == curSelected) { + if (isHighResolution) { + strncpy(lineBuffer, "> ", 3); + strncpy(lineBuffer + 2, optionsArrayPtr[i], 36); + strncpy(lineBuffer + strlen(optionsArrayPtr[i]) + 2, " <", 3); + } else { + strncpy(lineBuffer, ">", 2); + strncpy(lineBuffer + 1, optionsArrayPtr[i], 37); + strncpy(lineBuffer + strlen(optionsArrayPtr[i]) + 1, "<", 2); + } + lineBuffer[39] = '\0'; + linePointers[linesShown] = lineBuffer; + } else { + linePointers[linesShown] = optionsArrayPtr[i]; + } + } + if (alertBannerOptions > 0) { + drawNotificationBox(display, state, linePointers, totalLines, firstOptionToShow, maxWidth); + } else { + drawNotificationBox(display, state, linePointers, totalLines, firstOptionToShow); + } +} + +void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[], + uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth) +{ + + bool is_picker = false; + uint16_t lineCount = 0; + // === Layout Configuration === + constexpr uint16_t hPadding = 5; + constexpr uint16_t vPadding = 2; + bool needs_bell = false; + uint16_t lineWidths[totalLines] = {0}; + uint16_t lineLengths[totalLines] = {0}; + + if (maxWidth != 0) + is_picker = true; + + // Setup font and alignment + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + while (lines[lineCount] != nullptr) { + auto newlinePointer = strchr(lines[lineCount], '\n'); + if (newlinePointer) + lineLengths[lineCount] = (newlinePointer - lines[lineCount]); // Check for newlines first + else // if the newline wasn't found, then pull string length from strlen + lineLengths[lineCount] = strlen(lines[lineCount]); + lineWidths[lineCount] = display->getStringWidth(lines[lineCount], lineLengths[lineCount], true); + if (!is_picker) { + needs_bell |= (strstr(alertBannerMessage, "Alert Received") != nullptr); + if (lineWidths[lineCount] > maxWidth) + maxWidth = lineWidths[lineCount]; + } + lineCount++; + } + // count lines + uint16_t boxWidth = hPadding * 2 + maxWidth; if (needs_bell) { if (isHighResolution && boxWidth <= 150) @@ -141,14 +461,19 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp boxWidth += 20; } - uint16_t totalLines = lineCount + alertBannerOptions; uint16_t screenHeight = display->height(); uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3; - uint8_t visibleTotalLines = std::min(totalLines, (screenHeight - vPadding * 2) / effectiveLineHeight); + uint8_t visibleTotalLines = std::min(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight); uint16_t contentHeight = visibleTotalLines * effectiveLineHeight; uint16_t boxHeight = contentHeight + vPadding * 2; + if (visibleTotalLines == 1) { + boxHeight += (isHighResolution) ? 4 : 3; + } int16_t boxLeft = (display->width() / 2) - (boxWidth / 2); + if (totalLines > visibleTotalLines) { + boxWidth += (isHighResolution) ? 4 : 2; + } int16_t boxTop = (display->height() / 2) - (boxHeight / 2); // === Draw Box === @@ -169,21 +494,18 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp // === Draw Content === int16_t lineY = boxTop + vPadding; - uint8_t linesShown = 0; - - for (int i = 0; i < lineCount && linesShown < visibleTotalLines; i++, linesShown++) { - strncpy(lineBuffer, lineStarts[i], 40); - lineBuffer[lineLengths[i] > 39 ? 39 : lineLengths[i]] = '\0'; - + for (int i = 0; i < lineCount; i++) { int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2; if (needs_bell && i == 0) { int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2; display->drawXbm(textX - 10, bellY, 8, 8, bell_alert); display->drawXbm(textX + lineWidths[i] + 2, bellY, 8, 8, bell_alert); } - + char lineBuffer[lineLengths[i] + 1]; + strncpy(lineBuffer, lines[i], lineLengths[i]); + lineBuffer[lineLengths[i]] = '\0'; // Determine if this is a pop-up or a pick list - if (alertBannerOptions > 0) { + if (alertBannerOptions > 0 && i == 0) { // Pick List display->setColor(WHITE); int background_yOffset = 1; @@ -199,39 +521,14 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp lineY += (effectiveLineHeight - 2 - background_yOffset); } else { // Pop-up - display->drawString(textX, lineY - 2, lineBuffer); + display->drawString(textX, lineY, lineBuffer); lineY += (effectiveLineHeight); } } - uint8_t firstOptionToShow = 0; - if (alertBannerOptions > 0) { - if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) - firstOptionToShow = curSelected - 1; - else - firstOptionToShow = 0; - } - - for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) { - if (i == curSelected) { - strncpy(lineBuffer, "> ", 3); - strncpy(lineBuffer + 2, optionsArrayPtr[i], 36); - strncpy(lineBuffer + strlen(optionsArrayPtr[i]) + 2, " <", 3); - lineBuffer[39] = '\0'; - } else { - strncpy(lineBuffer, optionsArrayPtr[i], 40); - lineBuffer[39] = '\0'; - } - - int16_t textX = boxLeft + (boxWidth - optionWidths[i] - (i == curSelected ? arrowsWidth : 0)) / 2; - display->drawString(textX, lineY, lineBuffer); - lineY += effectiveLineHeight; - } - // === Scroll Bar (Thicker, inside box, not over title) === if (totalLines > visibleTotalLines) { const uint8_t scrollBarWidth = 5; - const uint8_t scrollPadding = 2; int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2; int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight; // start after title line @@ -239,7 +536,7 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp float ratio = (float)visibleTotalLines / totalLines; uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4); - float scrollRatio = (float)(firstOptionToShow + linesShown - visibleTotalLines) / (totalLines - visibleTotalLines); + float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines); uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight); display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight); diff --git a/src/graphics/draw/NotificationRenderer.h b/src/graphics/draw/NotificationRenderer.h index 2ec5fd9ec..9c30b329c 100644 --- a/src/graphics/draw/NotificationRenderer.h +++ b/src/graphics/draw/NotificationRenderer.h @@ -2,6 +2,8 @@ #include "OLEDDisplay.h" #include "OLEDDisplayUi.h" +#include "graphics/Screen.h" +#define MAX_LINES 5 namespace graphics { @@ -9,21 +11,34 @@ namespace graphics class NotificationRenderer { public: - static char inEvent; + static InputEvent inEvent; + static char inKeypress; static int8_t curSelected; static char alertBannerMessage[256]; static uint32_t alertBannerUntil; // 0 is a special case meaning forever static const char **optionsArrayPtr; + static const int *optionsEnumPtr; static uint8_t alertBannerOptions; // last x lines are seelctable options static std::function alertBannerCallback; + static uint32_t numDigits; + static uint32_t currentNumber; static bool pauseBanner; + static void resetBanner(); + static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state); static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state); + static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state); + static void drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state); + static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1], + uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0); + static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); static bool isOverlayBannerShowing(); + + static graphics::notificationTypeEnum current_notification_type; }; } // namespace graphics diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 9c3a9eabb..71d92616f 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -18,38 +18,29 @@ #include #include -bool isAllowedPunctuation(char c) -{ - const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ "; - return allowed.find(c) != std::string::npos; -} - -std::string sanitizeString(const std::string &input) -{ - std::string output; - bool inReplacement = false; - - for (char c : input) { - if (std::isalnum(static_cast(c)) || isAllowedPunctuation(c)) { - output += c; - inReplacement = false; - } else { - if (!inReplacement) { - output += 0xbf; // ISO-8859-1 for inverted question mark - inReplacement = true; - } - } - } - - return output; -} - // External variables extern graphics::Screen *screen; namespace graphics { NodeNum UIRenderer::currentFavoriteNodeNum = 0; +std::vector graphics::UIRenderer::favoritedNodes; + +void graphics::UIRenderer::rebuildFavoritedNodes() +{ + favoritedNodes.clear(); + size_t total = nodeDB->getNumMeshNodes(); + for (size_t i = 0; i < total; i++) { + meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); + if (!n || n->num == nodeDB->getNodeNum()) + continue; + if (n->is_favorite) + favoritedNodes.push_back(n); + } + + std::sort(favoritedNodes.begin(), favoritedNodes.end(), + [](const meshtastic_NodeInfoLite *a, const meshtastic_NodeInfoLite *b) { return a->num < b->num; }); +} #if !MESHTASTIC_EXCLUDE_GPS // GeoCoord object for coordinate conversions @@ -227,27 +218,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes // ********************** void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y) { - // --- Cache favorite nodes for the current frame only, to save computation --- - static std::vector favoritedNodes; - static int prevFrame = -1; - // --- Only rebuild favorites list if we're on a new frame --- - if (state->currentFrame != prevFrame) { - prevFrame = state->currentFrame; - favoritedNodes.clear(); - size_t total = nodeDB->getNumMeshNodes(); - for (size_t i = 0; i < total; i++) { - meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); - // Skip nulls and ourself - if (!n || n->num == nodeDB->getNodeNum()) - continue; - if (n->is_favorite) - favoritedNodes.push_back(n); - } - // Keep a stable, consistent display order - std::sort(favoritedNodes.begin(), favoritedNodes.end(), - [](const meshtastic_NodeInfoLite *a, const meshtastic_NodeInfoLite *b) { return a->num < b->num; }); - } if (favoritedNodes.empty()) return; @@ -443,7 +414,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); */ float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i)); - if (screen->ignoreCompass) { + if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) { myHeading = 0; } else { bearing -= myHeading; @@ -488,7 +459,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st const auto &op = ourNode->position; float myHeading = 0; - if (!screen->ignoreCompass) { + if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180 : screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i)); } @@ -500,7 +471,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); */ float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i)); - if (!screen->ignoreCompass) + if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) bearing -= myHeading; graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, bearing); @@ -600,7 +571,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta int chutil_bar_width = (isHighResolution) ? 100 : 50; if (!config.bluetooth.enabled) { +#if defined(USE_EINK) + chutil_bar_width = (isHighResolution) ? 50 : 30; +#else chutil_bar_width = (isHighResolution) ? 80 : 40; +#endif } int chutil_bar_height = (isHighResolution) ? 12 : 7; int extraoffset = (isHighResolution) ? 6 : 3; @@ -679,7 +654,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta char combinedName[50]; snprintf(combinedName, sizeof(combinedName), "%s (%s)", longNameStr.empty() ? "" : longNameStr.c_str(), shortnameble); - if (SCREEN_WIDTH - (display->getStringWidth(longName) + display->getStringWidth(shortnameble)) > 10) { + if (SCREEN_WIDTH - (display->getStringWidth(combinedName)) > 10) { size_t len = strlen(combinedName); if (len >= 3 && strcmp(combinedName + len - 3, " ()") == 0) { combinedName[len - 3] = '\0'; // Remove the last three characters @@ -690,7 +665,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta nameX, ((rows == 4) ? getTextPositions(display)[line++] : getTextPositions(display)[line++]) + yOffset, combinedName); } else { // === LongName Centered === - textWidth = display->getStringWidth(longName); + textWidth = display->getStringWidth(longNameStr.c_str()); nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], longNameStr.c_str()); @@ -933,7 +908,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU // === Determine Compass Heading === float heading = 0; bool validHeading = false; - if (screen->ignoreCompass) { + if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) { validHeading = true; } else { if (screen->hasHeading()) { @@ -999,7 +974,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU // "N" label float northAngle = 0; - if (!config.display.compass_north_top) + if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) northAngle = -heading; float radius = compassRadius; int16_t nX = compassX + (radius - 1) * sin(northAngle); @@ -1042,7 +1017,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU // "N" label float northAngle = 0; - if (!config.display.compass_north_top) + if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) northAngle = -heading; float radius = compassRadius; int16_t nX = compassX + (radius - 1) * sin(northAngle); @@ -1066,9 +1041,16 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU void UIRenderer::drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { static const uint8_t xbm[] = USERPREFS_OEM_IMAGE_DATA; - display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2, - y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH, - USERPREFS_OEM_IMAGE_HEIGHT, xbm); + if (isHighResolution) { + display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2, + y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH, + USERPREFS_OEM_IMAGE_HEIGHT, xbm); + } else { + + display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2, + y + (SCREEN_HEIGHT - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH, + USERPREFS_OEM_IMAGE_HEIGHT, xbm); + } switch (USERPREFS_OEM_FONT_SIZE) { case 0: @@ -1084,7 +1066,9 @@ void UIRenderer::drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, O display->setTextAlignment(TEXT_ALIGN_LEFT); const char *title = USERPREFS_OEM_TEXT; - display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); + if (isHighResolution) { + display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); + } display->setFont(FONT_SMALL); // Draw region in upper left diff --git a/src/graphics/draw/UIRenderer.h b/src/graphics/draw/UIRenderer.h index 9e5e8c4b4..3c8e1dd9d 100644 --- a/src/graphics/draw/UIRenderer.h +++ b/src/graphics/draw/UIRenderer.h @@ -61,6 +61,8 @@ class UIRenderer static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); static NodeNum currentFavoriteNodeNum; + static std::vector favoritedNodes; + static void rebuildFavoritedNodes(); // OEM screens #ifdef USERPREFS_OEM_TEXT diff --git a/src/graphics/emotes.cpp b/src/graphics/emotes.cpp index 205d5c660..e1a105d20 100644 --- a/src/graphics/emotes.cpp +++ b/src/graphics/emotes.cpp @@ -11,11 +11,11 @@ const Emote emotes[] = { {"\U0001F44E", thumbdown, thumbs_width, thumbs_height}, // 👎 Thumbs Down // --- Smileys (Multiple Unicode Aliases) --- - {"\U0001F60A", smiley, smiley_width, smiley_height}, // 😊 Smiling Face with Smiling Eyes - {"\U0001F600", smiley, smiley_width, smiley_height}, // 😀 Grinning Face - {"\U0001F642", smiley, smiley_width, smiley_height}, // 🙂 Slightly Smiling Face - {"\U0001F609", smiley, smiley_width, smiley_height}, // 😉 Winking Face - {"\U0001F601", smiley, smiley_width, smiley_height}, // 😁 Grinning Face with Smiling Eyes + {"\U0001F60A", Smiling_Eyes, Smiling_Eyes_width, Smiling_Eyes_height}, // 😊 Smiling Eyes + {"\U0001F600", Grinning, Grinning_width, Grinning_height}, // 😀 Grinning Face + {"\U0001F642", Slightly_Smiling, Slightly_Smiling_width, Slightly_Smiling_height}, // 🙂 Slightly Smiling Face + {"\U0001F609", Winking_Face, Winking_Face_width, Winking_Face_height}, // 😉 Winking Face + {"\U0001F601", Grinning_Smiling_Eyes, Grinning_Smiling_Eyes_width, Grinning_Smiling_Eyes_height}, // 😁 Grinning Smiling Eyes // --- Question/Alert --- {"\u2753", question, question_width, question_height}, // ❓ Question Mark @@ -23,10 +23,11 @@ const Emote emotes[] = { // --- Laughing Faces --- {"\U0001F602", haha, haha_width, haha_height}, // 😂 Face with Tears of Joy - {"\U0001F923", haha, haha_width, haha_height}, // 🤣 Rolling on the Floor Laughing - {"\U0001F606", haha, haha_width, haha_height}, // 😆 Smiling with Open Mouth and Closed Eyes - {"\U0001F605", haha, haha_width, haha_height}, // 😅 Smiling with Sweat - {"\U0001F604", haha, haha_width, haha_height}, // 😄 Grinning Face with Smiling Eyes + {"\U0001F923", ROFL, ROFL_width, ROFL_height}, // 🤣 Rolling on the Floor Laughing + {"\U0001F606", Smiling_Closed_Eyes, Smiling_Closed_Eyes_width, Smiling_Closed_Eyes_height}, // 😆 Smiling Closed Eyes + {"\U0001F605", haha, haha_width, haha_height}, // 😅 Smiling with Sweat + {"\U0001F604", Grinning_SmilingEyes2, Grinning_SmilingEyes2_width, + Grinning_SmilingEyes2_height}, // 😄 Grinning Face with Smiling Eyes // --- Gestures and People --- {"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand @@ -78,13 +79,45 @@ const unsigned char thumbdown[] PROGMEM = { 0x80, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, }; -const unsigned char smiley[] PROGMEM = { - 0x00, 0xfe, 0x0f, 0x00, 0x80, 0x01, 0x30, 0x00, 0x40, 0x00, 0xc0, 0x00, 0x20, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x02, - 0x08, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x10, 0x02, 0x0e, 0x0e, 0x10, 0x02, 0x09, 0x12, 0x10, - 0x01, 0x09, 0x12, 0x20, 0x01, 0x0f, 0x1e, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, - 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x81, 0x00, 0x20, 0x20, - 0x82, 0x00, 0x20, 0x10, 0x02, 0x01, 0x10, 0x10, 0x04, 0x02, 0x08, 0x08, 0x04, 0xfc, 0x07, 0x08, 0x08, 0x00, 0x00, 0x04, - 0x10, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x01, 0x40, 0x00, 0xc0, 0x00, 0x80, 0x01, 0x30, 0x00, 0x00, 0xfe, 0x0f, 0x00}; +const unsigned char Smiling_Eyes[] PROGMEM = { + 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1, + 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf, + 0x7e, 0xf8, 0xc3, 0xdf, 0x3e, 0xf0, 0x81, 0xdf, 0xbf, 0xf7, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0x3f, 0xff, + 0x6f, 0xff, 0xdf, 0xfe, 0x6f, 0xff, 0xdf, 0xfe, 0x9f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf, + 0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3, + 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0}; + +const unsigned char Grinning[] PROGMEM = { + 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1, + 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf, + 0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf, + 0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3, + 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0}; + +const unsigned char Slightly_Smiling[] PROGMEM = { + 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1, + 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf, + 0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf, + 0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3, + 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0}; + +const unsigned char Winking_Face[] PROGMEM = { + 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1, + 0xf0, 0xf0, 0xff, 0xc3, 0x78, 0xef, 0xc3, 0xc7, 0xb8, 0xdf, 0xbd, 0xcf, 0xfc, 0xf9, 0x7f, 0xcf, 0xfc, 0xf0, 0xff, 0xcf, + 0xfe, 0xf0, 0xc3, 0xdf, 0xfe, 0xf0, 0x81, 0xdf, 0xff, 0xf0, 0xbf, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf, + 0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3, + 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0}; + +const unsigned char Grinning_Smiling_Eyes[] PROGMEM = { + 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1, + 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf8, 0xe3, 0xcf, 0x7c, 0xf7, 0xdd, 0xcf, + 0xbe, 0xef, 0xbe, 0xdf, 0xbe, 0xef, 0xbe, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x5e, 0x55, 0x55, 0xdf, 0x5e, 0x55, 0x55, 0xdf, + 0x3c, 0x00, 0x80, 0xcf, 0x7c, 0x55, 0xd5, 0xcf, 0xf8, 0x54, 0xe5, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3, + 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0}; const unsigned char question[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00, @@ -104,31 +137,52 @@ const unsigned char bang[] PROGMEM = { }; const unsigned char haha[] PROGMEM = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, - 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x1F, 0x3E, 0x00, 0x80, 0x03, 0x70, 0x00, 0xC0, 0x01, 0xE0, 0x00, 0xC0, 0x00, 0xC2, 0x00, - 0x60, 0x00, 0x03, 0x00, 0x60, 0x00, 0xC1, 0x1F, 0x60, 0x80, 0x8F, 0x31, 0x30, 0x0E, 0x80, 0x31, 0x30, 0x10, 0x30, 0x1F, - 0x30, 0x08, 0x58, 0x00, 0x30, 0x04, 0x6C, 0x03, 0x60, 0x00, 0xF3, 0x01, 0x60, 0xC0, 0xFC, 0x01, 0x80, 0x38, 0xBF, 0x01, - 0xE0, 0xC5, 0xDF, 0x00, 0xB0, 0xF9, 0xEF, 0x00, 0x30, 0xF1, 0x73, 0x00, 0xB0, 0x1D, 0x3E, 0x00, 0xF0, 0xFD, 0x0F, 0x00, - 0xE0, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; + 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xf9, 0xf3, 0xc0, + 0xf0, 0xfe, 0xef, 0xc1, 0x38, 0xff, 0x9f, 0xc3, 0xd8, 0xff, 0x7f, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xcf, + 0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xef, 0xff, 0xff, 0xde, 0xe7, 0xff, 0xff, 0xdc, 0xeb, 0xff, 0xff, 0xda, + 0xed, 0xff, 0xff, 0xd6, 0xee, 0xff, 0xff, 0xce, 0x36, 0x00, 0x80, 0xcd, 0xb8, 0xff, 0xbf, 0xc3, 0x7e, 0x00, 0xc0, 0xdf, + 0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3, + 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0}; + +const unsigned char ROFL[] PROGMEM = { + 0x00, 0x00, 0x00, 0xc0, 0x00, 0xfc, 0x07, 0xc0, 0x00, 0xff, 0x1f, 0xc0, 0x80, 0xff, 0x7f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, + 0xe0, 0x9f, 0xff, 0xc1, 0xf0, 0x9f, 0xff, 0xc0, 0xf8, 0x9f, 0x7f, 0xcb, 0xf8, 0x9f, 0xbf, 0xcb, 0xfc, 0x9f, 0xdf, 0xdb, + 0xfc, 0x1f, 0x08, 0xdc, 0xfe, 0x1f, 0xf8, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0x1e, 0xf0, 0x7f, 0xfe, 0x1e, 0xf0, 0xbf, 0xfe, + 0xfe, 0xf3, 0xdf, 0xfe, 0xfe, 0xf3, 0x6f, 0xfe, 0xfe, 0xf3, 0x37, 0xfe, 0xfe, 0xeb, 0x1b, 0xfe, 0xfc, 0xef, 0x0d, 0xde, + 0xfc, 0xe7, 0x06, 0xcf, 0xf8, 0x6b, 0x83, 0xcf, 0xf8, 0x0d, 0xc0, 0xc7, 0xf0, 0xed, 0xff, 0xc7, 0xe0, 0xee, 0xff, 0xc3, + 0xc0, 0xee, 0xff, 0xc1, 0x80, 0xee, 0xff, 0xc0, 0x00, 0xe6, 0x3f, 0xc0, 0x00, 0xf0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0xc0}; + +const unsigned char Smiling_Closed_Eyes[] PROGMEM = { + 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1, + 0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0x7c, 0xfe, 0xcf, 0xcf, 0xfc, 0xfc, 0xe7, 0xcf, + 0xfe, 0xf9, 0xf3, 0xdf, 0xfe, 0xf3, 0xf9, 0xdf, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xfc, 0xe7, 0xff, 0x7f, 0xfe, 0xcf, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf, + 0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3, + 0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0}; + +const unsigned char Grinning_SmilingEyes2[] PROGMEM = { + 0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xff, 0xff, 0xc0, + 0xf0, 0xff, 0xff, 0xc1, 0xf8, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xc7, + 0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, + 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0x3f, 0x00, 0x80, 0xdf, 0xbe, 0xff, 0xbf, 0xcf, 0x7e, 0x00, 0xc0, 0xcf, + 0x7c, 0x00, 0xc0, 0xc7, 0xfc, 0x00, 0xe0, 0xc7, 0xf8, 0x01, 0xf0, 0xc3, 0xf8, 0x03, 0xf8, 0xc3, 0xf0, 0xff, 0xff, 0xc1, + 0xe0, 0xff, 0xff, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0}; const unsigned char wave_icon[] PROGMEM = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xC0, 0x00, - 0x00, 0x0C, 0x9C, 0x01, 0x80, 0x17, 0x20, 0x01, 0x80, 0x26, 0x46, 0x02, 0x80, 0x44, 0x88, 0x02, 0xC0, 0x89, 0x8A, 0x02, - 0x40, 0x93, 0x8B, 0x02, 0x40, 0x26, 0x13, 0x00, 0x80, 0x44, 0x16, 0x00, 0xC0, 0x89, 0x24, 0x00, 0x40, 0x93, 0x60, 0x00, - 0x40, 0x26, 0x40, 0x00, 0x80, 0x0C, 0x80, 0x00, 0x00, 0x09, 0x80, 0x00, 0x00, 0x02, 0x80, 0x00, 0x40, 0x06, 0x80, 0x00, - 0x50, 0x0C, 0x80, 0x00, 0x50, 0x08, 0x40, 0x00, 0x90, 0x10, 0x20, 0x00, 0xB0, 0x21, 0x10, 0x00, 0x20, 0x47, 0x18, 0x00, - 0x40, 0x80, 0x0F, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0xc7, + 0x00, 0x00, 0x1e, 0xcc, 0x00, 0x00, 0x30, 0xc8, 0x00, 0x00, 0x60, 0xd8, 0x00, 0x08, 0xc0, 0xd0, 0x00, 0x1a, 0x81, 0xd1, + 0x00, 0x36, 0x03, 0xd3, 0x80, 0x6d, 0x06, 0xd2, 0x00, 0xdb, 0x0c, 0xc2, 0x80, 0xb6, 0x1d, 0xc0, 0x80, 0x6d, 0x1f, 0xc0, + 0x00, 0xdb, 0x3f, 0xc0, 0x00, 0xf6, 0x7f, 0xc0, 0x00, 0xfc, 0x7f, 0xc0, 0x08, 0xf8, 0x7f, 0xc0, 0x48, 0xf0, 0x7f, 0xc0, + 0x48, 0xe0, 0x7f, 0xc0, 0xc8, 0xc0, 0x3f, 0xc0, 0x98, 0x81, 0x1f, 0xc0, 0x10, 0x03, 0x00, 0xc0, 0x30, 0x0e, 0x00, 0xc0, + 0x20, 0x38, 0x00, 0xc0, 0xe0, 0x00, 0x00, 0xc0, 0x80, 0x07, 0x00, 0xc0, 0x00, 0x1e, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0}; const unsigned char cowboy[] PROGMEM = { - 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x3C, 0xFE, 0x1F, 0x0F, - 0xFE, 0xFE, 0xDF, 0x1F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, - 0x3E, 0xC0, 0x00, 0x1F, 0x1E, 0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x0E, 0x1C, 0x04, 0x00, 0x0E, 0x1C, 0x00, - 0x04, 0x0E, 0x1C, 0x08, 0x04, 0x0E, 0x1C, 0x08, 0x04, 0x04, 0x08, 0x08, 0x04, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x08, - 0x8C, 0x07, 0x70, 0x0C, 0x88, 0xFC, 0x4F, 0x04, 0x88, 0x01, 0x40, 0x04, 0x90, 0xFF, 0x7F, 0x02, 0x30, 0x03, 0x30, 0x03, - 0x60, 0x0E, 0x9C, 0x01, 0xC0, 0xF8, 0xC7, 0x00, 0x80, 0x01, 0x60, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0xF8, 0x07, 0x00, -}; + 0x00, 0x0c, 0x0c, 0xc0, 0x00, 0x02, 0x10, 0xc0, 0x00, 0x01, 0x20, 0xc0, 0xbc, 0x00, 0x40, 0xcf, 0xc2, 0x01, 0xe0, 0xd0, + 0x01, 0x01, 0x20, 0xe0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xc1, 0x3f, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe1, 0xf2, 0xf3, 0xf3, 0xd3, 0xf4, 0xf1, 0xe3, 0xcb, 0xfc, 0xf1, 0xe3, 0xc7, + 0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xfb, 0xf7, 0xc7, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xc7, + 0x70, 0xf8, 0x8f, 0xc3, 0x70, 0x03, 0xb0, 0xc3, 0x70, 0xfe, 0xbf, 0xc3, 0x60, 0x00, 0x80, 0xc1, 0xc0, 0x00, 0xc0, 0xc0, + 0x80, 0x01, 0x60, 0xc0, 0x00, 0x07, 0x38, 0xc0, 0x00, 0xfe, 0x1f, 0xc0, 0x00, 0xf0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0}; const unsigned char deadmau5[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, @@ -181,13 +235,12 @@ const unsigned char fog[] PROGMEM = { }; const unsigned char devil[] PROGMEM = { - 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x10, 0x03, 0xC0, 0x01, 0x38, 0x07, 0x7C, 0x0F, 0x38, 0x1F, 0x03, 0x30, 0x1E, - 0xFE, 0x01, 0xE0, 0x1F, 0x7E, 0x00, 0x80, 0x1F, 0x3C, 0x00, 0x00, 0x0F, 0x1C, 0x00, 0x00, 0x0E, 0x18, 0x00, 0x00, 0x06, - 0x08, 0x00, 0x00, 0x04, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x0E, 0x1C, 0x0C, - 0x0C, 0x18, 0x06, 0x0C, 0x0C, 0x1C, 0x06, 0x0C, 0x0C, 0x1C, 0x0E, 0x0C, 0x0C, 0x1C, 0x0E, 0x0C, 0x0C, 0x0C, 0x06, 0x0C, - 0x08, 0x00, 0x00, 0x06, 0x18, 0x02, 0x10, 0x06, 0x10, 0x0C, 0x0C, 0x03, 0x30, 0xF8, 0x07, 0x03, 0x60, 0xE0, 0x80, 0x01, - 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x70, 0x00, 0x00, 0x06, 0x1C, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, -}; + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x0f, 0xfc, 0x0f, 0xfc, + 0x3f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfc, 0xff, 0xff, 0xcf, + 0xfc, 0xff, 0xff, 0xcf, 0xf8, 0xff, 0xff, 0xc7, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xf1, 0xe3, 0xc3, + 0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3, + 0xf0, 0xff, 0xff, 0xc3, 0xe0, 0xfd, 0xef, 0xc1, 0xe0, 0xf3, 0xf3, 0xc1, 0xc0, 0x07, 0xf8, 0xc0, 0x80, 0x1f, 0x7e, 0xc0, + 0x00, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0}; const unsigned char heart[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xF0, 0x00, 0xF8, 0x0F, 0xFC, 0x07, 0xFC, 0x1F, 0x06, 0x0E, 0xFE, 0x3F, 0x03, 0x18, @@ -199,13 +252,12 @@ const unsigned char heart[] PROGMEM = { }; const unsigned char poo[] PROGMEM = { - 0x00, 0x1C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0xEC, 0x01, 0x00, 0x00, 0x8C, 0x07, 0x00, 0x00, 0x0C, 0x06, 0x00, - 0x00, 0x24, 0x0C, 0x00, 0x00, 0x34, 0x08, 0x00, 0x00, 0x1F, 0x08, 0x00, 0xC0, 0x0F, 0x08, 0x00, 0xC0, 0x00, 0x3C, 0x00, - 0x60, 0x00, 0x7C, 0x00, 0x60, 0x00, 0xC6, 0x00, 0x20, 0x00, 0xCB, 0x00, 0xA0, 0xC7, 0xFF, 0x00, 0xE0, 0x7F, 0xF7, 0x00, - 0xF0, 0x18, 0xE3, 0x03, 0x78, 0x18, 0x41, 0x03, 0x6C, 0x9B, 0x5D, 0x06, 0x64, 0x9B, 0x5D, 0x04, 0x44, 0x1A, 0x41, 0x04, - 0x4C, 0xD8, 0x63, 0x06, 0xF8, 0xFC, 0x36, 0x06, 0xFE, 0x0F, 0x9C, 0x1F, 0x07, 0x03, 0xC0, 0x30, 0x03, 0x00, 0x78, 0x20, - 0x01, 0x00, 0x1F, 0x20, 0x03, 0xE0, 0x03, 0x20, 0x07, 0x7E, 0x04, 0x30, 0xFE, 0x0F, 0xFC, 0x1F, 0xF0, 0x00, 0xF0, 0x0F, -}; + 0x00, 0x1c, 0x00, 0xc0, 0x00, 0x7c, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0x7c, 0x03, 0xc0, 0x00, 0xbe, 0x03, 0xc0, + 0x00, 0xdf, 0x0f, 0xc0, 0x80, 0xcf, 0x0f, 0xc0, 0xc0, 0xf1, 0x0f, 0xc0, 0x60, 0xfc, 0x0f, 0xc0, 0x30, 0xff, 0x07, 0xc0, + 0x90, 0xff, 0x3b, 0xc0, 0xc0, 0xff, 0x7d, 0xc0, 0xf8, 0xff, 0xfc, 0xc0, 0xf8, 0x3f, 0xf0, 0xc0, 0x78, 0x88, 0xc0, 0xc0, + 0x20, 0xe3, 0x18, 0xc0, 0x98, 0xe7, 0xbc, 0xc1, 0x9c, 0x64, 0xa4, 0xc3, 0x9e, 0x64, 0xa4, 0xc7, 0xbe, 0xe4, 0xa4, 0xc7, + 0xbc, 0x27, 0xbc, 0xc7, 0x38, 0x03, 0xd9, 0xc3, 0x00, 0xf0, 0x63, 0xc0, 0xf8, 0xfc, 0x3f, 0xcf, 0xfc, 0xff, 0x87, 0xdf, + 0xfe, 0xff, 0xe0, 0xdf, 0xfc, 0x1f, 0xfe, 0xdf, 0xf8, 0x07, 0xf8, 0xcf, 0xf0, 0x03, 0xe0, 0xc7, 0x00, 0x00, 0x00, 0xc0}; const unsigned char bell_icon[] PROGMEM = { 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b11110000, diff --git a/src/graphics/emotes.h b/src/graphics/emotes.h index 5640ac04a..30b164cbc 100644 --- a/src/graphics/emotes.h +++ b/src/graphics/emotes.h @@ -22,9 +22,25 @@ extern const int numEmotes; extern const unsigned char thumbup[] PROGMEM; extern const unsigned char thumbdown[] PROGMEM; -#define smiley_height 30 -#define smiley_width 30 -extern const unsigned char smiley[] PROGMEM; +#define Smiling_Eyes_height 30 +#define Smiling_Eyes_width 30 +extern const unsigned char Smiling_Eyes[] PROGMEM; + +#define Grinning_height 30 +#define Grinning_width 30 +extern const unsigned char Grinning[] PROGMEM; + +#define Slightly_Smiling_height 30 +#define Slightly_Smiling_width 30 +extern const unsigned char Slightly_Smiling[] PROGMEM; + +#define Winking_Face_height 30 +#define Winking_Face_width 30 +extern const unsigned char Winking_Face[] PROGMEM; + +#define Grinning_Smiling_Eyes_height 30 +#define Grinning_Smiling_Eyes_width 30 +extern const unsigned char Grinning_Smiling_Eyes[] PROGMEM; #define question_height 25 #define question_width 25 @@ -38,6 +54,18 @@ extern const unsigned char bang[] PROGMEM; #define haha_width 30 extern const unsigned char haha[] PROGMEM; +#define ROFL_height 30 +#define ROFL_width 30 +extern const unsigned char ROFL[] PROGMEM; + +#define Smiling_Closed_Eyes_height 30 +#define Smiling_Closed_Eyes_width 30 +extern const unsigned char Smiling_Closed_Eyes[] PROGMEM; + +#define Grinning_SmilingEyes2_height 30 +#define Grinning_SmilingEyes2_width 30 +extern const unsigned char Grinning_SmilingEyes2[] PROGMEM; + #define wave_icon_height 30 #define wave_icon_width 30 extern const unsigned char wave_icon[] PROGMEM; diff --git a/src/graphics/images.h b/src/graphics/images.h index c5865878a..beef3a1b2 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -12,7 +12,10 @@ const uint8_t imgSatellite[] PROGMEM = { 0b00000000, 0b00000000, 0b00000000, 0b00011000, 0b11011011, 0b11111111, 0b11011011, 0b00011000, }; -const uint8_t imgUSB[] PROGMEM = {0x60, 0x60, 0x30, 0x18, 0x18, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x24, 0x24, 0x24, 0x3C}; +const uint8_t imgUSB[] PROGMEM = {0x00, 0xfc, 0xf0, 0xfc, 0x88, 0xff, 0x86, 0xfe, 0x85, 0xfe, 0x89, 0xff, 0xf1, 0xfc, 0x00, 0xfc}; +const uint8_t imgUSB_HighResolution[] PROGMEM = {0x00, 0x3e, 0xf8, 0x80, 0x43, 0xf8, 0xc0, 0xc2, 0xff, 0x60, 0x42, 0xfc, + 0x3c, 0xc2, 0xff, 0x22, 0x42, 0xf8, 0x3d, 0x42, 0xf8, 0x22, 0xc2, 0xff, + 0x61, 0x42, 0xfc, 0xc0, 0xc2, 0xff, 0x80, 0x43, 0xf8, 0x00, 0x3e, 0xf8}; const uint8_t imgPower[] PROGMEM = {0x40, 0x40, 0x40, 0x58, 0x48, 0x08, 0x08, 0x08, 0x1C, 0x22, 0x22, 0x41, 0x7F, 0x22, 0x22, 0x22}; const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3C}; diff --git a/src/graphics/niche/Drivers/EInk/E0213A367.cpp b/src/graphics/niche/Drivers/EInk/E0213A367.cpp new file mode 100644 index 000000000..f19cb4ff7 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/E0213A367.cpp @@ -0,0 +1,84 @@ +#include "./E0213A367.h" + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +using namespace NicheGraphics::Drivers; + +// Map the display controller IC's output to the connected panel +void E0213A367::configScanning() +{ + // "Driver output control" + // Scan gates from 0 to 249 (vertical resolution 250px) + sendCommand(0x01); + sendData(0xF9); + sendData(0x00); +} + +// Specify which information is used to control the sequence of voltages applied to move the pixels +void E0213A367::configWaveform() +{ + // This command (0x37) is poorly documented + // As of July 2025, the datasheet for this display's controller IC is unavailable + // The values are supplied by Heltec, who presumably have privileged access to information from the display manufacturer + // Datasheet for the similar SSD1680 IC hints at the function of this command: + + // "Spare VCOM OTP selection": + // Unclear why 0x40 is set. Sane values for related SSD1680 seem to be 0x80 or 0x00. + // Maybe value is redundant? No noticeable impact when set to 0x00. + // We'll leave it set to 0x40, following Heltec's lead, just in case. + + // "Display Mode" + // Seems to specify whether a waveform stored in OTP should use display mode 1 or 2 (full refresh or differential refresh) + + // Unusual that waveforms are programmed to OTP, but this meta information is not ..? + + sendCommand(0x37); // "Write Register for Display Option" ? + sendData(0x40); // "Spare VCOM OTP selection" ? + sendData(0x80); // "Display Mode for WS[7:0]" ? + sendData(0x03); // "Display Mode for WS[15:8]" ? + sendData(0x0E); // "Display Mode [23:16]" ? + + switch (updateType) { + case FAST: + sendCommand(0x3C); // Border waveform: + sendData(0x81); // As specified by Heltec. Actually VCOM (0x80)?. Bit 0 seems redundant here. + break; + case FULL: + default: + sendCommand(0x3C); // Border waveform: + sendData(0x01); // Follow LUT 1 (blink same as white pixels) + break; + } +} + +// Tell controller IC which operations to run +void E0213A367::configUpdateSequence() +{ + switch (updateType) { + case FAST: + sendCommand(0x22); // Set "update sequence" + sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh" + break; + case FULL: + default: + sendCommand(0x22); // Set "update sequence" + sendData(0xF7); // Will load LUT from OTP memory, Display mode 1 "full refresh" + break; + } +} + +// Once the refresh operation has been started, +// begin periodically polling the display to check for completion, using the normal Meshtastic threading code +// Only used when refresh is "async" +void E0213A367::detachFromUpdate() +{ + switch (updateType) { + case FAST: + return beginPolling(50, 500); // At least 500ms for fast refresh + case FULL: + default: + return beginPolling(100, 1500); // At least 1.5 seconds for full refresh + } +} + +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/E0213A367.h b/src/graphics/niche/Drivers/EInk/E0213A367.h new file mode 100644 index 000000000..a36fcb407 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/E0213A367.h @@ -0,0 +1,41 @@ +/* + +E-Ink display driver + - SSD1682 + - Manufacturer: SEEKINK + - Size: 2.13 inch + - Resolution: 122px x 255px + - Flex connector marking: HINK-E0213A162-A1 (hidden, printed on reverse) + +*/ + +#pragma once + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +#include "configuration.h" + +#include "./SSD1682.h" + +namespace NicheGraphics::Drivers +{ +class E0213A367 : public SSD1682 +{ + // Display properties + private: + static constexpr uint32_t width = 122; + static constexpr uint32_t height = 250; + static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST); + + public: + E0213A367() : SSD1682(width, height, supported, 0) {} + + protected: + void configScanning() override; + void configWaveform() override; + void configUpdateSequence() override; + void detachFromUpdate() override; +}; + +} // namespace NicheGraphics::Drivers +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/SSD1682.cpp b/src/graphics/niche/Drivers/EInk/SSD1682.cpp new file mode 100644 index 000000000..c3d7f7786 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/SSD1682.cpp @@ -0,0 +1,41 @@ +#include "./SSD1682.h" + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +using namespace NicheGraphics::Drivers; + +SSD1682::SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX) + : SSD16XX(width, height, supported, bufferOffsetX) +{ +} + +// SSD1682 only accepts single-byte x and y values +// This causes an incompatibility with the default SSD16XX::configFullscreen +void SSD1682::configFullscreen() +{ + // Define the boundaries of the "fullscreen" region, for the controller IC + static const uint8_t sx = bufferOffsetX; // Notice the offset + static const uint8_t sy = 0; + static const uint8_t ex = bufferRowSize + bufferOffsetX - 1; // End is "max index", not "count". Minus 1 handles this + static const uint8_t ey = height; + + // Data entry mode - Left to Right, Top to Bottom + sendCommand(0x11); + sendData(0x03); + + // Select controller IC memory region to display a fullscreen image + sendCommand(0x44); // Memory X start - end + sendData(sx); + sendData(ex); + sendCommand(0x45); // Memory Y start - end + sendData(sy); + sendData(ey); + + // Place the cursor at the start of this memory region, ready to send image data x=0 y=0 + sendCommand(0x4E); // Memory cursor X + sendData(sx); + sendCommand(0x4F); // Memory cursor y + sendData(sy); +} + +#endif \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/SSD1682.h b/src/graphics/niche/Drivers/EInk/SSD1682.h new file mode 100644 index 000000000..ba3008537 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/SSD1682.h @@ -0,0 +1,31 @@ +/* + +E-Ink base class for displays based on SSD1682 + +SSD1682 has a few quirks. We're implementing them here in a new base class, +to avoid re-implementing them every time we need to add a new SSD1682-based display. + +*/ + +#pragma once + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +#include "configuration.h" + +#include "./SSD16XX.h" + +namespace NicheGraphics::Drivers +{ + +class SSD1682 : public SSD16XX +{ + public: + SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX = 0); + virtual void configFullscreen(); // Select memory region on controller IC + virtual void deepSleep() {} // Not usable (image memory not retained) +}; + +} // namespace NicheGraphics::Drivers + +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Fonts/FreeSans12pt_Win1250.h b/src/graphics/niche/Fonts/FreeSans12pt_Win1250.h new file mode 100644 index 000000000..66edcc6ad --- /dev/null +++ b/src/graphics/niche/Fonts/FreeSans12pt_Win1250.h @@ -0,0 +1,527 @@ +// trunk-ignore-all(clang-format) +#pragma once +/* PROPERTIES + +FONT_NAME FreeSans12pt_Win1250 +*/ +const uint8_t FreeSans12pt_Win1250Bitmaps[] PROGMEM = { +/* 0x01 */ 0x00, 0x30, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x24, 0x00, 0x04, 0x80, 0x01, 0x90, 0x00, 0x62, 0x00, 0x30, 0xFE, 0x04, 0x10, 0x5F, 0x02, 0x0B, 0x00, 0x7F, 0xE0, 0x0C, 0x1C, 0x02, 0x83, 0x81, 0x9F, 0xF0, 0x02, 0x1E, 0x00, 0x41, 0xC0, 0x0E, 0x7F, 0x81, 0x78, 0x18, 0x62, 0x00, 0xFF, 0xC0, +/* 0x02 */ 0x00, 0xFF, 0x80, 0x61, 0x13, 0xF0, 0x62, 0x60, 0x07, 0xFC, 0x00, 0x83, 0x80, 0x10, 0xF0, 0x33, 0xF6, 0x01, 0x41, 0xC0, 0x18, 0x38, 0x03, 0xFF, 0xE0, 0x47, 0x02, 0x08, 0x20, 0x61, 0xC4, 0x06, 0x17, 0x00, 0x22, 0x00, 0x02, 0x40, 0x00, 0x48, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x3C, 0x00, +/* 0x03 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x04, 0x08, 0x48, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x08, 0x10, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA1, 0x81, 0x8D, 0x87, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x04 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x10, 0x02, 0x48, 0xE0, 0x61, 0xC1, 0xCC, 0x0E, 0x78, 0x1C, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xFC, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x05 */ 0x00, 0x18, 0x00, 0x00, 0x40, 0x01, 0x90, 0x01, 0xF4, 0x08, 0x12, 0x23, 0xC1, 0x91, 0x2C, 0x1C, 0x8A, 0xC3, 0x64, 0x64, 0x13, 0x22, 0x41, 0x98, 0x26, 0x2C, 0xC4, 0x22, 0x60, 0x42, 0x13, 0x04, 0x30, 0x80, 0x61, 0xA4, 0x02, 0x18, 0x20, 0x03, 0x41, 0x00, 0x20, 0x08, 0x02, 0x00, 0x60, 0x40, 0x03, 0xF8, +/* 0x06 */ 0x00, 0x10, 0x00, 0x03, 0x00, 0x1C, 0x48, 0x00, 0xB4, 0x80, 0x09, 0xF9, 0xC0, 0xE0, 0xE4, 0x0C, 0x02, 0x8F, 0x80, 0x38, 0x88, 0x01, 0x0D, 0x00, 0x18, 0x30, 0x01, 0x60, 0x80, 0x13, 0x18, 0x03, 0xF2, 0xC0, 0x20, 0x26, 0x06, 0x07, 0xFF, 0xA0, 0x02, 0x39, 0x00, 0x14, 0x70, 0x01, 0xC3, 0x00, 0x18, 0x00, +/* 0x07 */ +/* 0x08 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x10, 0x37, 0xE2, 0x61, 0x00, 0x0C, 0xC6, 0x10, 0x98, 0x0C, 0x63, 0x00, 0x00, 0xC6, 0x00, +/* 0x09 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x00, 0x37, 0xE0, +/* 0x0A */ +/* 0x0B */ 0x1F, 0x07, 0xC1, 0x86, 0x41, 0x10, 0x0C, 0x04, 0x80, 0x40, 0x18, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x00, 0x00, 0x30, 0x00, 0x01, 0x40, 0x00, 0x0A, 0x00, 0x00, 0x88, 0x00, 0x04, 0x40, 0x00, 0x41, 0x00, 0x02, 0x04, 0x00, 0x20, 0x20, 0x02, 0x00, 0x80, 0x20, 0x02, 0x02, 0x00, 0x08, 0x20, 0x00, 0x22, 0x00, 0x00, 0xE0, 0x00, +/* 0x0C */ 0x01, 0x00, 0x00, 0x38, 0x00, 0x04, 0xC0, 0x01, 0x08, 0x00, 0x18, 0x80, 0x1C, 0x10, 0x02, 0x07, 0x80, 0x81, 0x10, 0x1F, 0xC2, 0x02, 0x00, 0x60, 0x80, 0x1A, 0x20, 0x1C, 0x42, 0x1C, 0x08, 0xFE, 0x03, 0xA0, 0x01, 0x8C, 0x01, 0xC1, 0x43, 0xD0, 0x27, 0x81, 0xF8, +/* 0x0D */ +/* 0x0E */ 0x00, 0xE0, 0x00, 0x11, 0x00, 0x01, 0x10, 0x00, 0x0B, 0x00, 0x03, 0xF8, 0x00, 0x60, 0x60, 0x09, 0x02, 0x00, 0xA0, 0x10, 0x16, 0x01, 0x01, 0x40, 0x10, 0x10, 0x01, 0x01, 0x00, 0x08, 0x10, 0x00, 0x82, 0x1F, 0x08, 0x3F, 0x90, 0x44, 0x00, 0x06, 0xBF, 0xFF, 0xAF, 0xF0, 0xFF, 0xFF, 0x0F, 0xE3, 0xFB, 0xFC, +/* 0x0F */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x40, 0x12, 0x34, 0x00, 0x69, 0x40, 0x01, 0x49, 0xE0, 0xF1, 0xCD, 0x06, 0x8E, 0x28, 0x14, 0x71, 0x40, 0xA3, 0x8B, 0xFD, 0x14, 0x50, 0x68, 0xA2, 0x81, 0x4D, 0x97, 0xFA, 0x44, 0xBF, 0xD6, 0x31, 0x02, 0xE0, 0xC8, 0x16, 0x08, 0x61, 0x08, 0x21, 0xF0, 0x80, 0xF8, 0x78, 0x00, +/* 0x10 */ 0x00, 0xF0, 0x00, 0x3A, 0x00, 0x07, 0xC0, 0x00, 0xA8, 0x00, 0x1F, 0x00, 0x02, 0xB0, 0x00, 0x52, 0x00, 0x0A, 0x40, 0x02, 0x48, 0x00, 0x49, 0x00, 0x09, 0x30, 0x01, 0x22, 0x01, 0xC4, 0x70, 0xF0, 0x85, 0xE1, 0x10, 0x88, 0x37, 0x20, 0x03, 0x9C, 0x00, 0x37, 0x00, 0x06, 0x40, 0x01, 0x86, 0x00, +/* 0x11 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x60, 0x02, 0x36, 0x00, 0x09, 0x04, 0x0C, 0x48, 0x60, 0xC1, 0xC3, 0x0F, 0x0E, 0x00, 0x08, 0x70, 0x00, 0x23, 0x80, 0x63, 0x84, 0x01, 0x9F, 0x20, 0x0C, 0xFD, 0x80, 0x27, 0xE4, 0x03, 0x3F, 0x30, 0x33, 0xE0, 0xC0, 0x00, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x12 */ 0x00, 0xC2, 0x00, 0x1C, 0x24, 0x02, 0x18, 0x60, 0x64, 0x02, 0x02, 0x40, 0x20, 0x00, 0xF2, 0x03, 0x89, 0xE0, 0x7C, 0x80, 0x0E, 0x25, 0x80, 0xE1, 0x00, 0x1A, 0x08, 0x71, 0xB0, 0xC4, 0x39, 0x84, 0xC2, 0xCC, 0x40, 0x76, 0x7C, 0x05, 0xBB, 0x80, 0x4C, 0xE0, 0x0A, 0x78, 0x00, 0x9C, 0x00, 0x0F, 0x00, 0x00, +/* 0x13 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x60, 0xC1, 0xC6, 0xC9, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xF8, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x14 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x20, 0x22, 0x33, 0x01, 0x89, 0x20, 0x02, 0x48, 0x60, 0xE1, 0xC8, 0x80, 0x8E, 0x46, 0x46, 0x72, 0x32, 0x33, 0x9F, 0x9F, 0x94, 0x78, 0x78, 0xA0, 0x00, 0x0D, 0x80, 0x00, 0x44, 0x0E, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x15 */ 0x03, 0xFC, 0x20, 0x38, 0x1C, 0x81, 0x80, 0x1D, 0x08, 0x00, 0x32, 0x60, 0x00, 0x89, 0x00, 0x02, 0x18, 0x00, 0x08, 0x61, 0xC3, 0x22, 0x8D, 0x93, 0x72, 0x00, 0x00, 0x48, 0x00, 0x01, 0x20, 0x00, 0x04, 0x9F, 0xFF, 0x92, 0x60, 0x0E, 0x44, 0xFF, 0xF2, 0x11, 0xC3, 0x88, 0x21, 0xF8, 0x40, 0x40, 0x02, 0x00, 0xC0, 0x30, 0x00, 0xFF, 0x00, +/* 0x16 */ 0x03, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x01, 0xE0, 0x03, 0xF0, 0x03, 0xF0, 0x27, 0xF0, 0x6F, 0x70, 0x6E, 0x60, 0xFC, 0x60, 0xFC, 0x7E, 0xFC, 0x7E, 0xFC, 0x3F, 0xF4, 0x1F, 0xF4, 0x1F, 0xF0, 0x0E, 0x70, 0x0E, 0x30, 0x1C, 0x38, 0x38, 0x0F, 0xF0, +/* 0x17 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x00, 0x21, 0xC0, 0x02, 0x8E, 0x20, 0xF4, 0x70, 0x84, 0x11, 0x82, 0x40, 0x84, 0x01, 0x03, 0x20, 0x0F, 0x85, 0x80, 0x03, 0x04, 0x00, 0x04, 0x30, 0x78, 0x10, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x18 */ 0x00, 0xFC, 0x00, 0x02, 0x06, 0x00, 0x08, 0x24, 0x00, 0x21, 0xA4, 0x00, 0x4C, 0x48, 0x00, 0xA0, 0x50, 0x01, 0x92, 0x60, 0x03, 0x24, 0xC0, 0x06, 0x01, 0x81, 0x28, 0x03, 0x49, 0x6C, 0xC4, 0xAD, 0xD8, 0x16, 0xA4, 0xCC, 0xC4, 0x44, 0x86, 0x13, 0x05, 0x00, 0x28, 0x0A, 0x00, 0x50, 0x14, 0x00, 0x90, 0x48, 0x01, 0x20, 0x90, 0x02, 0x41, 0x20, 0x00, 0x00, +/* 0x19 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x49, 0xC3, 0x81, 0xC0, 0x00, 0x0E, 0x78, 0xF0, 0x77, 0xEF, 0xC3, 0xA7, 0x4E, 0x15, 0x0A, 0x10, 0xA7, 0x8F, 0x0D, 0x80, 0x00, 0x44, 0x00, 0x06, 0x33, 0xF0, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x1A */ 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0C, 0x3E, 0x18, 0x82, 0x32, 0x02, 0x64, 0x04, 0xC8, 0x09, 0x80, 0x23, 0x00, 0x86, 0x02, 0x0C, 0x08, 0x18, 0x10, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x81, 0x80, 0x03, 0x00, 0x07, 0xFF, 0xF8, +/* 0x1B */ 0x00, 0xFE, 0x00, 0x03, 0x81, 0x80, 0x04, 0x00, 0x60, 0x08, 0x00, 0x30, 0x10, 0x00, 0x10, 0x30, 0x07, 0x88, 0x23, 0xC8, 0x08, 0x22, 0x00, 0x04, 0x60, 0x00, 0x44, 0x60, 0x00, 0x84, 0x63, 0x03, 0x04, 0x61, 0xFC, 0x04, 0x6B, 0x00, 0x9E, 0xA5, 0x01, 0x6A, 0xD5, 0x01, 0x43, 0xA8, 0x81, 0x05, 0xD0, 0x82, 0x0A, 0xA0, 0x82, 0x05, 0xC0, 0x82, 0x02, 0x61, 0xFF, 0x0C, 0x1E, 0x00, 0xF0, +/* 0x1C */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x30, 0x02, 0x32, 0x00, 0x09, 0x00, 0x00, 0x48, 0x20, 0x61, 0xC3, 0x84, 0x0E, 0x1C, 0x78, 0x70, 0x40, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA0, 0x03, 0x0D, 0x83, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x1D */ 0x01, 0xFE, 0x00, 0x3A, 0x1C, 0x03, 0x00, 0x30, 0x23, 0x1E, 0xC3, 0x38, 0x03, 0x10, 0xC3, 0x09, 0x00, 0x18, 0x68, 0x00, 0xC1, 0x40, 0x00, 0x0A, 0x07, 0x80, 0x50, 0x46, 0x02, 0x80, 0x00, 0x1A, 0x1E, 0x00, 0xCB, 0x10, 0x0D, 0x03, 0x00, 0x48, 0x60, 0x06, 0x40, 0x00, 0x22, 0x0C, 0x02, 0x10, 0x60, 0x60, 0x43, 0xFC, 0x01, 0xE0, 0x00, 0x00, +/* 0x1E */ 0x01, 0xF0, 0x00, 0xEA, 0xC0, 0x31, 0x5F, 0x04, 0x5F, 0x88, 0x80, 0xA0, 0x48, 0x0E, 0x02, 0x8F, 0x40, 0x3C, 0x10, 0x21, 0x66, 0x87, 0x15, 0x98, 0x71, 0x41, 0x02, 0x14, 0x00, 0x01, 0x40, 0x00, 0x14, 0x00, 0x01, 0x21, 0xFE, 0x12, 0x00, 0x02, 0x10, 0x00, 0x60, 0x80, 0x0C, 0x06, 0x01, 0x80, 0x3F, 0xE0, +/* 0x1F */ 0x0E, 0x00, 0x13, 0x00, 0x23, 0x00, 0xF3, 0x01, 0x31, 0x01, 0x11, 0x03, 0xD3, 0x06, 0xF2, 0x30, 0x34, 0xC7, 0x25, 0x33, 0x2B, 0xC2, 0x57, 0x04, 0x3A, 0x08, 0x72, 0x30, 0xA3, 0xC3, 0x40, 0x04, 0x40, 0x18, 0x40, 0x60, 0x7F, 0x80, +/* ' ' 0x20 */ +/* '!' 0x21 */ 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, +/* '"' 0x22 */ 0xCF, 0x3C, 0xF3, 0x8A, 0x20, +/* '#' 0x23 */ 0x06, 0x30, 0x31, 0x03, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30, +/* '$' 0x24 */ 0x04, 0x03, 0xE1, 0xFF, 0x72, 0x7C, 0x47, 0x88, 0xF1, 0x07, 0xA0, 0x7E, 0x03, 0xF0, 0x17, 0x02, 0x7C, 0x47, 0x88, 0xF1, 0x1B, 0x26, 0x7F, 0xC3, 0xE0, 0x10, 0x02, 0x00, +/* '%' 0x25 */ 0x00, 0x06, 0x03, 0xC0, 0x40, 0x7E, 0x0C, 0x0E, 0x70, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xE7, 0x30, 0x07, 0xE6, 0x00, 0x3C, 0x40, 0x00, 0x0C, 0x7C, 0x00, 0x8F, 0xE0, 0x19, 0xC7, 0x01, 0x18, 0x30, 0x31, 0x83, 0x02, 0x1C, 0x70, 0x40, 0xFE, 0x04, 0x07, 0xC0, +/* '&' 0x26 */ 0x0F, 0x00, 0x7E, 0x03, 0x9C, 0x0C, 0x30, 0x30, 0xC0, 0xE7, 0x01, 0xF8, 0x03, 0x80, 0x3E, 0x01, 0xCC, 0x6E, 0x39, 0xB0, 0x7C, 0xC0, 0xF3, 0x03, 0xCE, 0x1F, 0x9F, 0xE6, 0x3E, 0x1C, +/* ''' 0x27 */ 0xFF, 0xA0, +/* '(' 0x28 */ 0x08, 0x8C, 0x46, 0x31, 0x98, 0xC6, 0x31, 0x8C, 0x63, 0x08, 0x63, 0x08, 0x61, 0x0C, 0x20, +/* ')' 0x29 */ 0x82, 0x18, 0xC3, 0x18, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x62, 0x31, 0x88, 0xC4, 0x62, 0x00, +/* '*' 0x2A */ 0x10, 0x23, 0x5B, 0xE3, 0x8D, 0x91, 0x00, +/* '+' 0x2B */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, +/* ',' 0x2C */ 0xF5, 0x60, +/* '-' 0x2D */ 0xFF, 0xF0, +/* '.' 0x2E */ 0xF0, +/* '/' 0x2F */ 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x00, +/* '0' 0x30 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x6C, 0x0F, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3E, 0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00, +/* '1' 0x31 */ 0x08, 0xCF, 0xFF, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, +/* '2' 0x32 */ 0x1F, 0x0F, 0xF9, 0x87, 0x60, 0x7C, 0x06, 0x00, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x06, 0x00, 0xC0, 0x30, 0x07, 0xFF, 0xFF, 0xE0, +/* '3' 0x33 */ 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0C, 0x01, 0x80, 0x60, 0x78, 0x0F, 0x80, 0x18, 0x01, 0x80, 0x3C, 0x07, 0x80, 0xD8, 0x73, 0xFC, 0x3F, 0x00, +/* '4' 0x34 */ 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0, 0xD8, 0x1B, 0x06, 0x61, 0x8C, 0x21, 0x8C, 0x33, 0x06, 0x7F, 0xFF, 0xFE, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, +/* '5' 0x35 */ 0x3F, 0xCF, 0xF9, 0x80, 0x30, 0x06, 0x00, 0xDE, 0x1F, 0xE7, 0x0E, 0x00, 0xE0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x81, 0xB8, 0x73, 0xFC, 0x1F, 0x00, +/* '6' 0x36 */ 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x74, 0x01, 0x80, 0x33, 0xC7, 0xFE, 0xF1, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x06, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00, +/* '7' 0x37 */ 0xFF, 0xFF, 0xFC, 0x01, 0x00, 0x60, 0x18, 0x02, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x04, 0x01, 0x80, 0x30, 0x06, 0x01, 0x80, 0x30, 0x00, +/* '8' 0x38 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x66, 0x0C, 0xC1, 0x8C, 0x61, 0xF8, 0x3F, 0x8E, 0x3B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xD8, 0x31, 0xFC, 0x1F, 0x00, +/* '9' 0x39 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C, 0x07, 0x80, 0xF0, 0x1E, 0x07, 0x61, 0xEF, 0xFC, 0x79, 0x80, 0x30, 0x05, 0xC1, 0x98, 0x73, 0xFC, 0x1E, 0x00, +/* ':' 0x3A */ 0xF0, 0x00, 0x03, 0xC0, +/* ';' 0x3B */ 0xF0, 0x00, 0x0F, 0x56, +/* '<' 0x3C */ 0x00, 0x70, 0x1E, 0x0F, 0x83, 0xC0, 0xF0, 0x0E, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x10, +/* '=' 0x3D */ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, +/* '>' 0x3E */ 0xE0, 0x07, 0x80, 0x1F, 0x00, 0x7C, 0x00, 0xF0, 0x07, 0x01, 0xE0, 0xF0, 0x3C, 0x0F, 0x00, 0x80, 0x00, +/* '?' 0x3F */ 0x3F, 0x1F, 0xEE, 0x1F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x03, 0x81, 0xC0, 0xE0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00, +/* '@' 0x40 */ 0x00, 0xFE, 0x00, 0x0F, 0xFE, 0x00, 0xF0, 0x3E, 0x07, 0x00, 0x3C, 0x38, 0x00, 0x38, 0xC1, 0xE0, 0x66, 0x0F, 0xD9, 0xD8, 0x61, 0xC3, 0xC3, 0x07, 0x0F, 0x1C, 0x1C, 0x3C, 0x60, 0x60, 0xF1, 0x81, 0x83, 0xC6, 0x06, 0x1B, 0x18, 0x38, 0xEE, 0x71, 0xE7, 0x18, 0xFD, 0xF8, 0x71, 0xE7, 0xC0, 0xE0, 0x00, 0x01, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x01, 0xFC, 0x00, +/* 'A' 0x41 */ 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 'B' 0x42 */ 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00, +/* 'C' 0x43 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0, +/* 'D' 0x44 */ 0xFF, 0xC3, 0xFF, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1B, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00, 0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3, 0xFE, 0x00, +/* 'E' 0x45 */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, +/* 'F' 0x46 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xDF, 0xFB, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00, +/* 'G' 0x47 */ 0x07, 0xF0, 0x1F, 0xFC, 0x3C, 0x1E, 0x70, 0x07, 0x60, 0x03, 0xE0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xC0, 0x03, 0xC0, 0x03, 0x60, 0x03, 0x60, 0x07, 0x30, 0x0F, 0x3C, 0x1F, 0x1F, 0xFB, 0x07, 0xE1, +/* 'H' 0x48 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0, +/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 'J' 0x4A */ 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00, +/* 'K' 0x4B */ 0xC0, 0x3E, 0x03, 0xB0, 0x39, 0x83, 0x8C, 0x38, 0x63, 0x83, 0x38, 0x19, 0xC0, 0xDE, 0x07, 0xB8, 0x38, 0xE1, 0x83, 0x0C, 0x1C, 0x60, 0x73, 0x01, 0x98, 0x0E, 0xC0, 0x3E, 0x00, 0xC0, +/* 'L' 0x4C */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0, +/* 'M' 0x4D */ 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83, +/* 'N' 0x4E */ 0xE0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE, 0x0F, 0x30, 0x79, 0xC3, 0xC6, 0x1E, 0x18, 0xF0, 0xE7, 0x83, 0x3C, 0x1D, 0xE0, 0x6F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, 0xC0, +/* 'O' 0x4F */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00, +/* 'P' 0x50 */ 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x06, 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, +/* 'Q' 0x51 */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x01, 0x98, 0xC0, 0xFC, 0x78, 0x3C, 0x1F, 0xFF, 0x03, 0xF9, 0x80, 0x00, 0x40, +/* 'R' 0x52 */ 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x70, +/* 'S' 0x53 */ 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80, +/* 'T' 0x54 */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, +/* 'U' 0x55 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x80, 0xEE, 0x0E, 0x3F, 0xE0, 0xFC, 0x00, +/* 'V' 0x56 */ 0xC0, 0x0F, 0x00, 0x7E, 0x01, 0x98, 0x06, 0x60, 0x39, 0xC0, 0xC3, 0x03, 0x0C, 0x1C, 0x38, 0x60, 0x61, 0x81, 0x8E, 0x07, 0x30, 0x0C, 0xC0, 0x37, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1C, 0x00, +/* 'W' 0x57 */ 0xE0, 0x30, 0x1D, 0x80, 0xE0, 0x76, 0x07, 0x81, 0xDC, 0x1E, 0x06, 0x70, 0x7C, 0x18, 0xC1, 0xB0, 0xE3, 0x0C, 0xC3, 0x8C, 0x33, 0x0C, 0x38, 0xC6, 0x30, 0x67, 0x18, 0xC1, 0x98, 0x67, 0x06, 0x61, 0xD8, 0x1D, 0x83, 0x60, 0x3C, 0x0D, 0x80, 0xF0, 0x3E, 0x03, 0xC0, 0x70, 0x0F, 0x01, 0xC0, 0x18, 0x07, 0x00, +/* 'X' 0x58 */ 0xE0, 0x1D, 0x80, 0xE7, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x00, 0xFC, 0x01, 0xE0, 0x07, 0x00, 0x1E, 0x00, 0xF8, 0x03, 0x30, 0x1C, 0xE0, 0xE1, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1B, 0x00, 0x70, +/* 'Y' 0x59 */ 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, +/* 'Z' 0x5A */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x03, 0x80, 0x18, 0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, +/* '[' 0x5B */ 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xF0, +/* '\' 0x5C */ 0x81, 0x81, 0x02, 0x06, 0x04, 0x08, 0x18, 0x10, 0x20, 0x60, 0x40, 0x81, 0x81, 0x02, 0x06, 0x04, +/* ']' 0x5D */ 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xF0, +/* '^' 0x5E */ 0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19, 0x8C, 0x83, 0xC1, 0x80, +/* '_' 0x5F */ 0xFF, 0xFE, +/* '`' 0x60 */ 0xE3, 0x8C, 0x30, +/* 'a' 0x61 */ 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70, +/* 'b' 0x62 */ 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0xF8, 0xDF, 0xCF, 0x0E, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xE0, 0x6F, 0x0E, 0xDF, 0xCC, 0xF8, +/* 'c' 0x63 */ 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x00, +/* 'd' 0x64 */ 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xCF, 0xFB, 0x8F, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8F, 0x3F, 0x63, 0xCC, +/* 'e' 0x65 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xC1, 0xF0, +/* 'f' 0x66 */ 0x3B, 0xD8, 0xC6, 0x7F, 0xEC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x00, +/* 'g' 0x67 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xB1, 0xE6, 0x00, 0xC0, 0x3E, 0x0E, 0x7F, 0xC7, 0xE0, +/* 'h' 0x68 */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30, +/* 'i' 0x69 */ 0xF0, 0x3F, 0xFF, 0xFF, 0xF0, +/* 'j' 0x6A */ 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0, +/* 'k' 0x6B */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0xB8, 0xC6, 0x31, 0xCC, 0x3B, 0x06, 0xC1, 0xF0, 0x30, +/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 'm' 0x6D */ 0xCF, 0x1F, 0x6F, 0xDF, 0xFC, 0x78, 0xFC, 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0F, 0x03, 0x07, 0x81, 0x83, 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x3C, 0x0C, 0x18, +/* 'n' 0x6E */ 0xCF, 0x37, 0xEF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0, +/* 'o' 0x6F */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0, +/* 'p' 0x70 */ 0xCF, 0x8D, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00, +/* 'q' 0x71 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xF1, 0xE6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, +/* 'r' 0x72 */ 0xCF, 0x7F, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, +/* 's' 0x73 */ 0x3E, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3F, 0x01, 0xF0, 0x3E, 0x1D, 0xFE, 0x3F, 0x00, +/* 't' 0x74 */ 0x63, 0x19, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0xE7, +/* 'u' 0x75 */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0, +/* 'v' 0x76 */ 0xE0, 0x6C, 0x0D, 0x81, 0xB8, 0x63, 0x0C, 0x61, 0x8E, 0x60, 0xCC, 0x19, 0x83, 0xE0, 0x3C, 0x07, 0x00, 0xE0, +/* 'w' 0x77 */ 0xC1, 0xC1, 0xB0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36, 0x31, 0x9B, 0x18, 0xCD, 0x98, 0x64, 0x6C, 0x16, 0x36, 0x0F, 0x1A, 0x07, 0x8F, 0x03, 0x83, 0x80, 0xC1, 0xC0, +/* 'x' 0x78 */ 0xC1, 0xF8, 0x66, 0x30, 0xCC, 0x3E, 0x07, 0x00, 0xC0, 0x78, 0x36, 0x0C, 0xC6, 0x3B, 0x06, 0xC0, 0xC0, +/* 'y' 0x79 */ 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0C, 0x60, 0xCC, 0x1B, 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, 0x00, +/* 'z' 0x7A */ 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0, +/* '{' 0x7B */ 0x19, 0xCC, 0x63, 0x18, 0xC6, 0x31, 0x99, 0x86, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x1C, 0x60, +/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, +/* '}' 0x7D */ 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x0C, 0x33, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x73, 0x00, +/* '~' 0x7E */ 0x70, 0x3E, 0x09, 0xE4, 0x1F, 0x03, 0x80, +/* 0x7F */ +/* 0x80 */ 0x01, 0xF0, 0x1F, 0xF0, 0xE0, 0xC7, 0x00, 0x18, 0x00, 0xC0, 0x07, 0xFF, 0x3F, 0xFC, 0x30, 0x01, 0xFF, 0x8F, 0xFC, 0x0C, 0x00, 0x18, 0x00, 0x70, 0x00, 0xE0, 0x81, 0xFE, 0x03, 0xF0, +/* 0x81 */ +/* 0x82 */ 0xF5, 0x80, +/* 0x83 */ +/* 0x84 */ 0xCF, 0x34, 0x51, 0x88, +/* 0x85 */ 0xC6, 0x3C, 0x63, +/* 0x86 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x00, +/* 0x87 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x0F, 0xFF, 0xFF, 0x0C, 0x03, 0x00, 0xC0, 0x30, +/* 0x88 */ +/* 0x89 */ 0x38, 0x18, 0x00, 0xF8, 0x30, 0x03, 0x18, 0xC0, 0x04, 0x11, 0x80, 0x0C, 0x66, 0x00, 0x0F, 0x8C, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x40, 0x00, 0x01, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x31, 0xC0, 0xE0, 0x67, 0xC3, 0xC1, 0x98, 0xCC, 0xC3, 0x20, 0x90, 0x8C, 0x63, 0x33, 0x10, 0x7C, 0x3C, 0x60, 0x70, 0x38, +/* 0x8A */ 0x0C, 0x40, 0x1F, 0x00, 0x38, 0x03, 0xF8, 0x1F, 0xF0, 0xE0, 0xE6, 0x01, 0xD8, 0x03, 0x60, 0x01, 0xC0, 0x07, 0x80, 0x0F, 0xE0, 0x0F, 0xF0, 0x03, 0xE0, 0x01, 0xF0, 0x03, 0xC0, 0x0F, 0x80, 0x37, 0x83, 0x8F, 0xFC, 0x0F, 0xE0, +/* 0x8B */ 0x2F, 0x49, 0x99, +/* 0x8C */ 0x01, 0x80, 0x0C, 0x00, 0x60, 0x00, 0x00, 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80, +/* 0x8D */ 0x0C, 0xC0, 0xF8, 0x07, 0x0F, 0xFF, 0xFF, 0xF0, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, +/* 0x8E */ 0x0C, 0xC0, 0x3C, 0x00, 0xE1, 0xFF, 0xFF, 0xFF, 0x80, 0x1C, 0x01, 0xC0, 0x1C, 0x00, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x01, 0xFF, 0xFF, 0xFF, 0x80, +/* 0x8F */ 0x01, 0x80, 0x18, 0x01, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x06, 0x00, 0x70, 0x07, 0x00, 0x70, 0x03, 0x00, 0x38, 0x03, 0x80, 0x38, 0x01, 0x80, 0x1C, 0x01, 0xC0, 0x0F, 0xFF, 0xFF, 0xFC, +/* 0x90 */ +/* 0x91 */ 0x6A, 0xF0, +/* 0x92 */ 0xF5, 0x60, +/* 0x93 */ 0x4E, 0x28, 0xA2, 0xCF, 0x30, +/* 0x94 */ 0xCF, 0x34, 0x51, 0x4E, 0x20, +/* 0x95 */ 0x7B, 0xFF, 0xFF, 0xFD, 0xE0, +/* 0x96 */ 0xFF, 0xFF, 0xF0, +/* 0x97 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 0x98 */ +/* 0x99 */ 0xFF, 0x70, 0x1F, 0xFD, 0xC0, 0x71, 0x87, 0x83, 0xC6, 0x1E, 0x0F, 0x18, 0x68, 0x3C, 0x61, 0xB1, 0xB1, 0x86, 0xC6, 0xC6, 0x19, 0x1B, 0x18, 0x66, 0xCC, 0x61, 0x9B, 0x31, 0x86, 0x3C, 0xC6, 0x18, 0xE3, 0x18, 0x63, 0x8C, +/* 0x9A */ 0x63, 0x0D, 0x83, 0x60, 0x70, 0x00, 0x0F, 0x87, 0xFB, 0x86, 0xC0, 0x30, 0x0F, 0x01, 0xFC, 0x0F, 0xC0, 0x7C, 0x0F, 0x87, 0x7F, 0x8F, 0xC0, +/* 0x9B */ 0x99, 0x92, 0xF4, +/* 0x9C */ 0x07, 0x03, 0x80, 0xC0, 0x60, 0x00, 0x0F, 0x87, 0xFB, 0x86, 0xC0, 0x30, 0x0F, 0x01, 0xFC, 0x0F, 0xC0, 0x7C, 0x0F, 0x87, 0x7F, 0x8F, 0xC0, +/* 0x9D */ 0x03, 0x06, 0x66, 0x64, 0x60, 0xF8, 0xF8, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x38, +/* 0x9E */ 0x63, 0x0C, 0x83, 0x60, 0x70, 0x00, 0x3F, 0xFF, 0xFC, 0x06, 0x03, 0x01, 0xC0, 0xE0, 0x70, 0x18, 0x0C, 0x06, 0x03, 0x80, 0xFF, 0xFF, 0xF0, +/* 0x9F */ 0x07, 0x01, 0x80, 0xC0, 0x20, 0x00, 0x3F, 0xFF, 0xFC, 0x06, 0x03, 0x01, 0xC0, 0xE0, 0x70, 0x18, 0x0C, 0x06, 0x03, 0x80, 0xFF, 0xFF, 0xF0, +/* 0xA0 */ +/* 0xA1 */ 0xC6, 0xD9, 0xB1, 0xC0, +/* 0xA2 */ 0x83, 0x8D, 0xF1, 0xC0, +/* 0xA3 */ 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x80, 0xCC, 0x06, 0xC0, 0x3C, 0x01, 0xC0, 0x3C, 0x01, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x3F, 0xF9, 0xFF, 0xC0, +/* 0xA4 */ 0xDD, 0xFF, 0xD8, 0xD8, 0x3C, 0x1E, 0x0F, 0x8D, 0xFF, 0xDD, 0x80, +/* 0xA5 */ 0x03, 0x80, 0x03, 0xC0, 0x07, 0xC0, 0x07, 0xC0, 0x04, 0xE0, 0x0C, 0xE0, 0x0C, 0xE0, 0x08, 0x70, 0x18, 0x70, 0x18, 0x70, 0x10, 0x38, 0x3F, 0xF8, 0x3F, 0xF8, 0x30, 0x1C, 0x70, 0x0C, 0x60, 0x0C, 0x60, 0x0E, 0xE0, 0x06, 0x00, 0x0E, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x0F, +/* 0xA6 */ 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC, +/* 0xA7 */ 0x0F, 0x03, 0xF0, 0xE7, 0x18, 0x63, 0x0C, 0x70, 0x07, 0x03, 0xF8, 0xC3, 0x98, 0x3B, 0x03, 0xF0, 0x37, 0x06, 0x78, 0xC7, 0xB0, 0x7C, 0x03, 0x80, 0x39, 0x83, 0x30, 0x67, 0x1C, 0x7F, 0x07, 0xC0, +/* 0xA8 */ 0xCF, 0x30, +/* 0xA9 */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xE3, 0x1C, 0x73, 0xF3, 0x99, 0x86, 0x6C, 0xC1, 0x8F, 0x30, 0x03, 0xCC, 0x00, 0xF3, 0x00, 0x3C, 0xC1, 0x8D, 0x98, 0x66, 0x77, 0xF3, 0x8E, 0x79, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00, +/* 0xAA */ 0x0F, 0xC0, 0xFF, 0xC3, 0x03, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDC, 0x0E, 0x3F, 0xF0, 0x3F, 0x00, 0x20, 0x01, 0xE0, 0x01, 0x80, 0x06, 0x00, 0xF0, 0x00, +/* 0xAB */ 0x21, 0x63, 0xE7, 0x84, 0x84, 0xE7, 0x63, 0x21, +/* 0xAC */ 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, +/* 0xAD */ 0xFF, 0xF0, +/* 0xAE */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xFF, 0x1C, 0x7F, 0xF3, 0x9B, 0x04, 0x6C, 0xC1, 0x8F, 0x30, 0x43, 0xCF, 0xF0, 0xF3, 0xFC, 0x3C, 0xC1, 0x0D, 0xB0, 0x66, 0x7C, 0x1B, 0x8F, 0x07, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00, +/* 0xAF */ 0x03, 0x00, 0x18, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x1C, 0x01, 0xC0, 0x1C, 0x00, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x01, 0xFF, 0xFF, 0xFF, 0x80, +/* 0xB0 */ 0x38, 0xFB, 0x1C, 0x18, 0x38, 0xDF, 0x1C, +/* 0xB1 */ 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x7F, 0xE7, 0xFE, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, +/* 0xB2 */ 0x76, 0x31, 0x87, 0x80, +/* 0xB3 */ 0x66, 0x66, 0x66, 0x67, 0x7E, 0xE6, 0x66, 0x66, 0x66, +/* 0xB4 */ 0x3B, 0x99, 0x80, +/* 0xB5 */ 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x1C, 0xE3, 0xCF, 0xEF, 0xFC, 0x7C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00, +/* 0xB6 */ 0x1F, 0xE7, 0xFD, 0xF3, 0x7E, 0x6F, 0xCD, 0xF9, 0xBF, 0x37, 0xE6, 0x7C, 0xCF, 0x98, 0xF3, 0x06, 0x60, 0xCC, 0x19, 0x83, 0x30, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x60, 0xCC, +/* 0xB7 */ 0xF0, +/* 0xB8 */ 0x10, 0xF0, 0xE3, 0x78, +/* 0xB9 */ 0x1F, 0x01, 0xFC, 0x1C, 0x70, 0xC1, 0x80, 0x0C, 0x00, 0xE0, 0xFF, 0x1F, 0x18, 0xC0, 0xC6, 0x06, 0x38, 0x70, 0xFF, 0xE3, 0xC7, 0x00, 0x30, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x03, 0xC0, +/* 0xBA */ 0x3F, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3E, 0x01, 0xF0, 0x3C, 0x0D, 0xDE, 0x7F, 0x02, 0x01, 0xE0, 0x18, 0x46, 0x0F, 0x00, +/* 0xBB */ 0x88, 0xC6, 0xE7, 0x21, 0x21, 0xE7, 0xC6, 0x88, +/* 0xBC */ 0xC3, 0x31, 0x8C, 0x63, 0x10, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0, +/* 0xBD */ 0x77, 0x66, 0xCC, 0xC8, +/* 0xBE */ 0xC7, 0x9B, 0x36, 0xCC, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x80, +/* 0xBF */ 0x0C, 0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0, +/* 0xC0 */ 0x03, 0x80, 0x18, 0x00, 0x40, 0x00, 0x00, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x70, +/* 0xC1 */ 0x01, 0xC0, 0x0C, 0x00, 0x20, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 0xC2 */ 0x07, 0x00, 0x3E, 0x01, 0x8C, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 0xC3 */ 0x10, 0x40, 0x63, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 0xC4 */ 0x0C, 0xC0, 0x33, 0x00, 0x00, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0xFC, 0x03, 0x30, 0x0C, 0xC0, 0x73, 0x81, 0x86, 0x06, 0x18, 0x38, 0x70, 0xC0, 0xC3, 0xFF, 0x1F, 0xFE, 0x60, 0x19, 0x80, 0x6E, 0x01, 0xF0, 0x03, 0xC0, 0x0C, +/* 0xC5 */ 0x18, 0x0C, 0x06, 0x00, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0, +/* 0xC6 */ 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x7E, 0x03, 0xFF, 0x0E, 0x07, 0x38, 0x07, 0x60, 0x06, 0xC0, 0x03, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0xE0, 0x06, 0xC0, 0x0D, 0xC0, 0x31, 0xE0, 0xE1, 0xFF, 0x80, 0xFC, 0x00, +/* 0xC7 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x66, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x01, 0xDC, 0x03, 0x1C, 0x1E, 0x1F, 0xF8, 0x0F, 0xC0, 0x08, 0x00, 0x1E, 0x00, 0x0C, 0x01, 0x18, 0x01, 0xE0, 0x00, +/* 0xC8 */ 0x06, 0x30, 0x07, 0xC0, 0x07, 0x00, 0x3F, 0x01, 0xFF, 0x87, 0x03, 0x9C, 0x03, 0xB0, 0x03, 0x60, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x70, 0x03, 0x60, 0x06, 0xE0, 0x18, 0xF0, 0x70, 0xFF, 0xC0, 0x7E, 0x00, +/* 0xC9 */ 0x07, 0x00, 0x60, 0x0C, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, +/* 0xCA */ 0xFF, 0xE7, 0xFF, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xFF, 0xE7, 0xFF, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xFF, 0xF7, 0xFF, 0x80, 0x18, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x01, 0xE0, +/* 0xCB */ 0x19, 0x81, 0x98, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFE, 0xFF, 0xEC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0, +/* 0xCC */ 0x08, 0xC0, 0xF8, 0x07, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFE, 0xFF, 0xEC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0, +/* 0xCD */ 0x36, 0xC0, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, +/* 0xCE */ 0x39, 0xFC, 0x40, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, +/* 0xCF */ 0x18, 0xC0, 0x3E, 0x00, 0x70, 0x3F, 0xF0, 0xFF, 0xE3, 0x01, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x07, 0xC0, 0x1B, 0x00, 0xEC, 0x07, 0x3F, 0xF8, 0xFF, 0x80, +/* 0xD0 */ 0x7F, 0xE0, 0xFF, 0xE1, 0x80, 0xE3, 0x00, 0xE6, 0x00, 0xCC, 0x01, 0xD8, 0x01, 0xB0, 0x03, 0xFE, 0x07, 0xFC, 0x0D, 0x80, 0x1B, 0x00, 0x36, 0x00, 0x6C, 0x01, 0x98, 0x07, 0x30, 0x1C, 0x7F, 0xF0, 0xFF, 0xC0, +/* 0xD1 */ 0x01, 0x80, 0x18, 0x01, 0x80, 0x00, 0x0E, 0x01, 0xF0, 0x0F, 0xC0, 0x7E, 0x03, 0xD8, 0x1E, 0xE0, 0xF3, 0x07, 0x9C, 0x3C, 0x61, 0xE1, 0x8F, 0x0E, 0x78, 0x33, 0xC1, 0xDE, 0x06, 0xF0, 0x1F, 0x80, 0xFC, 0x03, 0xE0, 0x1C, +/* 0xD2 */ 0x0C, 0xC0, 0x3C, 0x00, 0xE1, 0xC0, 0x3E, 0x01, 0xF8, 0x0F, 0xC0, 0x7B, 0x03, 0xDC, 0x1E, 0x60, 0xF3, 0x87, 0x8C, 0x3C, 0x31, 0xE1, 0xCF, 0x06, 0x78, 0x3B, 0xC0, 0xDE, 0x03, 0xF0, 0x1F, 0x80, 0x7C, 0x03, 0x80, +/* 0xD3 */ 0x00, 0xE0, 0x00, 0x60, 0x00, 0x40, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80, +/* 0xD4 */ 0x03, 0xC0, 0x01, 0xE0, 0x01, 0x98, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80, +/* 0xD5 */ 0x03, 0xB8, 0x01, 0x98, 0x00, 0x98, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80, +/* 0xD6 */ 0x06, 0x30, 0x03, 0x18, 0x00, 0x00, 0x00, 0xFE, 0x01, 0xFF, 0xC1, 0xE0, 0xF0, 0xC0, 0x18, 0xC0, 0x06, 0x60, 0x03, 0x60, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x06, 0xC0, 0x06, 0x60, 0x03, 0x18, 0x03, 0x0F, 0x07, 0x83, 0xFF, 0x80, 0x7F, 0x00, +/* 0xD7 */ 0x81, 0xC3, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0xC3, 0x81, +/* 0xD8 */ 0x0C, 0xC0, 0x1E, 0x00, 0x78, 0x3F, 0xF8, 0xFF, 0xF3, 0x00, 0xEC, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x03, 0x3F, 0xF8, 0xFF, 0xF3, 0x00, 0xEC, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1C, +/* 0xD9 */ 0x03, 0x00, 0x7C, 0x03, 0x70, 0x19, 0x80, 0xF8, 0x03, 0xC3, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3E, 0x03, 0xB8, 0x38, 0xFF, 0x83, 0xF0, +/* 0xDA */ 0x03, 0x80, 0x18, 0x01, 0x80, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0, +/* 0xDB */ 0x0E, 0xE0, 0x66, 0x03, 0x60, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0, +/* 0xDC */ 0x0C, 0xC0, 0x66, 0x00, 0x01, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0x01, 0xDC, 0x1C, 0x7F, 0xC1, 0xF8, 0x00, +/* 0xDD */ 0x01, 0x80, 0x0C, 0x00, 0x60, 0x00, 0x00, 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, +/* 0xDE */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x04, 0x00, 0x78, 0x01, 0x81, 0x18, 0x0F, 0x00, +/* 0xDF */ 0x1F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0D, 0x81, 0xB0, 0x66, 0x38, 0xC7, 0xD8, 0x1B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x3E, 0x0E, 0xCF, 0x99, 0xE0, +/* 0xE0 */ 0x0C, 0x61, 0x8C, 0x03, 0x3D, 0xFC, 0xE3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x00, +/* 0xE1 */ 0x07, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7, +/* 0xE2 */ 0x0C, 0x01, 0xE0, 0x1B, 0x03, 0x30, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7, +/* 0xE3 */ 0x20, 0x82, 0x10, 0x3F, 0x01, 0xE0, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7, +/* 0xE4 */ 0x19, 0x81, 0x98, 0x00, 0x00, 0x00, 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70, +/* 0xE5 */ 0x3B, 0x30, 0x06, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x30, +/* 0xE6 */ 0x07, 0x01, 0x80, 0xC0, 0x20, 0x00, 0x07, 0xC3, 0xF9, 0x87, 0xE0, 0xF0, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0E, 0x0D, 0xC7, 0x3F, 0x87, 0xC0, +/* 0xE7 */ 0x1F, 0x0F, 0xE7, 0x1D, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x02, 0x00, 0xE0, 0x0C, 0x23, 0x07, 0x80, +/* 0xE8 */ 0x21, 0x0C, 0xC1, 0xE0, 0x78, 0x00, 0x07, 0xC3, 0xF9, 0x87, 0xE0, 0xF0, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0E, 0x0D, 0xC7, 0x3F, 0x87, 0xC0, +/* 0xE9 */ 0x03, 0x00, 0xC0, 0x30, 0x04, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0, +/* 0xEA */ 0x1F, 0x07, 0xF1, 0x87, 0x60, 0x6C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x1D, 0x87, 0x1F, 0xC1, 0xF8, 0x06, 0x01, 0x80, 0x30, 0x06, 0x00, 0x78, +/* 0xEB */ 0x31, 0x86, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x03, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x0C, 0x01, 0xC0, 0xDC, 0x31, 0xFC, 0x1F, 0x00, +/* 0xEC */ 0x31, 0x82, 0x60, 0x6C, 0x07, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0, +/* 0xED */ 0x39, 0x99, 0x80, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x80, +/* 0xEE */ 0x71, 0xED, 0xA3, 0x00, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, +/* 0xEF */ 0x00, 0x67, 0x00, 0x66, 0x00, 0x64, 0x00, 0x6C, 0x00, 0x60, 0x1E, 0x60, 0x3F, 0xE0, 0x71, 0xE0, 0xE0, 0xE0, 0xC0, 0x60, 0xC0, 0x60, 0xC0, 0x60, 0xC0, 0x60, 0xC0, 0x60, 0xE0, 0xE0, 0x71, 0xE0, 0x3F, 0x60, 0x1E, 0x60, +/* 0xF0 */ 0x00, 0x60, 0x06, 0x03, 0xF0, 0x06, 0x00, 0x61, 0xE6, 0x3F, 0xE7, 0x1E, 0xE0, 0xEC, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xE0, 0xE7, 0x1E, 0x3F, 0xE1, 0xE6, +/* 0xF1 */ 0x03, 0x81, 0xC0, 0x60, 0x30, 0x00, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30, +/* 0xF2 */ 0x31, 0x84, 0xC1, 0xA0, 0x38, 0x00, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30, +/* 0xF3 */ 0x07, 0x00, 0xC0, 0x30, 0x04, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0, +/* 0xF4 */ 0x0C, 0x03, 0xC0, 0xD8, 0x19, 0x80, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0, +/* 0xF5 */ 0x0C, 0xC3, 0xB8, 0x66, 0x0D, 0x80, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0, +/* 0xF6 */ 0x31, 0x86, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x07, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00, +/* 0xF7 */ 0x06, 0x00, 0x60, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, +/* 0xF8 */ 0xC7, 0x37, 0x8E, 0x03, 0x3D, 0xFC, 0xE3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x00, +/* 0xF9 */ 0x0E, 0x07, 0xC1, 0xB8, 0x6C, 0x1F, 0x03, 0x8C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x07, 0xE3, 0xDF, 0xB3, 0xCC, +/* 0xFA */ 0x03, 0x01, 0x80, 0x60, 0x30, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30, +/* 0xFB */ 0x1D, 0xC6, 0x61, 0xB0, 0xCC, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30, +/* 0xFC */ 0x31, 0x8C, 0x60, 0x00, 0x00, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0, +/* 0xFD */ 0x03, 0x80, 0x60, 0x18, 0x06, 0x00, 0x01, 0xC0, 0xD8, 0x1B, 0x06, 0x70, 0xC6, 0x18, 0xC6, 0x18, 0xC1, 0x98, 0x36, 0x06, 0xC0, 0x78, 0x0E, 0x01, 0xC0, 0x30, 0x06, 0x01, 0xC0, 0xF0, 0x1C, 0x00, +/* 0xFE */ 0x61, 0x86, 0x3E, 0xF9, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0x8E, 0x10, 0x83, 0xC3, 0x78, +/* 0xFF */ 0xF0, +}; + +const GFXglyph FreeSans12pt_Win1250Glyphs[] PROGMEM = { +/* 0x01 */ { 0, 19, 20, 21, 1, -17 }, +/* 0x02 */ { 48, 19, 20, 21, 1, -17 }, +/* 0x03 */ { 96, 21, 20, 23, 1, -17 }, +/* 0x04 */ { 149, 21, 20, 23, 1, -17 }, +/* 0x05 */ { 202, 20, 20, 22, 1, -17 }, +/* 0x06 */ { 252, 20, 20, 22, 1, -17 }, +/* 0x07 */ { 302, 0, 0, 8, 0, 0 }, +/* 0x08 */ { 302, 23, 20, 25, 1, -17 }, +/* 0x09 */ { 360, 23, 16, 25, 1, -16 }, +/* 0x0A */ { 406, 0, 0, 8, 0, 0 }, +/* 0x0B */ { 406, 21, 20, 23, 1, -17 }, +/* 0x0C */ { 459, 19, 18, 21, 1, -15 }, +/* 0x0D */ { 502, 0, 0, 8, 0, 0 }, +/* 0x0E */ { 502, 20, 20, 22, 1, -17 }, +/* 0x0F */ { 552, 21, 21, 23, 1, -18 }, +/* 0x10 */ { 608, 19, 20, 21, 1, -17 }, +/* 0x11 */ { 656, 21, 20, 23, 1, -17 }, +/* 0x12 */ { 709, 20, 20, 22, 1, -17 }, +/* 0x13 */ { 759, 21, 20, 23, 1, -17 }, +/* 0x14 */ { 812, 21, 20, 23, 1, -17 }, +/* 0x15 */ { 865, 22, 20, 24, 1, -17 }, +/* 0x16 */ { 920, 16, 20, 18, 1, -17 }, +/* 0x17 */ { 960, 21, 20, 23, 1, -17 }, +/* 0x18 */ { 1013, 23, 20, 25, 1, -17 }, +/* 0x19 */ { 1071, 21, 20, 23, 1, -17 }, +/* 0x1A */ { 1124, 15, 19, 17, 1, -16 }, +/* 0x1B */ { 1160, 24, 21, 26, 1, -18 }, +/* 0x1C */ { 1223, 21, 20, 23, 1, -17 }, +/* 0x1D */ { 1276, 21, 21, 23, 1, -18 }, +/* 0x1E */ { 1332, 20, 20, 22, 1, -17 }, +/* 0x1F */ { 1382, 15, 20, 17, 1, -17 }, +/* ' ' 0x20 */ { 1420, 0, 0, 6, 0, 0 }, +/* '!' 0x21 */ { 1420, 2, 18, 8, 3, -16 }, +/* '"' 0x22 */ { 1425, 6, 6, 8, 1, -15 }, +/* '#' 0x23 */ { 1430, 13, 16, 13, 0, -14 }, +/* '$' 0x24 */ { 1456, 11, 20, 13, 1, -16 }, +/* '%' 0x25 */ { 1484, 20, 17, 21, 1, -15 }, +/* '&' 0x26 */ { 1527, 14, 17, 16, 1, -15 }, +/* ''' 0x27 */ { 1557, 2, 6, 5, 1, -15 }, +/* '(' 0x28 */ { 1559, 5, 23, 8, 2, -16 }, +/* ')' 0x29 */ { 1574, 5, 23, 8, 1, -16 }, +/* '*' 0x2A */ { 1589, 7, 7, 9, 1, -16 }, +/* '+' 0x2B */ { 1596, 10, 11, 14, 2, -9 }, +/* ',' 0x2C */ { 1610, 2, 6, 7, 2, 0 }, +/* '-' 0x2D */ { 1612, 6, 2, 8, 1, -6 }, +/* '.' 0x2E */ { 1614, 2, 2, 6, 2, 0 }, +/* '/' 0x2F */ { 1615, 7, 18, 7, 0, -16 }, +/* '0' 0x30 */ { 1631, 11, 17, 13, 1, -15 }, +/* '1' 0x31 */ { 1655, 5, 17, 13, 3, -15 }, +/* '2' 0x32 */ { 1666, 11, 17, 13, 1, -15 }, +/* '3' 0x33 */ { 1690, 11, 17, 13, 1, -15 }, +/* '4' 0x34 */ { 1714, 11, 17, 13, 1, -15 }, +/* '5' 0x35 */ { 1738, 11, 17, 13, 1, -15 }, +/* '6' 0x36 */ { 1762, 11, 17, 13, 1, -15 }, +/* '7' 0x37 */ { 1786, 11, 17, 13, 1, -15 }, +/* '8' 0x38 */ { 1810, 11, 17, 13, 1, -15 }, +/* '9' 0x39 */ { 1834, 11, 17, 13, 1, -15 }, +/* ':' 0x3A */ { 1858, 2, 13, 6, 2, -11 }, +/* ';' 0x3B */ { 1862, 2, 16, 6, 2, -10 }, +/* '<' 0x3C */ { 1866, 12, 11, 14, 1, -9 }, +/* '=' 0x3D */ { 1883, 12, 6, 14, 1, -7 }, +/* '>' 0x3E */ { 1892, 12, 11, 14, 1, -9 }, +/* '?' 0x3F */ { 1909, 10, 18, 13, 2, -16 }, +/* '@' 0x40 */ { 1932, 22, 21, 24, 1, -16 }, +/* 'A' 0x41 */ { 1990, 14, 18, 16, 1, -16 }, +/* 'B' 0x42 */ { 2022, 13, 18, 16, 2, -16 }, +/* 'C' 0x43 */ { 2052, 15, 18, 17, 1, -16 }, +/* 'D' 0x44 */ { 2086, 14, 18, 17, 2, -16 }, +/* 'E' 0x45 */ { 2118, 12, 18, 15, 2, -16 }, +/* 'F' 0x46 */ { 2145, 11, 18, 14, 2, -16 }, +/* 'G' 0x47 */ { 2170, 16, 18, 18, 1, -16 }, +/* 'H' 0x48 */ { 2206, 13, 18, 17, 2, -16 }, +/* 'I' 0x49 */ { 2236, 2, 18, 7, 2, -16 }, +/* 'J' 0x4A */ { 2241, 9, 18, 13, 1, -16 }, +/* 'K' 0x4B */ { 2262, 13, 18, 16, 2, -16 }, +/* 'L' 0x4C */ { 2292, 10, 18, 14, 2, -16 }, +/* 'M' 0x4D */ { 2315, 16, 18, 20, 2, -16 }, +/* 'N' 0x4E */ { 2351, 13, 18, 18, 2, -16 }, +/* 'O' 0x4F */ { 2381, 17, 18, 19, 1, -16 }, +/* 'P' 0x50 */ { 2420, 12, 18, 16, 2, -16 }, +/* 'Q' 0x51 */ { 2447, 17, 19, 19, 1, -16 }, +/* 'R' 0x52 */ { 2488, 14, 18, 17, 2, -16 }, +/* 'S' 0x53 */ { 2520, 14, 18, 16, 1, -16 }, +/* 'T' 0x54 */ { 2552, 12, 18, 15, 1, -16 }, +/* 'U' 0x55 */ { 2579, 13, 18, 17, 2, -16 }, +/* 'V' 0x56 */ { 2609, 14, 18, 15, 1, -16 }, +/* 'W' 0x57 */ { 2641, 22, 18, 22, 0, -16 }, +/* 'X' 0x58 */ { 2691, 14, 18, 16, 1, -16 }, +/* 'Y' 0x59 */ { 2723, 14, 18, 16, 1, -16 }, +/* 'Z' 0x5A */ { 2755, 13, 18, 15, 1, -16 }, +/* '[' 0x5B */ { 2785, 4, 23, 7, 2, -16 }, +/* '\' 0x5C */ { 2797, 7, 18, 7, 0, -16 }, +/* ']' 0x5D */ { 2813, 4, 23, 7, 1, -16 }, +/* '^' 0x5E */ { 2825, 9, 9, 11, 1, -15 }, +/* '_' 0x5F */ { 2836, 15, 1, 13, -1, 5 }, +/* '`' 0x60 */ { 2838, 5, 4, 6, 1, -16 }, +/* 'a' 0x61 */ { 2841, 12, 13, 13, 1, -11 }, +/* 'b' 0x62 */ { 2861, 12, 18, 13, 1, -16 }, +/* 'c' 0x63 */ { 2888, 10, 13, 12, 1, -11 }, +/* 'd' 0x64 */ { 2905, 11, 18, 13, 1, -16 }, +/* 'e' 0x65 */ { 2930, 11, 13, 13, 1, -11 }, +/* 'f' 0x66 */ { 2948, 5, 18, 7, 1, -16 }, +/* 'g' 0x67 */ { 2960, 11, 18, 13, 1, -11 }, +/* 'h' 0x68 */ { 2985, 10, 18, 13, 1, -16 }, +/* 'i' 0x69 */ { 3008, 2, 18, 5, 2, -16 }, +/* 'j' 0x6A */ { 3013, 4, 23, 6, 0, -16 }, +/* 'k' 0x6B */ { 3025, 10, 18, 12, 1, -16 }, +/* 'l' 0x6C */ { 3048, 2, 18, 5, 1, -16 }, +/* 'm' 0x6D */ { 3053, 17, 13, 19, 1, -11 }, +/* 'n' 0x6E */ { 3081, 10, 13, 13, 1, -11 }, +/* 'o' 0x6F */ { 3098, 11, 13, 13, 1, -11 }, +/* 'p' 0x70 */ { 3116, 12, 17, 13, 1, -11 }, +/* 'q' 0x71 */ { 3142, 11, 17, 13, 1, -11 }, +/* 'r' 0x72 */ { 3166, 6, 13, 8, 1, -11 }, +/* 's' 0x73 */ { 3176, 10, 13, 12, 1, -11 }, +/* 't' 0x74 */ { 3193, 5, 16, 7, 1, -14 }, +/* 'u' 0x75 */ { 3203, 10, 13, 13, 1, -11 }, +/* 'v' 0x76 */ { 3220, 11, 13, 12, 0, -11 }, +/* 'w' 0x77 */ { 3238, 17, 13, 17, 0, -11 }, +/* 'x' 0x78 */ { 3266, 10, 13, 11, 1, -11 }, +/* 'y' 0x79 */ { 3283, 11, 18, 11, 0, -11 }, +/* 'z' 0x7A */ { 3308, 10, 13, 12, 1, -11 }, +/* '{' 0x7B */ { 3325, 5, 23, 8, 1, -16 }, +/* '|' 0x7C */ { 3340, 2, 23, 6, 2, -16 }, +/* '}' 0x7D */ { 3346, 5, 23, 8, 2, -16 }, +/* '~' 0x7E */ { 3361, 10, 5, 12, 1, -9 }, +/* 0x7F */ { 3368, 0, 0, 0, 0, 0 }, +/* 0x80 */ { 3368, 14, 17, 16, 1, -17 }, +/* 0x81 */ { 3398, 0, 0, 8, 0, 0 }, +/* 0x82 */ { 3398, 2, 5, 6, 2, -2 }, +/* 0x83 */ { 3400, 0, 0, 8, 0, 0 }, +/* 0x84 */ { 3400, 6, 5, 10, 2, -2 }, +/* 0x85 */ { 3404, 12, 2, 16, 2, -2 }, +/* 0x86 */ { 3407, 10, 21, 13, 2, -17 }, +/* 0x87 */ { 3434, 10, 20, 13, 2, -17 }, +/* 0x88 */ { 3459, 0, 0, 8, 0, 0 }, +/* 0x89 */ { 3459, 23, 18, 24, 0, -18 }, +/* 0x8A */ { 3511, 14, 21, 16, 1, -21 }, +/* 0x8B */ { 3548, 3, 8, 6, 1, -11 }, +/* 0x8C */ { 3551, 14, 22, 16, 1, -22 }, +/* 0x8D */ { 3590, 12, 21, 15, 1, -21 }, +/* 0x8E */ { 3622, 13, 21, 15, 1, -21 }, +/* 0x8F */ { 3657, 13, 22, 15, 1, -22 }, +/* 0x90 */ { 3693, 0, 0, 8, 0, 0 }, +/* 0x91 */ { 3693, 2, 6, 6, 2, -18 }, +/* 0x92 */ { 3695, 2, 6, 6, 2, -18 }, +/* 0x93 */ { 3697, 6, 6, 10, 2, -18 }, +/* 0x94 */ { 3702, 6, 6, 10, 2, -18 }, +/* 0x95 */ { 3707, 6, 6, 10, 2, -11 }, +/* 0x96 */ { 3712, 10, 2, 12, 1, -8 }, +/* 0x97 */ { 3715, 22, 2, 24, 1, -8 }, +/* 0x98 */ { 3721, 0, 0, 8, 0, 0 }, +/* 0x99 */ { 3721, 22, 13, 24, 2, -18 }, +/* 0x9A */ { 3757, 10, 18, 12, 1, -18 }, +/* 0x9B */ { 3780, 3, 8, 6, 2, -10 }, +/* 0x9C */ { 3783, 10, 18, 12, 1, -18 }, +/* 0x9D */ { 3806, 8, 18, 11, 1, -18 }, +/* 0x9E */ { 3824, 10, 18, 12, 1, -18 }, +/* 0x9F */ { 3847, 10, 18, 12, 1, -18 }, +/* 0xA0 */ { 3870, 0, 0, 7, 0, 0 }, +/* 0xA1 */ { 3870, 7, 4, 8, 0, -18 }, +/* 0xA2 */ { 3874, 7, 4, 8, 0, -18 }, +/* 0xA3 */ { 3878, 13, 18, 15, 1, -18 }, +/* 0xA4 */ { 3908, 9, 9, 13, 2, -13 }, +/* 0xA5 */ { 3919, 16, 23, 16, 1, -18 }, +/* 0xA6 */ { 3965, 2, 23, 6, 2, -18 }, +/* 0xA7 */ { 3971, 11, 23, 13, 1, -18 }, +/* 0xA8 */ { 4003, 6, 2, 8, 1, -17 }, +/* 0xA9 */ { 4005, 18, 17, 19, 1, -17 }, +/* 0xAA */ { 4044, 14, 23, 16, 1, -18 }, +/* 0xAB */ { 4085, 8, 8, 12, 2, -11 }, +/* 0xAC */ { 4093, 12, 6, 14, 1, -9 }, +/* 0xAD */ { 4102, 6, 2, 8, 1, -8 }, +/* 0xAE */ { 4104, 18, 17, 19, 1, -17 }, +/* 0xAF */ { 4143, 13, 21, 15, 1, -21 }, +/* 0xB0 */ { 4178, 7, 8, 15, 4, -17 }, +/* 0xB1 */ { 4185, 12, 15, 14, 1, -15 }, +/* 0xB2 */ { 4208, 5, 5, 8, 1, 0 }, +/* 0xB3 */ { 4212, 4, 18, 6, 1, -18 }, +/* 0xB4 */ { 4221, 5, 4, 8, 2, -18 }, +/* 0xB5 */ { 4224, 12, 17, 13, 2, -13 }, +/* 0xB6 */ { 4250, 11, 21, 13, 2, -18 }, +/* 0xB7 */ { 4279, 2, 2, 6, 2, -8 }, +/* 0xB8 */ { 4280, 6, 5, 8, 1, 0 }, +/* 0xB9 */ { 4284, 13, 18, 13, 1, -13 }, +/* 0xBA */ { 4314, 10, 18, 12, 1, -13 }, +/* 0xBB */ { 4337, 8, 8, 12, 2, -10 }, +/* 0xBC */ { 4345, 10, 18, 14, 2, -18 }, +/* 0xBD */ { 4368, 8, 4, 8, 0, -18 }, +/* 0xBE */ { 4372, 7, 18, 9, 1, -18 }, +/* 0xBF */ { 4388, 10, 17, 12, 1, -17 }, +/* 0xC0 */ { 4410, 14, 22, 17, 2, -22 }, +/* 0xC1 */ { 4449, 14, 22, 16, 1, -22 }, +/* 0xC2 */ { 4488, 14, 22, 16, 1, -22 }, +/* 0xC3 */ { 4527, 14, 22, 16, 1, -22 }, +/* 0xC4 */ { 4566, 14, 21, 16, 1, -21 }, +/* 0xC5 */ { 4603, 10, 22, 14, 2, -22 }, +/* 0xC6 */ { 4631, 15, 22, 17, 1, -22 }, +/* 0xC7 */ { 4673, 15, 23, 17, 1, -18 }, +/* 0xC8 */ { 4717, 15, 21, 17, 1, -21 }, +/* 0xC9 */ { 4757, 12, 22, 15, 2, -22 }, +/* 0xCA */ { 4790, 13, 23, 15, 2, -18 }, +/* 0xCB */ { 4828, 12, 21, 15, 2, -21 }, +/* 0xCC */ { 4860, 12, 21, 15, 2, -21 }, +/* 0xCD */ { 4892, 4, 22, 7, 1, -22 }, +/* 0xCE */ { 4903, 6, 22, 7, 0, -22 }, +/* 0xCF */ { 4920, 14, 21, 17, 2, -21 }, +/* 0xD0 */ { 4957, 15, 18, 17, 1, -18 }, +/* 0xD1 */ { 4991, 13, 22, 18, 2, -22 }, +/* 0xD2 */ { 5027, 13, 21, 18, 2, -21 }, +/* 0xD3 */ { 5062, 17, 22, 19, 1, -22 }, +/* 0xD4 */ { 5109, 17, 22, 19, 1, -22 }, +/* 0xD5 */ { 5156, 17, 22, 19, 1, -22 }, +/* 0xD6 */ { 5203, 17, 21, 19, 1, -21 }, +/* 0xD7 */ { 5248, 8, 9, 14, 3, -10 }, +/* 0xD8 */ { 5257, 14, 21, 17, 2, -21 }, +/* 0xD9 */ { 5294, 13, 24, 17, 2, -24 }, +/* 0xDA */ { 5333, 13, 22, 17, 2, -22 }, +/* 0xDB */ { 5369, 13, 22, 17, 2, -22 }, +/* 0xDC */ { 5405, 13, 21, 17, 2, -21 }, +/* 0xDD */ { 5440, 14, 22, 16, 1, -22 }, +/* 0xDE */ { 5479, 12, 23, 15, 1, -18 }, +/* 0xDF */ { 5514, 11, 18, 14, 2, -18 }, +/* 0xE0 */ { 5539, 6, 18, 8, 1, -18 }, +/* 0xE1 */ { 5553, 12, 18, 13, 1, -18 }, +/* 0xE2 */ { 5580, 12, 18, 13, 1, -18 }, +/* 0xE3 */ { 5607, 12, 18, 13, 1, -18 }, +/* 0xE4 */ { 5634, 12, 17, 13, 1, -17 }, +/* 0xE5 */ { 5660, 5, 22, 6, 0, -22 }, +/* 0xE6 */ { 5674, 10, 18, 12, 1, -18 }, +/* 0xE7 */ { 5697, 10, 18, 12, 1, -13 }, +/* 0xE8 */ { 5720, 10, 18, 12, 1, -18 }, +/* 0xE9 */ { 5743, 11, 18, 13, 1, -18 }, +/* 0xEA */ { 5768, 11, 18, 13, 1, -13 }, +/* 0xEB */ { 5793, 11, 17, 13, 1, -17 }, +/* 0xEC */ { 5817, 11, 18, 13, 1, -18 }, +/* 0xED */ { 5842, 5, 18, 5, 0, -18 }, +/* 0xEE */ { 5854, 6, 18, 6, 0, -18 }, +/* 0xEF */ { 5868, 16, 18, 18, 1, -18 }, +/* 0xF0 */ { 5904, 12, 18, 14, 1, -18 }, +/* 0xF1 */ { 5931, 10, 18, 13, 1, -18 }, +/* 0xF2 */ { 5954, 10, 18, 13, 1, -18 }, +/* 0xF3 */ { 5977, 11, 18, 13, 1, -18 }, +/* 0xF4 */ { 6002, 11, 18, 13, 1, -18 }, +/* 0xF5 */ { 6027, 11, 18, 13, 1, -18 }, +/* 0xF6 */ { 6052, 11, 17, 13, 1, -17 }, +/* 0xF7 */ { 6076, 12, 11, 14, 1, -11 }, +/* 0xF8 */ { 6093, 6, 18, 8, 1, -18 }, +/* 0xF9 */ { 6107, 10, 19, 13, 1, -19 }, +/* 0xFA */ { 6131, 10, 18, 13, 1, -18 }, +/* 0xFB */ { 6154, 10, 18, 13, 1, -18 }, +/* 0xFC */ { 6177, 10, 17, 13, 1, -17 }, +/* 0xFD */ { 6199, 11, 23, 11, 0, -18 }, +/* 0xFE */ { 6231, 6, 21, 7, 1, -16 }, +/* 0xFF */ { 6247, 2, 2, 8, 3, -17 }, +}; + +const GFXfont FreeSans12pt_Win1250 PROGMEM = { +(uint8_t*)FreeSans12pt_Win1250Bitmaps, +(GFXglyph*)FreeSans12pt_Win1250Glyphs, +0x01, 0xFF, 19 +}; diff --git a/src/graphics/niche/Fonts/FreeSans12pt_Win1251.h b/src/graphics/niche/Fonts/FreeSans12pt_Win1251.h new file mode 100644 index 000000000..d2972f836 --- /dev/null +++ b/src/graphics/niche/Fonts/FreeSans12pt_Win1251.h @@ -0,0 +1,527 @@ +// trunk-ignore-all(clang-format) +#pragma once +/* PROPERTIES + +FONT_NAME FreeSans12pt_Win1251 +*/ +const uint8_t FreeSans12pt_Win1251Bitmaps[] PROGMEM = { +/* 0x01 */ 0x00, 0x30, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x24, 0x00, 0x04, 0x80, 0x01, 0x90, 0x00, 0x62, 0x00, 0x30, 0xFE, 0x04, 0x10, 0x5F, 0x02, 0x0B, 0x00, 0x7F, 0xE0, 0x0C, 0x1C, 0x02, 0x83, 0x81, 0x9F, 0xF0, 0x02, 0x1E, 0x00, 0x41, 0xC0, 0x0E, 0x7F, 0x81, 0x78, 0x18, 0x62, 0x00, 0xFF, 0xC0, +/* 0x02 */ 0x00, 0xFF, 0x80, 0x61, 0x13, 0xF0, 0x62, 0x60, 0x07, 0xFC, 0x00, 0x83, 0x80, 0x10, 0xF0, 0x33, 0xF6, 0x01, 0x41, 0xC0, 0x18, 0x38, 0x03, 0xFF, 0xE0, 0x47, 0x02, 0x08, 0x20, 0x61, 0xC4, 0x06, 0x17, 0x00, 0x22, 0x00, 0x02, 0x40, 0x00, 0x48, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x3C, 0x00, +/* 0x03 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x04, 0x08, 0x48, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x08, 0x10, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA1, 0x81, 0x8D, 0x87, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x04 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x10, 0x02, 0x48, 0xE0, 0x61, 0xC1, 0xCC, 0x0E, 0x78, 0x1C, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xFC, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x05 */ 0x00, 0x18, 0x00, 0x00, 0x40, 0x01, 0x90, 0x01, 0xF4, 0x08, 0x12, 0x23, 0xC1, 0x91, 0x2C, 0x1C, 0x8A, 0xC3, 0x64, 0x64, 0x13, 0x22, 0x41, 0x98, 0x26, 0x2C, 0xC4, 0x22, 0x60, 0x42, 0x13, 0x04, 0x30, 0x80, 0x61, 0xA4, 0x02, 0x18, 0x20, 0x03, 0x41, 0x00, 0x20, 0x08, 0x02, 0x00, 0x60, 0x40, 0x03, 0xF8, +/* 0x06 */ 0x00, 0x10, 0x00, 0x03, 0x00, 0x1C, 0x48, 0x00, 0xB4, 0x80, 0x09, 0xF9, 0xC0, 0xE0, 0xE4, 0x0C, 0x02, 0x8F, 0x80, 0x38, 0x88, 0x01, 0x0D, 0x00, 0x18, 0x30, 0x01, 0x60, 0x80, 0x13, 0x18, 0x03, 0xF2, 0xC0, 0x20, 0x26, 0x06, 0x07, 0xFF, 0xA0, 0x02, 0x39, 0x00, 0x14, 0x70, 0x01, 0xC3, 0x00, 0x18, 0x00, +/* 0x07 */ +/* 0x08 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x10, 0x37, 0xE2, 0x61, 0x00, 0x0C, 0xC6, 0x10, 0x98, 0x0C, 0x63, 0x00, 0x00, 0xC6, 0x00, +/* 0x09 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x00, 0x37, 0xE0, +/* 0x0A */ +/* 0x0B */ 0x1F, 0x07, 0xC1, 0x86, 0x41, 0x10, 0x0C, 0x04, 0x80, 0x40, 0x18, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x00, 0x00, 0x30, 0x00, 0x01, 0x40, 0x00, 0x0A, 0x00, 0x00, 0x88, 0x00, 0x04, 0x40, 0x00, 0x41, 0x00, 0x02, 0x04, 0x00, 0x20, 0x20, 0x02, 0x00, 0x80, 0x20, 0x02, 0x02, 0x00, 0x08, 0x20, 0x00, 0x22, 0x00, 0x00, 0xE0, 0x00, +/* 0x0C */ 0x01, 0x00, 0x00, 0x38, 0x00, 0x04, 0xC0, 0x01, 0x08, 0x00, 0x18, 0x80, 0x1C, 0x10, 0x02, 0x07, 0x80, 0x81, 0x10, 0x1F, 0xC2, 0x02, 0x00, 0x60, 0x80, 0x1A, 0x20, 0x1C, 0x42, 0x1C, 0x08, 0xFE, 0x03, 0xA0, 0x01, 0x8C, 0x01, 0xC1, 0x43, 0xD0, 0x27, 0x81, 0xF8, +/* 0x0D */ +/* 0x0E */ 0x00, 0xE0, 0x00, 0x11, 0x00, 0x01, 0x10, 0x00, 0x0B, 0x00, 0x03, 0xF8, 0x00, 0x60, 0x60, 0x09, 0x02, 0x00, 0xA0, 0x10, 0x16, 0x01, 0x01, 0x40, 0x10, 0x10, 0x01, 0x01, 0x00, 0x08, 0x10, 0x00, 0x82, 0x1F, 0x08, 0x3F, 0x90, 0x44, 0x00, 0x06, 0xBF, 0xFF, 0xAF, 0xF0, 0xFF, 0xFF, 0x0F, 0xE3, 0xFB, 0xFC, +/* 0x0F */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x40, 0x12, 0x34, 0x00, 0x69, 0x40, 0x01, 0x49, 0xE0, 0xF1, 0xCD, 0x06, 0x8E, 0x28, 0x14, 0x71, 0x40, 0xA3, 0x8B, 0xFD, 0x14, 0x50, 0x68, 0xA2, 0x81, 0x4D, 0x97, 0xFA, 0x44, 0xBF, 0xD6, 0x31, 0x02, 0xE0, 0xC8, 0x16, 0x08, 0x61, 0x08, 0x21, 0xF0, 0x80, 0xF8, 0x78, 0x00, +/* 0x10 */ 0x00, 0xF0, 0x00, 0x3A, 0x00, 0x07, 0xC0, 0x00, 0xA8, 0x00, 0x1F, 0x00, 0x02, 0xB0, 0x00, 0x52, 0x00, 0x0A, 0x40, 0x02, 0x48, 0x00, 0x49, 0x00, 0x09, 0x30, 0x01, 0x22, 0x01, 0xC4, 0x70, 0xF0, 0x85, 0xE1, 0x10, 0x88, 0x37, 0x20, 0x03, 0x9C, 0x00, 0x37, 0x00, 0x06, 0x40, 0x01, 0x86, 0x00, +/* 0x11 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x60, 0x02, 0x36, 0x00, 0x09, 0x04, 0x0C, 0x48, 0x60, 0xC1, 0xC3, 0x0F, 0x0E, 0x00, 0x08, 0x70, 0x00, 0x23, 0x80, 0x63, 0x84, 0x01, 0x9F, 0x20, 0x0C, 0xFD, 0x80, 0x27, 0xE4, 0x03, 0x3F, 0x30, 0x33, 0xE0, 0xC0, 0x00, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x12 */ 0x00, 0xC2, 0x00, 0x1C, 0x24, 0x02, 0x18, 0x60, 0x64, 0x02, 0x02, 0x40, 0x20, 0x00, 0xF2, 0x03, 0x89, 0xE0, 0x7C, 0x80, 0x0E, 0x25, 0x80, 0xE1, 0x00, 0x1A, 0x08, 0x71, 0xB0, 0xC4, 0x39, 0x84, 0xC2, 0xCC, 0x40, 0x76, 0x7C, 0x05, 0xBB, 0x80, 0x4C, 0xE0, 0x0A, 0x78, 0x00, 0x9C, 0x00, 0x0F, 0x00, 0x00, +/* 0x13 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x60, 0xC1, 0xC6, 0xC9, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xF8, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x14 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x20, 0x22, 0x33, 0x01, 0x89, 0x20, 0x02, 0x48, 0x60, 0xE1, 0xC8, 0x80, 0x8E, 0x46, 0x46, 0x72, 0x32, 0x33, 0x9F, 0x9F, 0x94, 0x78, 0x78, 0xA0, 0x00, 0x0D, 0x80, 0x00, 0x44, 0x0E, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x15 */ 0x03, 0xFC, 0x20, 0x38, 0x1C, 0x81, 0x80, 0x1D, 0x08, 0x00, 0x32, 0x60, 0x00, 0x89, 0x00, 0x02, 0x18, 0x00, 0x08, 0x61, 0xC3, 0x22, 0x8D, 0x93, 0x72, 0x00, 0x00, 0x48, 0x00, 0x01, 0x20, 0x00, 0x04, 0x9F, 0xFF, 0x92, 0x60, 0x0E, 0x44, 0xFF, 0xF2, 0x11, 0xC3, 0x88, 0x21, 0xF8, 0x40, 0x40, 0x02, 0x00, 0xC0, 0x30, 0x00, 0xFF, 0x00, +/* 0x16 */ 0x03, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x01, 0xE0, 0x03, 0xF0, 0x03, 0xF0, 0x27, 0xF0, 0x6F, 0x70, 0x6E, 0x60, 0xFC, 0x60, 0xFC, 0x7E, 0xFC, 0x7E, 0xFC, 0x3F, 0xF4, 0x1F, 0xF4, 0x1F, 0xF0, 0x0E, 0x70, 0x0E, 0x30, 0x1C, 0x38, 0x38, 0x0F, 0xF0, +/* 0x17 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x00, 0x21, 0xC0, 0x02, 0x8E, 0x20, 0xF4, 0x70, 0x84, 0x11, 0x82, 0x40, 0x84, 0x01, 0x03, 0x20, 0x0F, 0x85, 0x80, 0x03, 0x04, 0x00, 0x04, 0x30, 0x78, 0x10, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x18 */ 0x00, 0xFC, 0x00, 0x02, 0x06, 0x00, 0x08, 0x24, 0x00, 0x21, 0xA4, 0x00, 0x4C, 0x48, 0x00, 0xA0, 0x50, 0x01, 0x92, 0x60, 0x03, 0x24, 0xC0, 0x06, 0x01, 0x81, 0x28, 0x03, 0x49, 0x6C, 0xC4, 0xAD, 0xD8, 0x16, 0xA4, 0xCC, 0xC4, 0x44, 0x86, 0x13, 0x05, 0x00, 0x28, 0x0A, 0x00, 0x50, 0x14, 0x00, 0x90, 0x48, 0x01, 0x20, 0x90, 0x02, 0x41, 0x20, 0x00, 0x00, +/* 0x19 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x49, 0xC3, 0x81, 0xC0, 0x00, 0x0E, 0x78, 0xF0, 0x77, 0xEF, 0xC3, 0xA7, 0x4E, 0x15, 0x0A, 0x10, 0xA7, 0x8F, 0x0D, 0x80, 0x00, 0x44, 0x00, 0x06, 0x33, 0xF0, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x1A */ 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0C, 0x3E, 0x18, 0x82, 0x32, 0x02, 0x64, 0x04, 0xC8, 0x09, 0x80, 0x23, 0x00, 0x86, 0x02, 0x0C, 0x08, 0x18, 0x10, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x81, 0x80, 0x03, 0x00, 0x07, 0xFF, 0xF8, +/* 0x1B */ 0x00, 0xFE, 0x00, 0x03, 0x81, 0x80, 0x04, 0x00, 0x60, 0x08, 0x00, 0x30, 0x10, 0x00, 0x10, 0x30, 0x07, 0x88, 0x23, 0xC8, 0x08, 0x22, 0x00, 0x04, 0x60, 0x00, 0x44, 0x60, 0x00, 0x84, 0x63, 0x03, 0x04, 0x61, 0xFC, 0x04, 0x6B, 0x00, 0x9E, 0xA5, 0x01, 0x6A, 0xD5, 0x01, 0x43, 0xA8, 0x81, 0x05, 0xD0, 0x82, 0x0A, 0xA0, 0x82, 0x05, 0xC0, 0x82, 0x02, 0x61, 0xFF, 0x0C, 0x1E, 0x00, 0xF0, +/* 0x1C */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x30, 0x02, 0x32, 0x00, 0x09, 0x00, 0x00, 0x48, 0x20, 0x61, 0xC3, 0x84, 0x0E, 0x1C, 0x78, 0x70, 0x40, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA0, 0x03, 0x0D, 0x83, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x1D */ 0x01, 0xFE, 0x00, 0x3A, 0x1C, 0x03, 0x00, 0x30, 0x23, 0x1E, 0xC3, 0x38, 0x03, 0x10, 0xC3, 0x09, 0x00, 0x18, 0x68, 0x00, 0xC1, 0x40, 0x00, 0x0A, 0x07, 0x80, 0x50, 0x46, 0x02, 0x80, 0x00, 0x1A, 0x1E, 0x00, 0xCB, 0x10, 0x0D, 0x03, 0x00, 0x48, 0x60, 0x06, 0x40, 0x00, 0x22, 0x0C, 0x02, 0x10, 0x60, 0x60, 0x43, 0xFC, 0x01, 0xE0, 0x00, 0x00, +/* 0x1E */ 0x01, 0xF0, 0x00, 0xEA, 0xC0, 0x31, 0x5F, 0x04, 0x5F, 0x88, 0x80, 0xA0, 0x48, 0x0E, 0x02, 0x8F, 0x40, 0x3C, 0x10, 0x21, 0x66, 0x87, 0x15, 0x98, 0x71, 0x41, 0x02, 0x14, 0x00, 0x01, 0x40, 0x00, 0x14, 0x00, 0x01, 0x21, 0xFE, 0x12, 0x00, 0x02, 0x10, 0x00, 0x60, 0x80, 0x0C, 0x06, 0x01, 0x80, 0x3F, 0xE0, +/* 0x1F */ 0x0E, 0x00, 0x13, 0x00, 0x23, 0x00, 0xF3, 0x01, 0x31, 0x01, 0x11, 0x03, 0xD3, 0x06, 0xF2, 0x30, 0x34, 0xC7, 0x25, 0x33, 0x2B, 0xC2, 0x57, 0x04, 0x3A, 0x08, 0x72, 0x30, 0xA3, 0xC3, 0x40, 0x04, 0x40, 0x18, 0x40, 0x60, 0x7F, 0x80, +/* ' ' 0x20 */ +/* '!' 0x21 */ 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, +/* '"' 0x22 */ 0xCF, 0x3C, 0xF3, 0x8A, 0x20, +/* '#' 0x23 */ 0x06, 0x30, 0x31, 0x03, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30, +/* '$' 0x24 */ 0x04, 0x03, 0xE1, 0xFF, 0x72, 0x7C, 0x47, 0x88, 0xF1, 0x07, 0xA0, 0x7E, 0x03, 0xF0, 0x17, 0x02, 0x7C, 0x47, 0x88, 0xF1, 0x1B, 0x26, 0x7F, 0xC3, 0xE0, 0x10, 0x02, 0x00, +/* '%' 0x25 */ 0x00, 0x06, 0x03, 0xC0, 0x40, 0x7E, 0x0C, 0x0E, 0x70, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xE7, 0x30, 0x07, 0xE6, 0x00, 0x3C, 0x40, 0x00, 0x0C, 0x7C, 0x00, 0x8F, 0xE0, 0x19, 0xC7, 0x01, 0x18, 0x30, 0x31, 0x83, 0x02, 0x1C, 0x70, 0x40, 0xFE, 0x04, 0x07, 0xC0, +/* '&' 0x26 */ 0x0F, 0x00, 0x7E, 0x03, 0x9C, 0x0C, 0x30, 0x30, 0xC0, 0xE7, 0x01, 0xF8, 0x03, 0x80, 0x3E, 0x01, 0xCC, 0x6E, 0x39, 0xB0, 0x7C, 0xC0, 0xF3, 0x03, 0xCE, 0x1F, 0x9F, 0xE6, 0x3E, 0x1C, +/* ''' 0x27 */ 0xFF, 0xA0, +/* '(' 0x28 */ 0x08, 0x8C, 0x46, 0x31, 0x98, 0xC6, 0x31, 0x8C, 0x63, 0x08, 0x63, 0x08, 0x61, 0x0C, 0x20, +/* ')' 0x29 */ 0x82, 0x18, 0xC3, 0x18, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x62, 0x31, 0x88, 0xC4, 0x62, 0x00, +/* '*' 0x2A */ 0x10, 0x23, 0x5B, 0xE3, 0x8D, 0x91, 0x00, +/* '+' 0x2B */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, +/* ',' 0x2C */ 0xF5, 0x60, +/* '-' 0x2D */ 0xFF, 0xF0, +/* '.' 0x2E */ 0xF0, +/* '/' 0x2F */ 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x00, +/* '0' 0x30 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x6C, 0x0F, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3E, 0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00, +/* '1' 0x31 */ 0x08, 0xCF, 0xFF, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, +/* '2' 0x32 */ 0x1F, 0x0F, 0xF9, 0x87, 0x60, 0x7C, 0x06, 0x00, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x06, 0x00, 0xC0, 0x30, 0x07, 0xFF, 0xFF, 0xE0, +/* '3' 0x33 */ 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0C, 0x01, 0x80, 0x60, 0x78, 0x0F, 0x80, 0x18, 0x01, 0x80, 0x3C, 0x07, 0x80, 0xD8, 0x73, 0xFC, 0x3F, 0x00, +/* '4' 0x34 */ 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0, 0xD8, 0x1B, 0x06, 0x61, 0x8C, 0x21, 0x8C, 0x33, 0x06, 0x7F, 0xFF, 0xFE, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, +/* '5' 0x35 */ 0x3F, 0xCF, 0xF9, 0x80, 0x30, 0x06, 0x00, 0xDE, 0x1F, 0xE7, 0x0E, 0x00, 0xE0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x81, 0xB8, 0x73, 0xFC, 0x1F, 0x00, +/* '6' 0x36 */ 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x74, 0x01, 0x80, 0x33, 0xC7, 0xFE, 0xF1, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x06, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00, +/* '7' 0x37 */ 0xFF, 0xFF, 0xFC, 0x01, 0x00, 0x60, 0x18, 0x02, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x04, 0x01, 0x80, 0x30, 0x06, 0x01, 0x80, 0x30, 0x00, +/* '8' 0x38 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x66, 0x0C, 0xC1, 0x8C, 0x61, 0xF8, 0x3F, 0x8E, 0x3B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xD8, 0x31, 0xFC, 0x1F, 0x00, +/* '9' 0x39 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C, 0x07, 0x80, 0xF0, 0x1E, 0x07, 0x61, 0xEF, 0xFC, 0x79, 0x80, 0x30, 0x05, 0xC1, 0x98, 0x73, 0xFC, 0x1E, 0x00, +/* ':' 0x3A */ 0xF0, 0x00, 0x03, 0xC0, +/* ';' 0x3B */ 0xF0, 0x00, 0x0F, 0x56, +/* '<' 0x3C */ 0x00, 0x70, 0x1E, 0x0F, 0x83, 0xC0, 0xF0, 0x0E, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x10, +/* '=' 0x3D */ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, +/* '>' 0x3E */ 0xE0, 0x07, 0x80, 0x1F, 0x00, 0x7C, 0x00, 0xF0, 0x07, 0x01, 0xE0, 0xF0, 0x3C, 0x0F, 0x00, 0x80, 0x00, +/* '?' 0x3F */ 0x3F, 0x1F, 0xEE, 0x1F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x03, 0x81, 0xC0, 0xE0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00, +/* '@' 0x40 */ 0x00, 0xFE, 0x00, 0x0F, 0xFE, 0x00, 0xF0, 0x3E, 0x07, 0x00, 0x3C, 0x38, 0x00, 0x38, 0xC1, 0xE0, 0x66, 0x0F, 0xD9, 0xD8, 0x61, 0xC3, 0xC3, 0x07, 0x0F, 0x1C, 0x1C, 0x3C, 0x60, 0x60, 0xF1, 0x81, 0x83, 0xC6, 0x06, 0x1B, 0x18, 0x38, 0xEE, 0x71, 0xE7, 0x18, 0xFD, 0xF8, 0x71, 0xE7, 0xC0, 0xE0, 0x00, 0x01, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x01, 0xFC, 0x00, +/* 'A' 0x41 */ 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 'B' 0x42 */ 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00, +/* 'C' 0x43 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0, +/* 'D' 0x44 */ 0xFF, 0xC3, 0xFF, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1B, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00, 0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3, 0xFE, 0x00, +/* 'E' 0x45 */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, +/* 'F' 0x46 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xDF, 0xFB, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00, +/* 'G' 0x47 */ 0x07, 0xF0, 0x1F, 0xFC, 0x3C, 0x1E, 0x70, 0x07, 0x60, 0x03, 0xE0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xC0, 0x03, 0xC0, 0x03, 0x60, 0x03, 0x60, 0x07, 0x30, 0x0F, 0x3C, 0x1F, 0x1F, 0xFB, 0x07, 0xE1, +/* 'H' 0x48 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0, +/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 'J' 0x4A */ 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00, +/* 'K' 0x4B */ 0xC0, 0x3E, 0x03, 0xB0, 0x39, 0x83, 0x8C, 0x38, 0x63, 0x83, 0x38, 0x19, 0xC0, 0xDE, 0x07, 0xB8, 0x38, 0xE1, 0x83, 0x0C, 0x1C, 0x60, 0x73, 0x01, 0x98, 0x0E, 0xC0, 0x3E, 0x00, 0xC0, +/* 'L' 0x4C */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0, +/* 'M' 0x4D */ 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83, +/* 'N' 0x4E */ 0xE0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE, 0x0F, 0x30, 0x79, 0xC3, 0xC6, 0x1E, 0x18, 0xF0, 0xE7, 0x83, 0x3C, 0x1D, 0xE0, 0x6F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, 0xC0, +/* 'O' 0x4F */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00, +/* 'P' 0x50 */ 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x06, 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, +/* 'Q' 0x51 */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x01, 0x98, 0xC0, 0xFC, 0x78, 0x3C, 0x1F, 0xFF, 0x03, 0xF9, 0x80, 0x00, 0x40, +/* 'R' 0x52 */ 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x70, +/* 'S' 0x53 */ 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80, +/* 'T' 0x54 */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, +/* 'U' 0x55 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x80, 0xEE, 0x0E, 0x3F, 0xE0, 0xFC, 0x00, +/* 'V' 0x56 */ 0xC0, 0x0F, 0x00, 0x7E, 0x01, 0x98, 0x06, 0x60, 0x39, 0xC0, 0xC3, 0x03, 0x0C, 0x1C, 0x38, 0x60, 0x61, 0x81, 0x8E, 0x07, 0x30, 0x0C, 0xC0, 0x37, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1C, 0x00, +/* 'W' 0x57 */ 0xE0, 0x30, 0x1D, 0x80, 0xE0, 0x76, 0x07, 0x81, 0xDC, 0x1E, 0x06, 0x70, 0x7C, 0x18, 0xC1, 0xB0, 0xE3, 0x0C, 0xC3, 0x8C, 0x33, 0x0C, 0x38, 0xC6, 0x30, 0x67, 0x18, 0xC1, 0x98, 0x67, 0x06, 0x61, 0xD8, 0x1D, 0x83, 0x60, 0x3C, 0x0D, 0x80, 0xF0, 0x3E, 0x03, 0xC0, 0x70, 0x0F, 0x01, 0xC0, 0x18, 0x07, 0x00, +/* 'X' 0x58 */ 0xE0, 0x1D, 0x80, 0xE7, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x00, 0xFC, 0x01, 0xE0, 0x07, 0x00, 0x1E, 0x00, 0xF8, 0x03, 0x30, 0x1C, 0xE0, 0xE1, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1B, 0x00, 0x70, +/* 'Y' 0x59 */ 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, +/* 'Z' 0x5A */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x03, 0x80, 0x18, 0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, +/* '[' 0x5B */ 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xF0, +/* '\' 0x5C */ 0x81, 0x81, 0x02, 0x06, 0x04, 0x08, 0x18, 0x10, 0x20, 0x60, 0x40, 0x81, 0x81, 0x02, 0x06, 0x04, +/* ']' 0x5D */ 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xF0, +/* '^' 0x5E */ 0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19, 0x8C, 0x83, 0xC1, 0x80, +/* '_' 0x5F */ 0xFF, 0xFE, +/* '`' 0x60 */ 0xE3, 0x8C, 0x30, +/* 'a' 0x61 */ 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70, +/* 'b' 0x62 */ 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0xF8, 0xDF, 0xCF, 0x0E, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xE0, 0x6F, 0x0E, 0xDF, 0xCC, 0xF8, +/* 'c' 0x63 */ 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x00, +/* 'd' 0x64 */ 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xCF, 0xFB, 0x8F, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8F, 0x3F, 0x63, 0xCC, +/* 'e' 0x65 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xC1, 0xF0, +/* 'f' 0x66 */ 0x3B, 0xD8, 0xC6, 0x7F, 0xEC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x00, +/* 'g' 0x67 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xB1, 0xE6, 0x00, 0xC0, 0x3E, 0x0E, 0x7F, 0xC7, 0xE0, +/* 'h' 0x68 */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30, +/* 'i' 0x69 */ 0xF0, 0x3F, 0xFF, 0xFF, 0xF0, +/* 'j' 0x6A */ 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0, +/* 'k' 0x6B */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0xB8, 0xC6, 0x31, 0xCC, 0x3B, 0x06, 0xC1, 0xF0, 0x30, +/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 'm' 0x6D */ 0xCF, 0x1F, 0x6F, 0xDF, 0xFC, 0x78, 0xFC, 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0F, 0x03, 0x07, 0x81, 0x83, 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x3C, 0x0C, 0x18, +/* 'n' 0x6E */ 0xCF, 0x37, 0xEF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0, +/* 'o' 0x6F */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0, +/* 'p' 0x70 */ 0xCF, 0x8D, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00, +/* 'q' 0x71 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xF1, 0xE6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, +/* 'r' 0x72 */ 0xCF, 0x7F, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, +/* 's' 0x73 */ 0x3E, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3F, 0x01, 0xF0, 0x3E, 0x1D, 0xFE, 0x3F, 0x00, +/* 't' 0x74 */ 0x63, 0x19, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0xE7, +/* 'u' 0x75 */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0, +/* 'v' 0x76 */ 0xE0, 0x6C, 0x0D, 0x81, 0xB8, 0x63, 0x0C, 0x61, 0x8E, 0x60, 0xCC, 0x19, 0x83, 0xE0, 0x3C, 0x07, 0x00, 0xE0, +/* 'w' 0x77 */ 0xC1, 0xC1, 0xB0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36, 0x31, 0x9B, 0x18, 0xCD, 0x98, 0x64, 0x6C, 0x16, 0x36, 0x0F, 0x1A, 0x07, 0x8F, 0x03, 0x83, 0x80, 0xC1, 0xC0, +/* 'x' 0x78 */ 0xC1, 0xF8, 0x66, 0x30, 0xCC, 0x3E, 0x07, 0x00, 0xC0, 0x78, 0x36, 0x0C, 0xC6, 0x3B, 0x06, 0xC0, 0xC0, +/* 'y' 0x79 */ 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0C, 0x60, 0xCC, 0x1B, 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, 0x00, +/* 'z' 0x7A */ 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0, +/* '{' 0x7B */ 0x19, 0xCC, 0x63, 0x18, 0xC6, 0x31, 0x99, 0x86, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x1C, 0x60, +/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, +/* '}' 0x7D */ 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x0C, 0x33, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x73, 0x00, +/* '~' 0x7E */ 0x70, 0x3E, 0x09, 0xE4, 0x1F, 0x03, 0x80, +/* 0x7F */ +/* 0x80 */ 0xFF, 0xE0, 0xFF, 0xE0, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0xFC, 0x07, 0xFE, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x1E, 0x00, 0x1C, +/* 0x81 */ 0x07, 0x01, 0xC0, 0x20, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x00, +/* 0x82 */ 0xF5, 0x80, +/* 0x83 */ 0x0C, 0x38, 0x61, 0x80, 0x1F, 0xFF, 0xE0, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x80, +/* 0x84 */ 0xCF, 0x34, 0x51, 0x88, +/* 0x85 */ 0xC6, 0x3C, 0x63, +/* 0x86 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x00, +/* 0x87 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x0F, 0xFF, 0xFF, 0x0C, 0x03, 0x00, 0xC0, 0x30, +/* 0x88 */ 0x01, 0xF0, 0x1F, 0xF0, 0xE0, 0xC7, 0x00, 0x18, 0x00, 0xC0, 0x07, 0xFF, 0x3F, 0xFC, 0x30, 0x01, 0xFF, 0x8F, 0xFC, 0x0C, 0x00, 0x18, 0x00, 0x70, 0x00, 0xE0, 0x81, 0xFE, 0x03, 0xF0, +/* 0x89 */ 0x38, 0x18, 0x00, 0xF8, 0x30, 0x03, 0x18, 0xC0, 0x04, 0x11, 0x80, 0x0C, 0x66, 0x00, 0x0F, 0x8C, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x40, 0x00, 0x01, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x31, 0xC0, 0xE0, 0x67, 0xC3, 0xC1, 0x98, 0xCC, 0xC3, 0x20, 0x90, 0x8C, 0x63, 0x33, 0x10, 0x7C, 0x3C, 0x60, 0x70, 0x38, +/* 0x8A */ 0x1F, 0xF8, 0x00, 0x3F, 0xF0, 0x00, 0x60, 0x60, 0x00, 0xC0, 0xC0, 0x01, 0x81, 0x80, 0x03, 0x03, 0x00, 0x06, 0x06, 0x00, 0x0C, 0x0C, 0x00, 0x18, 0x1F, 0xF0, 0x30, 0x3F, 0xF0, 0x60, 0x60, 0x30, 0xC0, 0xC0, 0x31, 0x81, 0x80, 0x66, 0x03, 0x00, 0xCC, 0x06, 0x01, 0xB8, 0x0C, 0x06, 0xE0, 0x1F, 0xFD, 0x80, 0x3F, 0xE0, +/* 0x8B */ 0x2F, 0x49, 0x99, +/* 0x8C */ 0xC0, 0x60, 0x06, 0x03, 0x00, 0x30, 0x18, 0x01, 0x80, 0xC0, 0x0C, 0x06, 0x00, 0x60, 0x30, 0x03, 0x01, 0x80, 0x18, 0x0C, 0x00, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x30, 0x18, 0x1D, 0x80, 0xC0, 0x3C, 0x06, 0x01, 0xE0, 0x30, 0x0F, 0x01, 0x80, 0x78, 0x0C, 0x06, 0xC0, 0x7F, 0xF6, 0x03, 0xFE, 0x00, +/* 0x8D */ 0x03, 0x00, 0x60, 0x0C, 0x00, 0x00, 0xC0, 0x7C, 0x0E, 0xC1, 0xCC, 0x38, 0xC7, 0x0C, 0xE0, 0xDC, 0x0F, 0x80, 0xF0, 0x0F, 0x80, 0xDC, 0x0C, 0xE0, 0xC7, 0x0C, 0x30, 0xC3, 0x8C, 0x1C, 0xC0, 0xEC, 0x07, +/* 0x8E */ 0xFF, 0xE0, 0xFF, 0xE0, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0xFC, 0x07, 0xFE, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, +/* 0x8F */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, 0xC0, 0x06, 0x00, 0x30, 0x00, +/* 0x90 */ 0x30, 0x0F, 0xF0, 0xFF, 0x03, 0x00, 0x30, 0x03, 0x3C, 0x37, 0xE3, 0xC7, 0x38, 0x33, 0x03, 0x30, 0x33, 0x03, 0x30, 0x33, 0x03, 0x30, 0x33, 0x03, 0x30, 0x33, 0x03, 0x00, 0x60, 0x06, 0x01, 0x80, 0x30, +/* 0x91 */ 0x6A, 0xF0, +/* 0x92 */ 0xF5, 0x60, +/* 0x93 */ 0x4E, 0x28, 0xA2, 0xCF, 0x30, +/* 0x94 */ 0xCF, 0x34, 0x51, 0x4E, 0x20, +/* 0x95 */ 0x7B, 0xFF, 0xFF, 0xFD, 0xE0, +/* 0x96 */ 0xFF, 0xFF, 0xF0, +/* 0x97 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 0x98 */ +/* 0x99 */ 0xFF, 0x70, 0x1F, 0xFD, 0xC0, 0x71, 0x87, 0x83, 0xC6, 0x1E, 0x0F, 0x18, 0x68, 0x3C, 0x61, 0xB1, 0xB1, 0x86, 0xC6, 0xC6, 0x19, 0x1B, 0x18, 0x66, 0xCC, 0x61, 0x9B, 0x31, 0x86, 0x3C, 0xC6, 0x18, 0xE3, 0x18, 0x63, 0x8C, +/* 0x9A */ 0x7F, 0x80, 0x3F, 0xC0, 0x18, 0x60, 0x0C, 0x30, 0x06, 0x18, 0x03, 0x0F, 0xF1, 0x87, 0xFC, 0xC3, 0x07, 0x61, 0x81, 0xB0, 0xC0, 0xD0, 0x60, 0xF8, 0x3F, 0xEC, 0x1F, 0xE0, +/* 0x9B */ 0x99, 0x92, 0xF4, +/* 0x9C */ 0xC0, 0xC0, 0x30, 0x30, 0x0C, 0x0C, 0x03, 0x03, 0x00, 0xC0, 0xC0, 0x3F, 0xFF, 0xCF, 0xFF, 0xFB, 0x03, 0x07, 0xC0, 0xC0, 0xF0, 0x30, 0x3C, 0x0C, 0x1F, 0x03, 0xFE, 0xC0, 0xFF, 0x00, +/* 0x9D */ 0x07, 0x07, 0x03, 0x03, 0x00, 0x06, 0x0F, 0x0D, 0x8C, 0xCC, 0x6C, 0x3C, 0x1E, 0x0F, 0x86, 0xE3, 0x39, 0x8E, 0xC3, 0xE0, 0xC0, +/* 0x9E */ 0x30, 0x1F, 0xE3, 0xFC, 0x18, 0x03, 0x00, 0x67, 0x0D, 0xF9, 0xC7, 0x38, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x60, 0xCC, 0x19, 0x83, 0x30, 0x66, 0x0C, +/* 0x9F */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0xFF, 0xFF, 0xC1, 0x80, 0x60, +/* 0xA0 */ +/* 0xA1 */ 0x10, 0x40, 0xC4, 0x07, 0xE0, 0x1E, 0x0C, 0x01, 0xF0, 0x1D, 0x80, 0xCE, 0x0E, 0x30, 0x61, 0xC7, 0x06, 0x30, 0x3B, 0x81, 0xD8, 0x07, 0xC0, 0x3C, 0x00, 0xE0, 0x06, 0x00, 0x70, 0x03, 0x80, 0x38, 0x01, 0xC0, 0x1C, 0x00, +/* 0xA2 */ 0x21, 0x86, 0x20, 0xFC, 0x07, 0x0E, 0x06, 0xC0, 0xD8, 0x33, 0x86, 0x30, 0xC6, 0x30, 0xC6, 0x0C, 0xC1, 0xB0, 0x36, 0x03, 0xC0, 0x70, 0x0E, 0x01, 0x80, 0x30, 0x0E, 0x07, 0x80, 0xE0, 0x00, +/* 0xA3 */ 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00, +/* 0xA4 */ 0xDD, 0xFF, 0xD8, 0xD8, 0x3C, 0x1E, 0x0F, 0x8D, 0xFF, 0xDD, 0x80, +/* 0xA5 */ 0x00, 0x60, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x00, +/* 0xA6 */ 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC, +/* 0xA7 */ 0x0F, 0x03, 0xF0, 0xE7, 0x18, 0x63, 0x0C, 0x70, 0x07, 0x03, 0xF8, 0xC3, 0x98, 0x3B, 0x03, 0xF0, 0x37, 0x06, 0x78, 0xC7, 0xB0, 0x7C, 0x03, 0x80, 0x39, 0x83, 0x30, 0x67, 0x1C, 0x7F, 0x07, 0xC0, +/* 0xA8 */ 0x19, 0x81, 0x98, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFE, 0xFF, 0xEC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0, +/* 0xA9 */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xE3, 0x1C, 0x73, 0xF3, 0x99, 0x86, 0x6C, 0xC1, 0x8F, 0x30, 0x03, 0xCC, 0x00, 0xF3, 0x00, 0x3C, 0xC1, 0x8D, 0x98, 0x66, 0x77, 0xF3, 0x8E, 0x79, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00, +/* 0xAA */ 0x07, 0xE0, 0x3F, 0xF0, 0xF0, 0x71, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xFF, 0xE1, 0xFF, 0xC3, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1C, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0, +/* 0xAB */ 0x21, 0x63, 0xE7, 0x84, 0x84, 0xE7, 0x63, 0x21, +/* 0xAC */ 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, +/* 0xAD */ 0xFF, 0xF0, +/* 0xAE */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xFF, 0x1C, 0x7F, 0xF3, 0x9B, 0x04, 0x6C, 0xC1, 0x8F, 0x30, 0x43, 0xCF, 0xF0, 0xF3, 0xFC, 0x3C, 0xC1, 0x0D, 0xB0, 0x66, 0x7C, 0x1B, 0x8F, 0x07, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00, +/* 0xAF */ 0xC7, 0x8C, 0x01, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x00, +/* 0xB0 */ 0x38, 0xFB, 0x1C, 0x18, 0x38, 0xDF, 0x1C, +/* 0xB1 */ 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x7F, 0xE7, 0xFE, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, +/* 0xB2 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 0xB3 */ 0xF0, 0x3F, 0xFF, 0xFF, 0xF0, +/* 0xB4 */ 0x03, 0x03, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, +/* 0xB5 */ 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x1C, 0xE3, 0xCF, 0xEF, 0xFC, 0x7C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00, +/* 0xB6 */ 0x1F, 0xE7, 0xFD, 0xF3, 0x7E, 0x6F, 0xCD, 0xF9, 0xBF, 0x37, 0xE6, 0x7C, 0xCF, 0x98, 0xF3, 0x06, 0x60, 0xCC, 0x19, 0x83, 0x30, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x60, 0xCC, +/* 0xB7 */ 0xF0, +/* 0xB8 */ 0x19, 0x83, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x03, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x0C, 0x01, 0xC0, 0xDC, 0x31, 0xFC, 0x1F, 0x00, +/* 0xB9 */ 0xC0, 0xC0, 0x18, 0x18, 0x03, 0x83, 0x00, 0x70, 0x60, 0x0B, 0x0C, 0x01, 0x61, 0x8F, 0xA6, 0x33, 0x1C, 0xC6, 0x41, 0x88, 0xC8, 0x31, 0x99, 0x06, 0x13, 0x20, 0xC3, 0x66, 0x38, 0x6C, 0xEF, 0x07, 0x8F, 0xA0, 0xF0, 0x04, 0x0E, 0x00, 0x81, 0xC7, 0xF0, 0x18, 0xFC, +/* 0xBA */ 0x1F, 0x87, 0xF9, 0xC3, 0x30, 0x3E, 0x01, 0xFE, 0x3F, 0xC6, 0x00, 0xE0, 0x0C, 0x0D, 0xC3, 0x9F, 0xE1, 0xF8, +/* 0xBB */ 0x88, 0xC6, 0xE7, 0x21, 0x21, 0xE7, 0xC6, 0x88, +/* 0xBC */ 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0, +/* 0xBD */ 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80, +/* 0xBE */ 0x3E, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3F, 0x00, 0xF0, 0x3E, 0x1D, 0xFE, 0x3E, 0x00, +/* 0xBF */ 0xCF, 0x30, 0x00, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, +/* 0xC0 */ 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 0xC1 */ 0xFF, 0xE7, 0xFF, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x1F, 0xF8, 0xFF, 0xF6, 0x01, 0xB0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00, +/* 0xC2 */ 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00, +/* 0xC3 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00, +/* 0xC4 */ 0x07, 0xFE, 0x01, 0xFF, 0x80, 0x60, 0x60, 0x18, 0x18, 0x06, 0x06, 0x01, 0x81, 0x80, 0x60, 0x60, 0x18, 0x18, 0x06, 0x06, 0x01, 0x81, 0x80, 0x60, 0x60, 0x18, 0x18, 0x0E, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x70, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0x00, 0x03, 0xC0, 0x00, 0xC0, +/* 0xC5 */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, +/* 0xC6 */ 0x70, 0x60, 0xE3, 0x06, 0x0C, 0x38, 0x61, 0xC1, 0xC6, 0x38, 0x0E, 0x67, 0x00, 0x66, 0x60, 0x03, 0x6C, 0x00, 0x3F, 0xC0, 0x01, 0xF8, 0x00, 0x1F, 0x80, 0x03, 0xFC, 0x00, 0x76, 0xE0, 0x0E, 0x67, 0x01, 0xC6, 0x38, 0x38, 0x61, 0xC3, 0x06, 0x0C, 0x60, 0x60, 0xEE, 0x06, 0x07, +/* 0xC7 */ 0x0F, 0x81, 0xFF, 0x0C, 0x18, 0xC0, 0x66, 0x03, 0x00, 0x18, 0x01, 0xC0, 0x1C, 0x07, 0xC0, 0x3F, 0x00, 0x1C, 0x00, 0x7C, 0x01, 0xE0, 0x0F, 0x80, 0x6E, 0x0E, 0x3F, 0xE0, 0x7E, 0x00, +/* 0xC8 */ 0xC0, 0x3E, 0x01, 0xF0, 0x1F, 0x80, 0xFC, 0x0D, 0xE0, 0xEF, 0x06, 0x78, 0x73, 0xC3, 0x1E, 0x30, 0xF1, 0x87, 0x98, 0x3D, 0xC1, 0xEC, 0x0F, 0xC0, 0x7E, 0x03, 0xE0, 0x1F, 0x00, 0xC0, +/* 0xC9 */ 0x10, 0x40, 0xC4, 0x07, 0xE0, 0x1E, 0x0C, 0x03, 0xE0, 0x1F, 0x01, 0xF8, 0x0F, 0xC0, 0xDE, 0x0E, 0xF0, 0x67, 0x87, 0x3C, 0x31, 0xE3, 0x0F, 0x18, 0x79, 0x83, 0xDC, 0x1E, 0xC0, 0xFC, 0x07, 0xE0, 0x3E, 0x01, 0xF0, 0x0C, +/* 0xCA */ 0xC0, 0x7C, 0x0E, 0xC1, 0xCC, 0x38, 0xC7, 0x0C, 0xE0, 0xDC, 0x0F, 0x80, 0xF0, 0x0F, 0x80, 0xDC, 0x0C, 0xE0, 0xC7, 0x0C, 0x30, 0xC3, 0x8C, 0x1C, 0xC0, 0xEC, 0x07, +/* 0xCB */ 0x1F, 0xFC, 0x7F, 0xF1, 0x80, 0xC6, 0x03, 0x18, 0x0C, 0x60, 0x31, 0x80, 0xC6, 0x03, 0x18, 0x0C, 0x60, 0x31, 0x80, 0xC6, 0x03, 0x18, 0x0C, 0xC0, 0x33, 0x00, 0xDC, 0x03, 0xE0, 0x0F, 0x00, 0x30, +/* 0xCC */ 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83, +/* 0xCD */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0, +/* 0xCE */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00, +/* 0xCF */ 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0, +/* 0xD0 */ 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x06, 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, +/* 0xD1 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0, +/* 0xD2 */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, +/* 0xD3 */ 0xC0, 0x1F, 0x01, 0xD8, 0x0C, 0xE0, 0xE3, 0x06, 0x1C, 0x70, 0x63, 0x03, 0xB8, 0x1D, 0x80, 0x7C, 0x03, 0xC0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x38, 0x03, 0x80, 0x1C, 0x01, 0xC0, 0x00, +/* 0xD4 */ 0x00, 0xC0, 0x00, 0x30, 0x00, 0xFF, 0xC0, 0xFF, 0xFC, 0x78, 0xC7, 0x98, 0x30, 0x6E, 0x0C, 0x1F, 0x03, 0x03, 0xC0, 0xC0, 0xF0, 0x30, 0x3C, 0x0C, 0x0F, 0x83, 0x07, 0x60, 0xC1, 0x9E, 0x31, 0xE3, 0xFF, 0xF0, 0x3F, 0xF0, 0x00, 0xC0, 0x00, 0x30, 0x00, +/* 0xD5 */ 0xE0, 0x1D, 0x80, 0xE7, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x00, 0xFC, 0x01, 0xE0, 0x07, 0x00, 0x1E, 0x00, 0xF8, 0x03, 0x30, 0x1C, 0xE0, 0xE1, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1B, 0x00, 0x70, +/* 0xD6 */ 0xC0, 0x19, 0x80, 0x33, 0x00, 0x66, 0x00, 0xCC, 0x01, 0x98, 0x03, 0x30, 0x06, 0x60, 0x0C, 0xC0, 0x19, 0x80, 0x33, 0x00, 0x66, 0x00, 0xCC, 0x01, 0x98, 0x03, 0x30, 0x06, 0x60, 0x0C, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, +/* 0xD7 */ 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x37, 0xFF, 0x3F, 0xF0, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, +/* 0xD8 */ 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, +/* 0xD9 */ 0xC1, 0x83, 0x30, 0x60, 0xCC, 0x18, 0x33, 0x06, 0x0C, 0xC1, 0x83, 0x30, 0x60, 0xCC, 0x18, 0x33, 0x06, 0x0C, 0xC1, 0x83, 0x30, 0x60, 0xCC, 0x18, 0x33, 0x06, 0x0C, 0xC1, 0x83, 0x30, 0x60, 0xCC, 0x18, 0x33, 0x06, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0C, 0x00, 0x03, 0x00, 0x00, 0xC0, +/* 0xDA */ 0xFE, 0x00, 0x3F, 0x80, 0x00, 0x60, 0x00, 0x18, 0x00, 0x06, 0x00, 0x01, 0x80, 0x00, 0x60, 0x00, 0x1F, 0xF8, 0x07, 0xFF, 0x01, 0x80, 0xE0, 0x60, 0x1C, 0x18, 0x03, 0x06, 0x00, 0xC1, 0x80, 0x30, 0x60, 0x1C, 0x18, 0x0E, 0x07, 0xFF, 0x01, 0xFF, 0x80, +/* 0xDB */ 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0xFF, 0x83, 0xFF, 0xE1, 0xE0, 0x38, 0xF0, 0x0E, 0x78, 0x03, 0x3C, 0x01, 0x9E, 0x00, 0xCF, 0x00, 0xE7, 0x80, 0xE3, 0xFF, 0xE1, 0xFF, 0xE0, 0xC0, +/* 0xDC */ 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x1F, 0xF8, 0xFF, 0xE6, 0x03, 0xB0, 0x0F, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0xF8, 0x0E, 0xFF, 0xE7, 0xFE, 0x00, +/* 0xDD */ 0x0F, 0xC0, 0x7F, 0xE1, 0xC1, 0xE7, 0x01, 0xCC, 0x01, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x1F, 0xFE, 0x3F, 0xFC, 0x00, 0x38, 0x00, 0x7C, 0x00, 0xD8, 0x03, 0x98, 0x0E, 0x38, 0x3C, 0x3F, 0xF0, 0x1F, 0x80, +/* 0xDE */ 0xC0, 0x3F, 0x06, 0x07, 0xFE, 0x30, 0x70, 0x39, 0x87, 0x00, 0xEC, 0x30, 0x03, 0x61, 0x80, 0x1B, 0x18, 0x00, 0x78, 0xC0, 0x03, 0xFE, 0x00, 0x1F, 0xF0, 0x00, 0xF1, 0x80, 0x07, 0x8C, 0x00, 0x3C, 0x70, 0x03, 0xE1, 0x80, 0x1B, 0x0E, 0x01, 0xD8, 0x38, 0x1C, 0xC0, 0xFF, 0xC6, 0x01, 0xF8, 0x00, +/* 0xDF */ 0x0F, 0xFC, 0xFF, 0xF3, 0x00, 0xD8, 0x03, 0x60, 0x0D, 0x80, 0x36, 0x00, 0xCC, 0x03, 0x3F, 0xFC, 0x3F, 0xF0, 0x38, 0xC1, 0xC3, 0x0E, 0x0C, 0x70, 0x31, 0x80, 0xCE, 0x03, 0x70, 0x0F, 0x80, 0x30, +/* 0xE0 */ 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70, +/* 0xE1 */ 0x00, 0xC0, 0x38, 0x3F, 0x1F, 0x87, 0x00, 0xC0, 0x17, 0xC7, 0xFC, 0xF1, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1F, 0x07, 0x71, 0xC7, 0xF0, 0x7C, 0x00, +/* 0xE2 */ 0xFE, 0x3F, 0xEC, 0x3B, 0x06, 0xC1, 0xB0, 0xEF, 0xF3, 0x0E, 0xC0, 0xF0, 0x3C, 0x1F, 0xFE, 0xFF, 0x00, +/* 0xE3 */ 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x00, +/* 0xE4 */ 0x0F, 0xF0, 0x3F, 0xC0, 0xC3, 0x03, 0x0C, 0x0C, 0x30, 0x30, 0xC0, 0xC3, 0x03, 0x0C, 0x1C, 0x30, 0x60, 0xC1, 0x83, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x3C, 0x00, 0xC0, +/* 0xE5 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xC1, 0xF0, +/* 0xE6 */ 0xE1, 0x87, 0x71, 0x8E, 0x39, 0x9C, 0x1D, 0xB8, 0x0F, 0xF0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xF0, 0x1D, 0xB8, 0x39, 0x9C, 0x71, 0x8E, 0xE1, 0x87, 0xC1, 0x83, +/* 0xE7 */ 0x3E, 0x7F, 0xB0, 0xE0, 0x30, 0x18, 0x78, 0x3C, 0x07, 0x01, 0xE0, 0xF8, 0xEF, 0xE3, 0xE0, +/* 0xE8 */ 0xC0, 0xF8, 0x3F, 0x07, 0xE1, 0xFC, 0x37, 0x8C, 0xF3, 0x9E, 0x63, 0xD8, 0x7F, 0x0F, 0xC1, 0xF8, 0x3E, 0x06, +/* 0xE9 */ 0x21, 0x86, 0x20, 0xFC, 0x0F, 0x0C, 0x0F, 0x83, 0xF0, 0x7E, 0x1F, 0xC3, 0x78, 0xCF, 0x39, 0xE6, 0x3D, 0x87, 0xF0, 0xFC, 0x1F, 0x83, 0xE0, 0x60, +/* 0xEA */ 0xC1, 0xE1, 0xB1, 0x99, 0x8D, 0x87, 0x83, 0xC1, 0xF0, 0xDC, 0x67, 0x31, 0xD8, 0x7C, 0x18, +/* 0xEB */ 0x3F, 0xCF, 0xF3, 0x0C, 0xC3, 0x30, 0xCC, 0x33, 0x0C, 0xC3, 0x30, 0xDC, 0x36, 0x0F, 0x83, 0xC0, 0xC0, +/* 0xEC */ 0xE0, 0x7E, 0x07, 0xF0, 0xFF, 0x0F, 0xF0, 0xFD, 0x9B, 0xD9, 0xBD, 0xFB, 0xCF, 0x3C, 0xF3, 0xC6, 0x3C, 0x63, 0xC0, 0x30, +/* 0xED */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0, +/* 0xEE */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0, +/* 0xEF */ 0xFF, 0xFF, 0xFC, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0, +/* 0xF0 */ 0xCF, 0x8D, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00, +/* 0xF1 */ 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x00, +/* 0xF2 */ 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +/* 0xF3 */ 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0C, 0x60, 0xCC, 0x1B, 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, 0x00, +/* 0xF4 */ 0x00, 0xC0, 0x00, 0x18, 0x00, 0x03, 0x00, 0x0F, 0x67, 0x87, 0xFD, 0xF8, 0xC3, 0xE3, 0xB8, 0x78, 0x3E, 0x06, 0x03, 0xC0, 0xC0, 0x78, 0x18, 0x0F, 0x03, 0x01, 0xE0, 0x60, 0x3E, 0x1E, 0x0E, 0xC3, 0xE3, 0x9F, 0xFF, 0xE1, 0xF6, 0x78, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x60, 0x00, +/* 0xF5 */ 0xC1, 0xF8, 0x66, 0x30, 0xCC, 0x3E, 0x07, 0x00, 0xC0, 0x78, 0x36, 0x0C, 0xC6, 0x3B, 0x06, 0xC0, 0xC0, +/* 0xF6 */ 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCF, 0xFF, 0xFF, 0xF0, 0x03, 0x00, 0x30, +/* 0xF7 */ 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0xFE, 0xFF, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, +/* 0xF8 */ 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xFF, 0xFF, 0xFF, 0xFC, +/* 0xF9 */ 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0x00, 0x03, +/* 0xFA */ 0xFC, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0x00, 0x0F, 0xF0, 0x3F, 0xE0, 0xC1, 0xC3, 0x03, 0x0C, 0x0C, 0x30, 0x30, 0xC1, 0xC3, 0xFE, 0x0F, 0xF0, +/* 0xFB */ 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xFF, 0x0F, 0xFE, 0x3C, 0x1C, 0xF0, 0x33, 0xC0, 0xCF, 0x03, 0x3C, 0x1C, 0xFF, 0xE3, 0xFF, 0x0C, +/* 0xFC */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0x3F, 0xEC, 0x1F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0xFE, 0xFF, 0x00, +/* 0xFD */ 0x3F, 0x0F, 0xF1, 0x87, 0x60, 0x60, 0x0E, 0x3F, 0xC7, 0xF8, 0x03, 0x00, 0xD8, 0x1B, 0x87, 0x3F, 0xC3, 0xF0, +/* 0xFE */ 0xC0, 0xF8, 0xC1, 0xFC, 0xC3, 0x8E, 0xC7, 0x07, 0xC6, 0x03, 0xFE, 0x03, 0xFE, 0x03, 0xC6, 0x03, 0xC6, 0x03, 0xC7, 0x06, 0xC3, 0x8E, 0xC1, 0xFC, 0xC0, 0xF8, +/* 0xFF */ 0x1F, 0xCF, 0xF7, 0x0D, 0x83, 0x60, 0xD8, 0x33, 0xFC, 0x7F, 0x0C, 0xC6, 0x33, 0x0D, 0x83, 0xC0, 0xC0, +}; + +const GFXglyph FreeSans12pt_Win1251Glyphs[] PROGMEM = { +/* 0x01 */ { 0, 19, 20, 21, 1, -17 }, +/* 0x02 */ { 48, 19, 20, 21, 1, -17 }, +/* 0x03 */ { 96, 21, 20, 23, 1, -17 }, +/* 0x04 */ { 149, 21, 20, 23, 1, -17 }, +/* 0x05 */ { 202, 20, 20, 22, 1, -17 }, +/* 0x06 */ { 252, 20, 20, 22, 1, -17 }, +/* 0x07 */ { 302, 0, 0, 8, 0, 0 }, +/* 0x08 */ { 302, 23, 20, 25, 1, -17 }, +/* 0x09 */ { 360, 23, 16, 25, 1, -16 }, +/* 0x0A */ { 406, 0, 0, 8, 0, 0 }, +/* 0x0B */ { 406, 21, 20, 23, 1, -17 }, +/* 0x0C */ { 459, 19, 18, 21, 1, -15 }, +/* 0x0D */ { 502, 0, 0, 8, 0, 0 }, +/* 0x0E */ { 502, 20, 20, 22, 1, -17 }, +/* 0x0F */ { 552, 21, 21, 23, 1, -18 }, +/* 0x10 */ { 608, 19, 20, 21, 1, -17 }, +/* 0x11 */ { 656, 21, 20, 23, 1, -17 }, +/* 0x12 */ { 709, 20, 20, 22, 1, -17 }, +/* 0x13 */ { 759, 21, 20, 23, 1, -17 }, +/* 0x14 */ { 812, 21, 20, 23, 1, -17 }, +/* 0x15 */ { 865, 22, 20, 24, 1, -17 }, +/* 0x16 */ { 920, 16, 20, 18, 1, -17 }, +/* 0x17 */ { 960, 21, 20, 23, 1, -17 }, +/* 0x18 */ { 1013, 23, 20, 25, 1, -17 }, +/* 0x19 */ { 1071, 21, 20, 23, 1, -17 }, +/* 0x1A */ { 1124, 15, 19, 17, 1, -16 }, +/* 0x1B */ { 1160, 24, 21, 26, 1, -18 }, +/* 0x1C */ { 1223, 21, 20, 23, 1, -17 }, +/* 0x1D */ { 1276, 21, 21, 23, 1, -18 }, +/* 0x1E */ { 1332, 20, 20, 22, 1, -17 }, +/* 0x1F */ { 1382, 15, 20, 17, 1, -17 }, +/* ' ' 0x20 */ { 1420, 0, 0, 6, 0, 0 }, +/* '!' 0x21 */ { 1420, 2, 18, 8, 3, -16 }, +/* '"' 0x22 */ { 1425, 6, 6, 8, 1, -15 }, +/* '#' 0x23 */ { 1430, 13, 16, 13, 0, -14 }, +/* '$' 0x24 */ { 1456, 11, 20, 13, 1, -16 }, +/* '%' 0x25 */ { 1484, 20, 17, 21, 1, -15 }, +/* '&' 0x26 */ { 1527, 14, 17, 16, 1, -15 }, +/* ''' 0x27 */ { 1557, 2, 6, 5, 1, -15 }, +/* '(' 0x28 */ { 1559, 5, 23, 8, 2, -16 }, +/* ')' 0x29 */ { 1574, 5, 23, 8, 1, -16 }, +/* '*' 0x2A */ { 1589, 7, 7, 9, 1, -16 }, +/* '+' 0x2B */ { 1596, 10, 11, 14, 2, -9 }, +/* ',' 0x2C */ { 1610, 2, 6, 7, 2, 0 }, +/* '-' 0x2D */ { 1612, 6, 2, 8, 1, -6 }, +/* '.' 0x2E */ { 1614, 2, 2, 6, 2, 0 }, +/* '/' 0x2F */ { 1615, 7, 18, 7, 0, -16 }, +/* '0' 0x30 */ { 1631, 11, 17, 13, 1, -15 }, +/* '1' 0x31 */ { 1655, 5, 17, 13, 3, -15 }, +/* '2' 0x32 */ { 1666, 11, 17, 13, 1, -15 }, +/* '3' 0x33 */ { 1690, 11, 17, 13, 1, -15 }, +/* '4' 0x34 */ { 1714, 11, 17, 13, 1, -15 }, +/* '5' 0x35 */ { 1738, 11, 17, 13, 1, -15 }, +/* '6' 0x36 */ { 1762, 11, 17, 13, 1, -15 }, +/* '7' 0x37 */ { 1786, 11, 17, 13, 1, -15 }, +/* '8' 0x38 */ { 1810, 11, 17, 13, 1, -15 }, +/* '9' 0x39 */ { 1834, 11, 17, 13, 1, -15 }, +/* ':' 0x3A */ { 1858, 2, 13, 6, 2, -11 }, +/* ';' 0x3B */ { 1862, 2, 16, 6, 2, -10 }, +/* '<' 0x3C */ { 1866, 12, 11, 14, 1, -9 }, +/* '=' 0x3D */ { 1883, 12, 6, 14, 1, -7 }, +/* '>' 0x3E */ { 1892, 12, 11, 14, 1, -9 }, +/* '?' 0x3F */ { 1909, 10, 18, 13, 2, -16 }, +/* '@' 0x40 */ { 1932, 22, 21, 24, 1, -16 }, +/* 'A' 0x41 */ { 1990, 14, 18, 16, 1, -16 }, +/* 'B' 0x42 */ { 2022, 13, 18, 16, 2, -16 }, +/* 'C' 0x43 */ { 2052, 15, 18, 17, 1, -16 }, +/* 'D' 0x44 */ { 2086, 14, 18, 17, 2, -16 }, +/* 'E' 0x45 */ { 2118, 12, 18, 15, 2, -16 }, +/* 'F' 0x46 */ { 2145, 11, 18, 14, 2, -16 }, +/* 'G' 0x47 */ { 2170, 16, 18, 18, 1, -16 }, +/* 'H' 0x48 */ { 2206, 13, 18, 17, 2, -16 }, +/* 'I' 0x49 */ { 2236, 2, 18, 7, 2, -16 }, +/* 'J' 0x4A */ { 2241, 9, 18, 13, 1, -16 }, +/* 'K' 0x4B */ { 2262, 13, 18, 16, 2, -16 }, +/* 'L' 0x4C */ { 2292, 10, 18, 14, 2, -16 }, +/* 'M' 0x4D */ { 2315, 16, 18, 20, 2, -16 }, +/* 'N' 0x4E */ { 2351, 13, 18, 18, 2, -16 }, +/* 'O' 0x4F */ { 2381, 17, 18, 19, 1, -16 }, +/* 'P' 0x50 */ { 2420, 12, 18, 16, 2, -16 }, +/* 'Q' 0x51 */ { 2447, 17, 19, 19, 1, -16 }, +/* 'R' 0x52 */ { 2488, 14, 18, 17, 2, -16 }, +/* 'S' 0x53 */ { 2520, 14, 18, 16, 1, -16 }, +/* 'T' 0x54 */ { 2552, 12, 18, 15, 1, -16 }, +/* 'U' 0x55 */ { 2579, 13, 18, 17, 2, -16 }, +/* 'V' 0x56 */ { 2609, 14, 18, 15, 1, -16 }, +/* 'W' 0x57 */ { 2641, 22, 18, 22, 0, -16 }, +/* 'X' 0x58 */ { 2691, 14, 18, 16, 1, -16 }, +/* 'Y' 0x59 */ { 2723, 14, 18, 16, 1, -16 }, +/* 'Z' 0x5A */ { 2755, 13, 18, 15, 1, -16 }, +/* '[' 0x5B */ { 2785, 4, 23, 7, 2, -16 }, +/* '\' 0x5C */ { 2797, 7, 18, 7, 0, -16 }, +/* ']' 0x5D */ { 2813, 4, 23, 7, 1, -16 }, +/* '^' 0x5E */ { 2825, 9, 9, 11, 1, -15 }, +/* '_' 0x5F */ { 2836, 15, 1, 13, -1, 5 }, +/* '`' 0x60 */ { 2838, 5, 4, 6, 1, -16 }, +/* 'a' 0x61 */ { 2841, 12, 13, 13, 1, -11 }, +/* 'b' 0x62 */ { 2861, 12, 18, 13, 1, -16 }, +/* 'c' 0x63 */ { 2888, 10, 13, 12, 1, -11 }, +/* 'd' 0x64 */ { 2905, 11, 18, 13, 1, -16 }, +/* 'e' 0x65 */ { 2930, 11, 13, 13, 1, -11 }, +/* 'f' 0x66 */ { 2948, 5, 18, 7, 1, -16 }, +/* 'g' 0x67 */ { 2960, 11, 18, 13, 1, -11 }, +/* 'h' 0x68 */ { 2985, 10, 18, 13, 1, -16 }, +/* 'i' 0x69 */ { 3008, 2, 18, 5, 2, -16 }, +/* 'j' 0x6A */ { 3013, 4, 23, 6, 0, -16 }, +/* 'k' 0x6B */ { 3025, 10, 18, 12, 1, -16 }, +/* 'l' 0x6C */ { 3048, 2, 18, 5, 1, -16 }, +/* 'm' 0x6D */ { 3053, 17, 13, 19, 1, -11 }, +/* 'n' 0x6E */ { 3081, 10, 13, 13, 1, -11 }, +/* 'o' 0x6F */ { 3098, 11, 13, 13, 1, -11 }, +/* 'p' 0x70 */ { 3116, 12, 17, 13, 1, -11 }, +/* 'q' 0x71 */ { 3142, 11, 17, 13, 1, -11 }, +/* 'r' 0x72 */ { 3166, 6, 13, 8, 1, -11 }, +/* 's' 0x73 */ { 3176, 10, 13, 12, 1, -11 }, +/* 't' 0x74 */ { 3193, 5, 16, 7, 1, -14 }, +/* 'u' 0x75 */ { 3203, 10, 13, 13, 1, -11 }, +/* 'v' 0x76 */ { 3220, 11, 13, 12, 0, -11 }, +/* 'w' 0x77 */ { 3238, 17, 13, 17, 0, -11 }, +/* 'x' 0x78 */ { 3266, 10, 13, 11, 1, -11 }, +/* 'y' 0x79 */ { 3283, 11, 18, 11, 0, -11 }, +/* 'z' 0x7A */ { 3308, 10, 13, 12, 1, -11 }, +/* '{' 0x7B */ { 3325, 5, 23, 8, 1, -16 }, +/* '|' 0x7C */ { 3340, 2, 23, 6, 2, -16 }, +/* '}' 0x7D */ { 3346, 5, 23, 8, 2, -16 }, +/* '~' 0x7E */ { 3361, 10, 5, 12, 1, -9 }, +/* 0x7F */ { 3368, 0, 0, 0, 0, 0 }, +/* 0x80 */ { 3368, 16, 22, 18, 1, -18 }, +/* 0x81 */ { 3412, 11, 22, 14, 2, -22 }, +/* 0x82 */ { 3443, 2, 5, 6, 2, -2 }, +/* 0x83 */ { 3445, 7, 18, 9, 1, -18 }, +/* 0x84 */ { 3461, 6, 5, 10, 2, -2 }, +/* 0x85 */ { 3465, 12, 2, 16, 2, -2 }, +/* 0x86 */ { 3468, 10, 21, 13, 2, -17 }, +/* 0x87 */ { 3495, 10, 20, 13, 2, -17 }, +/* 0x88 */ { 3520, 14, 17, 16, 1, -17 }, +/* 0x89 */ { 3550, 23, 18, 24, 0, -18 }, +/* 0x8A */ { 3602, 23, 18, 24, 0, -18 }, +/* 0x8B */ { 3654, 3, 8, 6, 1, -11 }, +/* 0x8C */ { 3657, 21, 18, 24, 2, -18 }, +/* 0x8D */ { 3705, 12, 22, 15, 2, -22 }, +/* 0x8E */ { 3738, 16, 18, 18, 1, -18 }, +/* 0x8F */ { 3774, 13, 21, 17, 2, -18 }, +/* 0x90 */ { 3809, 12, 22, 14, 0, -18 }, +/* 0x91 */ { 3842, 2, 6, 6, 2, -18 }, +/* 0x92 */ { 3844, 2, 6, 6, 2, -18 }, +/* 0x93 */ { 3846, 6, 6, 10, 2, -18 }, +/* 0x94 */ { 3851, 6, 6, 10, 2, -18 }, +/* 0x95 */ { 3856, 6, 6, 10, 2, -11 }, +/* 0x96 */ { 3861, 10, 2, 12, 1, -8 }, +/* 0x97 */ { 3864, 22, 2, 24, 1, -8 }, +/* 0x98 */ { 3870, 0, 0, 8, 0, 0 }, +/* 0x99 */ { 3870, 22, 13, 24, 2, -18 }, +/* 0x9A */ { 3906, 17, 13, 19, 1, -13 }, +/* 0x9B */ { 3934, 3, 8, 6, 2, -10 }, +/* 0x9C */ { 3937, 18, 13, 20, 1, -13 }, +/* 0x9D */ { 3967, 9, 18, 12, 1, -18 }, +/* 0x9E */ { 3988, 11, 18, 14, 1, -18 }, +/* 0x9F */ { 4013, 10, 15, 13, 1, -13 }, +/* 0xA0 */ { 4032, 0, 0, 7, 0, 0 }, +/* 0xA1 */ { 4032, 13, 22, 15, 1, -22 }, +/* 0xA2 */ { 4068, 11, 22, 11, 0, -17 }, +/* 0xA3 */ { 4099, 9, 18, 13, 1, -18 }, +/* 0xA4 */ { 4120, 9, 9, 13, 2, -13 }, +/* 0xA5 */ { 4131, 11, 20, 14, 2, -20 }, +/* 0xA6 */ { 4159, 2, 23, 6, 2, -18 }, +/* 0xA7 */ { 4165, 11, 23, 13, 1, -18 }, +/* 0xA8 */ { 4197, 12, 21, 15, 2, -21 }, +/* 0xA9 */ { 4229, 18, 17, 19, 1, -17 }, +/* 0xAA */ { 4268, 15, 18, 17, 1, -18 }, +/* 0xAB */ { 4302, 8, 8, 12, 2, -11 }, +/* 0xAC */ { 4310, 12, 6, 14, 1, -9 }, +/* 0xAD */ { 4319, 6, 2, 8, 1, -8 }, +/* 0xAE */ { 4321, 18, 17, 19, 1, -17 }, +/* 0xAF */ { 4360, 7, 21, 7, 0, -21 }, +/* 0xB0 */ { 4379, 7, 8, 15, 4, -17 }, +/* 0xB1 */ { 4386, 12, 15, 14, 1, -15 }, +/* 0xB2 */ { 4409, 2, 18, 7, 2, -18 }, +/* 0xB3 */ { 4414, 2, 18, 5, 2, -18 }, +/* 0xB4 */ { 4419, 8, 15, 9, 1, -15 }, +/* 0xB5 */ { 4434, 12, 17, 13, 2, -13 }, +/* 0xB6 */ { 4460, 11, 21, 13, 2, -18 }, +/* 0xB7 */ { 4489, 2, 2, 6, 2, -8 }, +/* 0xB8 */ { 4490, 11, 17, 13, 1, -17 }, +/* 0xB9 */ { 4514, 19, 18, 22, 2, -18 }, +/* 0xBA */ { 4557, 11, 13, 12, 0, -13 }, +/* 0xBB */ { 4575, 8, 8, 12, 2, -10 }, +/* 0xBC */ { 4583, 4, 23, 6, 0, -18 }, +/* 0xBD */ { 4595, 14, 18, 16, 1, -18 }, +/* 0xBE */ { 4627, 10, 13, 12, 1, -13 }, +/* 0xBF */ { 4644, 6, 17, 6, 0, -17 }, +/* 0xC0 */ { 4657, 14, 18, 16, 1, -18 }, +/* 0xC1 */ { 4689, 13, 18, 16, 2, -18 }, +/* 0xC2 */ { 4719, 13, 18, 16, 2, -18 }, +/* 0xC3 */ { 4749, 11, 18, 14, 2, -18 }, +/* 0xC4 */ { 4774, 18, 21, 19, 1, -18 }, +/* 0xC5 */ { 4822, 12, 18, 15, 2, -18 }, +/* 0xC6 */ { 4849, 20, 18, 22, 1, -18 }, +/* 0xC7 */ { 4894, 13, 18, 16, 1, -18 }, +/* 0xC8 */ { 4924, 13, 18, 18, 2, -18 }, +/* 0xC9 */ { 4954, 13, 22, 18, 2, -22 }, +/* 0xCA */ { 4990, 12, 18, 15, 2, -18 }, +/* 0xCB */ { 5017, 14, 18, 16, 0, -18 }, +/* 0xCC */ { 5049, 16, 18, 20, 2, -18 }, +/* 0xCD */ { 5085, 13, 18, 17, 2, -18 }, +/* 0xCE */ { 5115, 17, 18, 19, 1, -18 }, +/* 0xCF */ { 5154, 13, 18, 17, 2, -18 }, +/* 0xD0 */ { 5184, 12, 18, 16, 2, -18 }, +/* 0xD1 */ { 5211, 15, 18, 17, 1, -18 }, +/* 0xD2 */ { 5245, 12, 18, 15, 1, -18 }, +/* 0xD3 */ { 5272, 13, 18, 15, 1, -18 }, +/* 0xD4 */ { 5302, 18, 18, 20, 1, -18 }, +/* 0xD5 */ { 5343, 14, 18, 16, 1, -18 }, +/* 0xD6 */ { 5375, 15, 21, 18, 2, -18 }, +/* 0xD7 */ { 5415, 12, 18, 15, 1, -18 }, +/* 0xD8 */ { 5442, 16, 18, 20, 2, -18 }, +/* 0xD9 */ { 5478, 18, 21, 20, 2, -18 }, +/* 0xDA */ { 5526, 18, 18, 20, 1, -18 }, +/* 0xDB */ { 5567, 17, 18, 21, 2, -18 }, +/* 0xDC */ { 5606, 13, 18, 16, 2, -18 }, +/* 0xDD */ { 5636, 15, 18, 17, 1, -18 }, +/* 0xDE */ { 5670, 21, 18, 24, 2, -18 }, +/* 0xDF */ { 5718, 14, 18, 16, 0, -18 }, +/* 0xE0 */ { 5750, 12, 13, 13, 1, -13 }, +/* 0xE1 */ { 5770, 11, 19, 13, 1, -19 }, +/* 0xE2 */ { 5797, 10, 13, 12, 1, -13 }, +/* 0xE3 */ { 5814, 7, 13, 9, 1, -13 }, +/* 0xE4 */ { 5826, 14, 15, 14, 0, -13 }, +/* 0xE5 */ { 5853, 11, 13, 13, 1, -13 }, +/* 0xE6 */ { 5871, 16, 13, 18, 1, -13 }, +/* 0xE7 */ { 5897, 9, 13, 12, 1, -13 }, +/* 0xE8 */ { 5912, 11, 13, 13, 1, -13 }, +/* 0xE9 */ { 5930, 11, 17, 13, 1, -17 }, +/* 0xEA */ { 5954, 9, 13, 12, 1, -13 }, +/* 0xEB */ { 5969, 10, 13, 12, 0, -13 }, +/* 0xEC */ { 5986, 12, 13, 14, 1, -13 }, +/* 0xED */ { 6006, 10, 13, 13, 1, -13 }, +/* 0xEE */ { 6023, 11, 13, 13, 1, -13 }, +/* 0xEF */ { 6041, 10, 13, 13, 1, -13 }, +/* 0xF0 */ { 6058, 12, 17, 13, 1, -13 }, +/* 0xF1 */ { 6084, 10, 13, 12, 1, -13 }, +/* 0xF2 */ { 6101, 8, 13, 10, 1, -13 }, +/* 0xF3 */ { 6114, 11, 18, 11, 0, -13 }, +/* 0xF4 */ { 6139, 19, 20, 20, 1, -16 }, +/* 0xF5 */ { 6187, 10, 13, 11, 1, -13 }, +/* 0xF6 */ { 6204, 12, 15, 13, 1, -13 }, +/* 0xF7 */ { 6227, 9, 13, 12, 1, -13 }, +/* 0xF8 */ { 6242, 14, 13, 16, 1, -13 }, +/* 0xF9 */ { 6265, 16, 15, 17, 1, -13 }, +/* 0xFA */ { 6295, 14, 13, 15, 1, -13 }, +/* 0xFB */ { 6318, 14, 13, 16, 1, -13 }, +/* 0xFC */ { 6341, 10, 13, 12, 1, -13 }, +/* 0xFD */ { 6358, 11, 13, 12, 1, -13 }, +/* 0xFE */ { 6376, 16, 13, 18, 1, -13 }, +/* 0xFF */ { 6402, 10, 13, 13, 1, -13 }, +}; + +const GFXfont FreeSans12pt_Win1251 PROGMEM = { +(uint8_t*)FreeSans12pt_Win1251Bitmaps, +(GFXglyph*)FreeSans12pt_Win1251Glyphs, +0x01, 0xFF, 19 +}; diff --git a/src/graphics/niche/Fonts/FreeSans12pt_Win1252.h b/src/graphics/niche/Fonts/FreeSans12pt_Win1252.h new file mode 100644 index 000000000..752925d6d --- /dev/null +++ b/src/graphics/niche/Fonts/FreeSans12pt_Win1252.h @@ -0,0 +1,527 @@ +// trunk-ignore-all(clang-format) +#pragma once +/* PROPERTIES + +FONT_NAME FreeSans12pt_Win1252 +*/ +const uint8_t FreeSans12pt_Win1252Bitmaps[] PROGMEM = { +/* 0x01 */ 0x00, 0x30, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x24, 0x00, 0x04, 0x80, 0x01, 0x90, 0x00, 0x62, 0x00, 0x30, 0xFE, 0x04, 0x10, 0x5F, 0x02, 0x0B, 0x00, 0x7F, 0xE0, 0x0C, 0x1C, 0x02, 0x83, 0x81, 0x9F, 0xF0, 0x02, 0x1E, 0x00, 0x41, 0xC0, 0x0E, 0x7F, 0x81, 0x78, 0x18, 0x62, 0x00, 0xFF, 0xC0, +/* 0x02 */ 0x00, 0xFF, 0x80, 0x61, 0x13, 0xF0, 0x62, 0x60, 0x07, 0xFC, 0x00, 0x83, 0x80, 0x10, 0xF0, 0x33, 0xF6, 0x01, 0x41, 0xC0, 0x18, 0x38, 0x03, 0xFF, 0xE0, 0x47, 0x02, 0x08, 0x20, 0x61, 0xC4, 0x06, 0x17, 0x00, 0x22, 0x00, 0x02, 0x40, 0x00, 0x48, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x3C, 0x00, +/* 0x03 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x04, 0x08, 0x48, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x08, 0x10, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA1, 0x81, 0x8D, 0x87, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x04 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x10, 0x02, 0x48, 0xE0, 0x61, 0xC1, 0xCC, 0x0E, 0x78, 0x1C, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xFC, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x05 */ 0x00, 0x18, 0x00, 0x00, 0x40, 0x01, 0x90, 0x01, 0xF4, 0x08, 0x12, 0x23, 0xC1, 0x91, 0x2C, 0x1C, 0x8A, 0xC3, 0x64, 0x64, 0x13, 0x22, 0x41, 0x98, 0x26, 0x2C, 0xC4, 0x22, 0x60, 0x42, 0x13, 0x04, 0x30, 0x80, 0x61, 0xA4, 0x02, 0x18, 0x20, 0x03, 0x41, 0x00, 0x20, 0x08, 0x02, 0x00, 0x60, 0x40, 0x03, 0xF8, +/* 0x06 */ 0x00, 0x10, 0x00, 0x03, 0x00, 0x1C, 0x48, 0x00, 0xB4, 0x80, 0x09, 0xF9, 0xC0, 0xE0, 0xE4, 0x0C, 0x02, 0x8F, 0x80, 0x38, 0x88, 0x01, 0x0D, 0x00, 0x18, 0x30, 0x01, 0x60, 0x80, 0x13, 0x18, 0x03, 0xF2, 0xC0, 0x20, 0x26, 0x06, 0x07, 0xFF, 0xA0, 0x02, 0x39, 0x00, 0x14, 0x70, 0x01, 0xC3, 0x00, 0x18, 0x00, +/* 0x07 */ +/* 0x08 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x10, 0x37, 0xE2, 0x61, 0x00, 0x0C, 0xC6, 0x10, 0x98, 0x0C, 0x63, 0x00, 0x00, 0xC6, 0x00, +/* 0x09 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x00, 0x37, 0xE0, +/* 0x0A */ +/* 0x0B */ 0x1F, 0x07, 0xC1, 0x86, 0x41, 0x10, 0x0C, 0x04, 0x80, 0x40, 0x18, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x00, 0x00, 0x30, 0x00, 0x01, 0x40, 0x00, 0x0A, 0x00, 0x00, 0x88, 0x00, 0x04, 0x40, 0x00, 0x41, 0x00, 0x02, 0x04, 0x00, 0x20, 0x20, 0x02, 0x00, 0x80, 0x20, 0x02, 0x02, 0x00, 0x08, 0x20, 0x00, 0x22, 0x00, 0x00, 0xE0, 0x00, +/* 0x0C */ 0x01, 0x00, 0x00, 0x38, 0x00, 0x04, 0xC0, 0x01, 0x08, 0x00, 0x18, 0x80, 0x1C, 0x10, 0x02, 0x07, 0x80, 0x81, 0x10, 0x1F, 0xC2, 0x02, 0x00, 0x60, 0x80, 0x1A, 0x20, 0x1C, 0x42, 0x1C, 0x08, 0xFE, 0x03, 0xA0, 0x01, 0x8C, 0x01, 0xC1, 0x43, 0xD0, 0x27, 0x81, 0xF8, +/* 0x0D */ +/* 0x0E */ 0x00, 0xE0, 0x00, 0x11, 0x00, 0x01, 0x10, 0x00, 0x0B, 0x00, 0x03, 0xF8, 0x00, 0x60, 0x60, 0x09, 0x02, 0x00, 0xA0, 0x10, 0x16, 0x01, 0x01, 0x40, 0x10, 0x10, 0x01, 0x01, 0x00, 0x08, 0x10, 0x00, 0x82, 0x1F, 0x08, 0x3F, 0x90, 0x44, 0x00, 0x06, 0xBF, 0xFF, 0xAF, 0xF0, 0xFF, 0xFF, 0x0F, 0xE3, 0xFB, 0xFC, +/* 0x0F */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x40, 0x12, 0x34, 0x00, 0x69, 0x40, 0x01, 0x49, 0xE0, 0xF1, 0xCD, 0x06, 0x8E, 0x28, 0x14, 0x71, 0x40, 0xA3, 0x8B, 0xFD, 0x14, 0x50, 0x68, 0xA2, 0x81, 0x4D, 0x97, 0xFA, 0x44, 0xBF, 0xD6, 0x31, 0x02, 0xE0, 0xC8, 0x16, 0x08, 0x61, 0x08, 0x21, 0xF0, 0x80, 0xF8, 0x78, 0x00, +/* 0x10 */ 0x00, 0xF0, 0x00, 0x3A, 0x00, 0x07, 0xC0, 0x00, 0xA8, 0x00, 0x1F, 0x00, 0x02, 0xB0, 0x00, 0x52, 0x00, 0x0A, 0x40, 0x02, 0x48, 0x00, 0x49, 0x00, 0x09, 0x30, 0x01, 0x22, 0x01, 0xC4, 0x70, 0xF0, 0x85, 0xE1, 0x10, 0x88, 0x37, 0x20, 0x03, 0x9C, 0x00, 0x37, 0x00, 0x06, 0x40, 0x01, 0x86, 0x00, +/* 0x11 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x60, 0x02, 0x36, 0x00, 0x09, 0x04, 0x0C, 0x48, 0x60, 0xC1, 0xC3, 0x0F, 0x0E, 0x00, 0x08, 0x70, 0x00, 0x23, 0x80, 0x63, 0x84, 0x01, 0x9F, 0x20, 0x0C, 0xFD, 0x80, 0x27, 0xE4, 0x03, 0x3F, 0x30, 0x33, 0xE0, 0xC0, 0x00, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x12 */ 0x00, 0xC2, 0x00, 0x1C, 0x24, 0x02, 0x18, 0x60, 0x64, 0x02, 0x02, 0x40, 0x20, 0x00, 0xF2, 0x03, 0x89, 0xE0, 0x7C, 0x80, 0x0E, 0x25, 0x80, 0xE1, 0x00, 0x1A, 0x08, 0x71, 0xB0, 0xC4, 0x39, 0x84, 0xC2, 0xCC, 0x40, 0x76, 0x7C, 0x05, 0xBB, 0x80, 0x4C, 0xE0, 0x0A, 0x78, 0x00, 0x9C, 0x00, 0x0F, 0x00, 0x00, +/* 0x13 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x60, 0xC1, 0xC6, 0xC9, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xF8, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x14 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x20, 0x22, 0x33, 0x01, 0x89, 0x20, 0x02, 0x48, 0x60, 0xE1, 0xC8, 0x80, 0x8E, 0x46, 0x46, 0x72, 0x32, 0x33, 0x9F, 0x9F, 0x94, 0x78, 0x78, 0xA0, 0x00, 0x0D, 0x80, 0x00, 0x44, 0x0E, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x15 */ 0x03, 0xFC, 0x20, 0x38, 0x1C, 0x81, 0x80, 0x1D, 0x08, 0x00, 0x32, 0x60, 0x00, 0x89, 0x00, 0x02, 0x18, 0x00, 0x08, 0x61, 0xC3, 0x22, 0x8D, 0x93, 0x72, 0x00, 0x00, 0x48, 0x00, 0x01, 0x20, 0x00, 0x04, 0x9F, 0xFF, 0x92, 0x60, 0x0E, 0x44, 0xFF, 0xF2, 0x11, 0xC3, 0x88, 0x21, 0xF8, 0x40, 0x40, 0x02, 0x00, 0xC0, 0x30, 0x00, 0xFF, 0x00, +/* 0x16 */ 0x03, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x01, 0xE0, 0x03, 0xF0, 0x03, 0xF0, 0x27, 0xF0, 0x6F, 0x70, 0x6E, 0x60, 0xFC, 0x60, 0xFC, 0x7E, 0xFC, 0x7E, 0xFC, 0x3F, 0xF4, 0x1F, 0xF4, 0x1F, 0xF0, 0x0E, 0x70, 0x0E, 0x30, 0x1C, 0x38, 0x38, 0x0F, 0xF0, +/* 0x17 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x00, 0x21, 0xC0, 0x02, 0x8E, 0x20, 0xF4, 0x70, 0x84, 0x11, 0x82, 0x40, 0x84, 0x01, 0x03, 0x20, 0x0F, 0x85, 0x80, 0x03, 0x04, 0x00, 0x04, 0x30, 0x78, 0x10, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x18 */ 0x00, 0xFC, 0x00, 0x02, 0x06, 0x00, 0x08, 0x24, 0x00, 0x21, 0xA4, 0x00, 0x4C, 0x48, 0x00, 0xA0, 0x50, 0x01, 0x92, 0x60, 0x03, 0x24, 0xC0, 0x06, 0x01, 0x81, 0x28, 0x03, 0x49, 0x6C, 0xC4, 0xAD, 0xD8, 0x16, 0xA4, 0xCC, 0xC4, 0x44, 0x86, 0x13, 0x05, 0x00, 0x28, 0x0A, 0x00, 0x50, 0x14, 0x00, 0x90, 0x48, 0x01, 0x20, 0x90, 0x02, 0x41, 0x20, 0x00, 0x00, +/* 0x19 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x49, 0xC3, 0x81, 0xC0, 0x00, 0x0E, 0x78, 0xF0, 0x77, 0xEF, 0xC3, 0xA7, 0x4E, 0x15, 0x0A, 0x10, 0xA7, 0x8F, 0x0D, 0x80, 0x00, 0x44, 0x00, 0x06, 0x33, 0xF0, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x1A */ 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0C, 0x3E, 0x18, 0x82, 0x32, 0x02, 0x64, 0x04, 0xC8, 0x09, 0x80, 0x23, 0x00, 0x86, 0x02, 0x0C, 0x08, 0x18, 0x10, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x81, 0x80, 0x03, 0x00, 0x07, 0xFF, 0xF8, +/* 0x1B */ 0x00, 0xFE, 0x00, 0x03, 0x81, 0x80, 0x04, 0x00, 0x60, 0x08, 0x00, 0x30, 0x10, 0x00, 0x10, 0x30, 0x07, 0x88, 0x23, 0xC8, 0x08, 0x22, 0x00, 0x04, 0x60, 0x00, 0x44, 0x60, 0x00, 0x84, 0x63, 0x03, 0x04, 0x61, 0xFC, 0x04, 0x6B, 0x00, 0x9E, 0xA5, 0x01, 0x6A, 0xD5, 0x01, 0x43, 0xA8, 0x81, 0x05, 0xD0, 0x82, 0x0A, 0xA0, 0x82, 0x05, 0xC0, 0x82, 0x02, 0x61, 0xFF, 0x0C, 0x1E, 0x00, 0xF0, +/* 0x1C */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x30, 0x02, 0x32, 0x00, 0x09, 0x00, 0x00, 0x48, 0x20, 0x61, 0xC3, 0x84, 0x0E, 0x1C, 0x78, 0x70, 0x40, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA0, 0x03, 0x0D, 0x83, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00, +/* 0x1D */ 0x01, 0xFE, 0x00, 0x3A, 0x1C, 0x03, 0x00, 0x30, 0x23, 0x1E, 0xC3, 0x38, 0x03, 0x10, 0xC3, 0x09, 0x00, 0x18, 0x68, 0x00, 0xC1, 0x40, 0x00, 0x0A, 0x07, 0x80, 0x50, 0x46, 0x02, 0x80, 0x00, 0x1A, 0x1E, 0x00, 0xCB, 0x10, 0x0D, 0x03, 0x00, 0x48, 0x60, 0x06, 0x40, 0x00, 0x22, 0x0C, 0x02, 0x10, 0x60, 0x60, 0x43, 0xFC, 0x01, 0xE0, 0x00, 0x00, +/* 0x1E */ 0x01, 0xF0, 0x00, 0xEA, 0xC0, 0x31, 0x5F, 0x04, 0x5F, 0x88, 0x80, 0xA0, 0x48, 0x0E, 0x02, 0x8F, 0x40, 0x3C, 0x10, 0x21, 0x66, 0x87, 0x15, 0x98, 0x71, 0x41, 0x02, 0x14, 0x00, 0x01, 0x40, 0x00, 0x14, 0x00, 0x01, 0x21, 0xFE, 0x12, 0x00, 0x02, 0x10, 0x00, 0x60, 0x80, 0x0C, 0x06, 0x01, 0x80, 0x3F, 0xE0, +/* 0x1F */ 0x0E, 0x00, 0x13, 0x00, 0x23, 0x00, 0xF3, 0x01, 0x31, 0x01, 0x11, 0x03, 0xD3, 0x06, 0xF2, 0x30, 0x34, 0xC7, 0x25, 0x33, 0x2B, 0xC2, 0x57, 0x04, 0x3A, 0x08, 0x72, 0x30, 0xA3, 0xC3, 0x40, 0x04, 0x40, 0x18, 0x40, 0x60, 0x7F, 0x80, +/* ' ' 0x20 */ +/* '!' 0x21 */ 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, +/* '"' 0x22 */ 0xCF, 0x3C, 0xF3, 0x8A, 0x20, +/* '#' 0x23 */ 0x06, 0x30, 0x31, 0x03, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30, +/* '$' 0x24 */ 0x04, 0x03, 0xE1, 0xFF, 0x72, 0x7C, 0x47, 0x88, 0xF1, 0x07, 0xA0, 0x7E, 0x03, 0xF0, 0x17, 0x02, 0x7C, 0x47, 0x88, 0xF1, 0x1B, 0x26, 0x7F, 0xC3, 0xE0, 0x10, 0x02, 0x00, +/* '%' 0x25 */ 0x00, 0x06, 0x03, 0xC0, 0x40, 0x7E, 0x0C, 0x0E, 0x70, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xE7, 0x30, 0x07, 0xE6, 0x00, 0x3C, 0x40, 0x00, 0x0C, 0x7C, 0x00, 0x8F, 0xE0, 0x19, 0xC7, 0x01, 0x18, 0x30, 0x31, 0x83, 0x02, 0x1C, 0x70, 0x40, 0xFE, 0x04, 0x07, 0xC0, +/* '&' 0x26 */ 0x0F, 0x00, 0x7E, 0x03, 0x9C, 0x0C, 0x30, 0x30, 0xC0, 0xE7, 0x01, 0xF8, 0x03, 0x80, 0x3E, 0x01, 0xCC, 0x6E, 0x39, 0xB0, 0x7C, 0xC0, 0xF3, 0x03, 0xCE, 0x1F, 0x9F, 0xE6, 0x3E, 0x1C, +/* ''' 0x27 */ 0xFF, 0xA0, +/* '(' 0x28 */ 0x08, 0x8C, 0x46, 0x31, 0x98, 0xC6, 0x31, 0x8C, 0x63, 0x08, 0x63, 0x08, 0x61, 0x0C, 0x20, +/* ')' 0x29 */ 0x82, 0x18, 0xC3, 0x18, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x62, 0x31, 0x88, 0xC4, 0x62, 0x00, +/* '*' 0x2A */ 0x10, 0x23, 0x5B, 0xE3, 0x8D, 0x91, 0x00, +/* '+' 0x2B */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, +/* ',' 0x2C */ 0xF5, 0x60, +/* '-' 0x2D */ 0xFF, 0xF0, +/* '.' 0x2E */ 0xF0, +/* '/' 0x2F */ 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x00, +/* '0' 0x30 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x6C, 0x0F, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3E, 0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00, +/* '1' 0x31 */ 0x08, 0xCF, 0xFF, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, +/* '2' 0x32 */ 0x1F, 0x0F, 0xF9, 0x87, 0x60, 0x7C, 0x06, 0x00, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x06, 0x00, 0xC0, 0x30, 0x07, 0xFF, 0xFF, 0xE0, +/* '3' 0x33 */ 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0C, 0x01, 0x80, 0x60, 0x78, 0x0F, 0x80, 0x18, 0x01, 0x80, 0x3C, 0x07, 0x80, 0xD8, 0x73, 0xFC, 0x3F, 0x00, +/* '4' 0x34 */ 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0, 0xD8, 0x1B, 0x06, 0x61, 0x8C, 0x21, 0x8C, 0x33, 0x06, 0x7F, 0xFF, 0xFE, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, +/* '5' 0x35 */ 0x3F, 0xCF, 0xF9, 0x80, 0x30, 0x06, 0x00, 0xDE, 0x1F, 0xE7, 0x0E, 0x00, 0xE0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x81, 0xB8, 0x73, 0xFC, 0x1F, 0x00, +/* '6' 0x36 */ 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x74, 0x01, 0x80, 0x33, 0xC7, 0xFE, 0xF1, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x06, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00, +/* '7' 0x37 */ 0xFF, 0xFF, 0xFC, 0x01, 0x00, 0x60, 0x18, 0x02, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x04, 0x01, 0x80, 0x30, 0x06, 0x01, 0x80, 0x30, 0x00, +/* '8' 0x38 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x66, 0x0C, 0xC1, 0x8C, 0x61, 0xF8, 0x3F, 0x8E, 0x3B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xD8, 0x31, 0xFC, 0x1F, 0x00, +/* '9' 0x39 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C, 0x07, 0x80, 0xF0, 0x1E, 0x07, 0x61, 0xEF, 0xFC, 0x79, 0x80, 0x30, 0x05, 0xC1, 0x98, 0x73, 0xFC, 0x1E, 0x00, +/* ':' 0x3A */ 0xF0, 0x00, 0x03, 0xC0, +/* ';' 0x3B */ 0xF0, 0x00, 0x0F, 0x56, +/* '<' 0x3C */ 0x00, 0x70, 0x1E, 0x0F, 0x83, 0xC0, 0xF0, 0x0E, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x10, +/* '=' 0x3D */ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, +/* '>' 0x3E */ 0xE0, 0x07, 0x80, 0x1F, 0x00, 0x7C, 0x00, 0xF0, 0x07, 0x01, 0xE0, 0xF0, 0x3C, 0x0F, 0x00, 0x80, 0x00, +/* '?' 0x3F */ 0x3F, 0x1F, 0xEE, 0x1F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x03, 0x81, 0xC0, 0xE0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00, +/* '@' 0x40 */ 0x00, 0xFE, 0x00, 0x0F, 0xFE, 0x00, 0xF0, 0x3E, 0x07, 0x00, 0x3C, 0x38, 0x00, 0x38, 0xC1, 0xE0, 0x66, 0x0F, 0xD9, 0xD8, 0x61, 0xC3, 0xC3, 0x07, 0x0F, 0x1C, 0x1C, 0x3C, 0x60, 0x60, 0xF1, 0x81, 0x83, 0xC6, 0x06, 0x1B, 0x18, 0x38, 0xEE, 0x71, 0xE7, 0x18, 0xFD, 0xF8, 0x71, 0xE7, 0xC0, 0xE0, 0x00, 0x01, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x01, 0xFC, 0x00, +/* 'A' 0x41 */ 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 'B' 0x42 */ 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00, +/* 'C' 0x43 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0, +/* 'D' 0x44 */ 0xFF, 0xC3, 0xFF, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1B, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00, 0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3, 0xFE, 0x00, +/* 'E' 0x45 */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, +/* 'F' 0x46 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xDF, 0xFB, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00, +/* 'G' 0x47 */ 0x07, 0xF0, 0x1F, 0xFC, 0x3C, 0x1E, 0x70, 0x07, 0x60, 0x03, 0xE0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xC0, 0x03, 0xC0, 0x03, 0x60, 0x03, 0x60, 0x07, 0x30, 0x0F, 0x3C, 0x1F, 0x1F, 0xFB, 0x07, 0xE1, +/* 'H' 0x48 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0, +/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 'J' 0x4A */ 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00, +/* 'K' 0x4B */ 0xC0, 0x3E, 0x03, 0xB0, 0x39, 0x83, 0x8C, 0x38, 0x63, 0x83, 0x38, 0x19, 0xC0, 0xDE, 0x07, 0xB8, 0x38, 0xE1, 0x83, 0x0C, 0x1C, 0x60, 0x73, 0x01, 0x98, 0x0E, 0xC0, 0x3E, 0x00, 0xC0, +/* 'L' 0x4C */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0, +/* 'M' 0x4D */ 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83, +/* 'N' 0x4E */ 0xE0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE, 0x0F, 0x30, 0x79, 0xC3, 0xC6, 0x1E, 0x18, 0xF0, 0xE7, 0x83, 0x3C, 0x1D, 0xE0, 0x6F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, 0xC0, +/* 'O' 0x4F */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00, +/* 'P' 0x50 */ 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x06, 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, +/* 'Q' 0x51 */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x01, 0x98, 0xC0, 0xFC, 0x78, 0x3C, 0x1F, 0xFF, 0x03, 0xF9, 0x80, 0x00, 0x40, +/* 'R' 0x52 */ 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x70, +/* 'S' 0x53 */ 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80, +/* 'T' 0x54 */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, +/* 'U' 0x55 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x80, 0xEE, 0x0E, 0x3F, 0xE0, 0xFC, 0x00, +/* 'V' 0x56 */ 0xC0, 0x0F, 0x00, 0x7E, 0x01, 0x98, 0x06, 0x60, 0x39, 0xC0, 0xC3, 0x03, 0x0C, 0x1C, 0x38, 0x60, 0x61, 0x81, 0x8E, 0x07, 0x30, 0x0C, 0xC0, 0x37, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1C, 0x00, +/* 'W' 0x57 */ 0xE0, 0x30, 0x1D, 0x80, 0xE0, 0x76, 0x07, 0x81, 0xDC, 0x1E, 0x06, 0x70, 0x7C, 0x18, 0xC1, 0xB0, 0xE3, 0x0C, 0xC3, 0x8C, 0x33, 0x0C, 0x38, 0xC6, 0x30, 0x67, 0x18, 0xC1, 0x98, 0x67, 0x06, 0x61, 0xD8, 0x1D, 0x83, 0x60, 0x3C, 0x0D, 0x80, 0xF0, 0x3E, 0x03, 0xC0, 0x70, 0x0F, 0x01, 0xC0, 0x18, 0x07, 0x00, +/* 'X' 0x58 */ 0xE0, 0x1D, 0x80, 0xE7, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x00, 0xFC, 0x01, 0xE0, 0x07, 0x00, 0x1E, 0x00, 0xF8, 0x03, 0x30, 0x1C, 0xE0, 0xE1, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1B, 0x00, 0x70, +/* 'Y' 0x59 */ 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, +/* 'Z' 0x5A */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x03, 0x80, 0x18, 0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, +/* '[' 0x5B */ 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xF0, +/* '\' 0x5C */ 0x81, 0x81, 0x02, 0x06, 0x04, 0x08, 0x18, 0x10, 0x20, 0x60, 0x40, 0x81, 0x81, 0x02, 0x06, 0x04, +/* ']' 0x5D */ 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xF0, +/* '^' 0x5E */ 0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19, 0x8C, 0x83, 0xC1, 0x80, +/* '_' 0x5F */ 0xFF, 0xFE, +/* '`' 0x60 */ 0xE3, 0x8C, 0x30, +/* 'a' 0x61 */ 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70, +/* 'b' 0x62 */ 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0xF8, 0xDF, 0xCF, 0x0E, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xE0, 0x6F, 0x0E, 0xDF, 0xCC, 0xF8, +/* 'c' 0x63 */ 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x00, +/* 'd' 0x64 */ 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xCF, 0xFB, 0x8F, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8F, 0x3F, 0x63, 0xCC, +/* 'e' 0x65 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xC1, 0xF0, +/* 'f' 0x66 */ 0x3B, 0xD8, 0xC6, 0x7F, 0xEC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x00, +/* 'g' 0x67 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xB1, 0xE6, 0x00, 0xC0, 0x3E, 0x0E, 0x7F, 0xC7, 0xE0, +/* 'h' 0x68 */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30, +/* 'i' 0x69 */ 0xF0, 0x3F, 0xFF, 0xFF, 0xF0, +/* 'j' 0x6A */ 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0, +/* 'k' 0x6B */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0xB8, 0xC6, 0x31, 0xCC, 0x3B, 0x06, 0xC1, 0xF0, 0x30, +/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 'm' 0x6D */ 0xCF, 0x1F, 0x6F, 0xDF, 0xFC, 0x78, 0xFC, 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0F, 0x03, 0x07, 0x81, 0x83, 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x3C, 0x0C, 0x18, +/* 'n' 0x6E */ 0xCF, 0x37, 0xEF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0, +/* 'o' 0x6F */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0, +/* 'p' 0x70 */ 0xCF, 0x8D, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00, +/* 'q' 0x71 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xF1, 0xE6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, +/* 'r' 0x72 */ 0xCF, 0x7F, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, +/* 's' 0x73 */ 0x3E, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3F, 0x01, 0xF0, 0x3E, 0x1D, 0xFE, 0x3F, 0x00, +/* 't' 0x74 */ 0x63, 0x19, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0xE7, +/* 'u' 0x75 */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0, +/* 'v' 0x76 */ 0xE0, 0x6C, 0x0D, 0x81, 0xB8, 0x63, 0x0C, 0x61, 0x8E, 0x60, 0xCC, 0x19, 0x83, 0xE0, 0x3C, 0x07, 0x00, 0xE0, +/* 'w' 0x77 */ 0xC1, 0xC1, 0xB0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36, 0x31, 0x9B, 0x18, 0xCD, 0x98, 0x64, 0x6C, 0x16, 0x36, 0x0F, 0x1A, 0x07, 0x8F, 0x03, 0x83, 0x80, 0xC1, 0xC0, +/* 'x' 0x78 */ 0xC1, 0xF8, 0x66, 0x30, 0xCC, 0x3E, 0x07, 0x00, 0xC0, 0x78, 0x36, 0x0C, 0xC6, 0x3B, 0x06, 0xC0, 0xC0, +/* 'y' 0x79 */ 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0C, 0x60, 0xCC, 0x1B, 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, 0x00, +/* 'z' 0x7A */ 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0, +/* '{' 0x7B */ 0x19, 0xCC, 0x63, 0x18, 0xC6, 0x31, 0x99, 0x86, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x1C, 0x60, +/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, +/* '}' 0x7D */ 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x0C, 0x33, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x73, 0x00, +/* '~' 0x7E */ 0x70, 0x3E, 0x09, 0xE4, 0x1F, 0x03, 0x80, +/* 0x7F */ +/* 0x80 */ 0x01, 0xF0, 0x1F, 0xF0, 0xE0, 0xC7, 0x00, 0x18, 0x00, 0xC0, 0x07, 0xFF, 0x3F, 0xFC, 0x30, 0x01, 0xFF, 0x8F, 0xFC, 0x0C, 0x00, 0x18, 0x00, 0x70, 0x00, 0xE0, 0x81, 0xFE, 0x03, 0xF0, +/* 0x81 */ +/* 0x82 */ 0xF5, 0x80, +/* 0x83 */ 0x1C, 0xF3, 0x0C, 0x31, 0xF7, 0xCC, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x33, 0xCE, 0x00, +/* 0x84 */ 0xCF, 0x34, 0x51, 0x88, +/* 0x85 */ 0xC6, 0x3C, 0x63, +/* 0x86 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x00, +/* 0x87 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x0F, 0xFF, 0xFF, 0x0C, 0x03, 0x00, 0xC0, 0x30, +/* 0x88 */ 0x38, 0xD9, 0xB6, 0x30, +/* 0x89 */ 0x38, 0x18, 0x00, 0xF8, 0x30, 0x03, 0x18, 0xC0, 0x04, 0x11, 0x80, 0x0C, 0x66, 0x00, 0x0F, 0x8C, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x40, 0x00, 0x01, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x31, 0xC0, 0xE0, 0x67, 0xC3, 0xC1, 0x98, 0xCC, 0xC3, 0x20, 0x90, 0x8C, 0x63, 0x33, 0x10, 0x7C, 0x3C, 0x60, 0x70, 0x38, +/* 0x8A */ 0x0C, 0x40, 0x1F, 0x00, 0x38, 0x03, 0xF8, 0x1F, 0xF0, 0xE0, 0xE6, 0x01, 0xD8, 0x03, 0x60, 0x01, 0xC0, 0x07, 0x80, 0x0F, 0xE0, 0x0F, 0xF0, 0x03, 0xE0, 0x01, 0xF0, 0x03, 0xC0, 0x0F, 0x80, 0x37, 0x83, 0x8F, 0xFC, 0x0F, 0xE0, +/* 0x8B */ 0x2F, 0x49, 0x99, +/* 0x8C */ 0x07, 0xCF, 0xFC, 0x7F, 0xFF, 0xF3, 0x83, 0xC0, 0x18, 0x07, 0x00, 0x60, 0x0C, 0x03, 0x00, 0x30, 0x0C, 0x00, 0xC0, 0x30, 0x03, 0x00, 0xC0, 0x0F, 0xFF, 0x00, 0x3F, 0xFC, 0x00, 0xC0, 0x30, 0x03, 0x00, 0xC0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x01, 0xC0, 0x0E, 0x0F, 0x00, 0x1F, 0xEF, 0xFC, 0x1F, 0x3F, 0xF0, +/* 0x8D */ +/* 0x8E */ 0x0C, 0xC0, 0x3C, 0x00, 0xE1, 0xFF, 0xFF, 0xFF, 0x80, 0x1C, 0x01, 0xC0, 0x1C, 0x00, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x01, 0xFF, 0xFF, 0xFF, 0x80, +/* 0x8F */ +/* 0x90 */ +/* 0x91 */ 0x6A, 0xF0, +/* 0x92 */ 0xF5, 0x60, +/* 0x93 */ 0x4E, 0x28, 0xA2, 0xCF, 0x30, +/* 0x94 */ 0xCF, 0x34, 0x51, 0x4E, 0x20, +/* 0x95 */ 0x7B, 0xFF, 0xFF, 0xFD, 0xE0, +/* 0x96 */ 0xFF, 0xFF, 0xF0, +/* 0x97 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, +/* 0x98 */ 0x63, 0xFE, 0x70, +/* 0x99 */ 0xFF, 0x70, 0x1F, 0xFD, 0xC0, 0x71, 0x87, 0x83, 0xC6, 0x1E, 0x0F, 0x18, 0x68, 0x3C, 0x61, 0xB1, 0xB1, 0x86, 0xC6, 0xC6, 0x19, 0x1B, 0x18, 0x66, 0xCC, 0x61, 0x9B, 0x31, 0x86, 0x3C, 0xC6, 0x18, 0xE3, 0x18, 0x63, 0x8C, +/* 0x9A */ 0x63, 0x0D, 0x83, 0x60, 0x70, 0x00, 0x0F, 0x87, 0xFB, 0x86, 0xC0, 0x30, 0x0F, 0x01, 0xFC, 0x0F, 0xC0, 0x7C, 0x0F, 0x87, 0x7F, 0x8F, 0xC0, +/* 0x9B */ 0x99, 0x92, 0xF4, +/* 0x9C */ 0x1F, 0x0F, 0x83, 0xF9, 0xFC, 0x71, 0xF8, 0x6E, 0x0F, 0x03, 0xC0, 0x60, 0x3C, 0x07, 0xFF, 0xC0, 0x7F, 0xFC, 0x06, 0x00, 0xC0, 0x60, 0x0E, 0x0F, 0x03, 0x71, 0xF8, 0x63, 0xF9, 0xFC, 0x1F, 0x0F, 0x80, +/* 0x9D */ +/* 0x9E */ 0x63, 0x0C, 0x83, 0x60, 0x70, 0x00, 0x3F, 0xFF, 0xFC, 0x06, 0x03, 0x01, 0xC0, 0xE0, 0x70, 0x18, 0x0C, 0x06, 0x03, 0x80, 0xFF, 0xFF, 0xF0, +/* 0x9F */ 0x0C, 0xC0, 0x33, 0x00, 0x00, 0x30, 0x03, 0xE0, 0x1D, 0x80, 0x67, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x80, 0xCC, 0x03, 0xE0, 0x07, 0x80, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, +/* 0xA0 */ +/* 0xA1 */ 0xF0, 0xBF, 0xFF, 0xFF, 0xF0, +/* 0xA2 */ 0x04, 0x00, 0x80, 0x7C, 0x1F, 0xE7, 0x4C, 0xC8, 0xF1, 0x1E, 0x20, 0xC4, 0x18, 0x83, 0x10, 0x72, 0x37, 0x4E, 0x7F, 0x87, 0xC0, 0x20, 0x04, 0x00, +/* 0xA3 */ 0x0F, 0xC1, 0xFE, 0x38, 0x76, 0x03, 0x60, 0x36, 0x00, 0x70, 0x03, 0x80, 0xFF, 0x0F, 0xF0, 0x1C, 0x00, 0xC0, 0x0C, 0x01, 0x80, 0x10, 0x02, 0xF1, 0x7F, 0xF6, 0x1F, +/* 0xA4 */ 0xDD, 0xFF, 0xD8, 0xD8, 0x3C, 0x1E, 0x0F, 0x8D, 0xFF, 0xDD, 0x80, +/* 0xA5 */ 0xC0, 0x3E, 0x06, 0x60, 0x63, 0x0C, 0x30, 0xC1, 0x98, 0x19, 0x80, 0xF0, 0x0F, 0x07, 0xFE, 0x06, 0x00, 0x60, 0x7F, 0xE0, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, +/* 0xA6 */ 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC, +/* 0xA7 */ 0x0F, 0x03, 0xF0, 0xE7, 0x18, 0x63, 0x0C, 0x70, 0x07, 0x03, 0xF8, 0xC3, 0x98, 0x3B, 0x03, 0xF0, 0x37, 0x06, 0x78, 0xC7, 0xB0, 0x7C, 0x03, 0x80, 0x39, 0x83, 0x30, 0x67, 0x1C, 0x7F, 0x07, 0xC0, +/* 0xA8 */ 0xCF, 0x30, +/* 0xA9 */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xE3, 0x1C, 0x73, 0xF3, 0x99, 0x86, 0x6C, 0xC1, 0x8F, 0x30, 0x03, 0xCC, 0x00, 0xF3, 0x00, 0x3C, 0xC1, 0x8D, 0x98, 0x66, 0x77, 0xF3, 0x8E, 0x79, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00, +/* 0xAA */ 0x79, 0x08, 0x11, 0xEE, 0x50, 0xA3, 0x3B, 0x00, 0x03, 0xF8, +/* 0xAB */ 0x21, 0x63, 0xE7, 0x84, 0x84, 0xE7, 0x63, 0x21, +/* 0xAC */ 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, +/* 0xAD */ 0xFF, 0xF0, +/* 0xAE */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xFF, 0x1C, 0x7F, 0xF3, 0x9B, 0x04, 0x6C, 0xC1, 0x8F, 0x30, 0x43, 0xCF, 0xF0, 0xF3, 0xFC, 0x3C, 0xC1, 0x0D, 0xB0, 0x66, 0x7C, 0x1B, 0x8F, 0x07, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00, +/* 0xAF */ 0xFF, 0xF0, +/* 0xB0 */ 0x38, 0xFB, 0x1C, 0x18, 0x38, 0xDF, 0x1C, +/* 0xB1 */ 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x7F, 0xE7, 0xFE, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, +/* 0xB2 */ 0x7D, 0x8F, 0x18, 0x30, 0xC6, 0x18, 0x60, 0xFF, 0xFC, +/* 0xB3 */ 0x7D, 0x8F, 0x18, 0x31, 0x80, 0xC1, 0xE3, 0xC6, 0xF8, +/* 0xB4 */ 0x3B, 0x99, 0x80, +/* 0xB5 */ 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x1C, 0xE3, 0xCF, 0xEF, 0xFC, 0x7C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00, +/* 0xB6 */ 0x1F, 0xE7, 0xFD, 0xF3, 0x7E, 0x6F, 0xCD, 0xF9, 0xBF, 0x37, 0xE6, 0x7C, 0xCF, 0x98, 0xF3, 0x06, 0x60, 0xCC, 0x19, 0x83, 0x30, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x60, 0xCC, +/* 0xB7 */ 0xF0, +/* 0xB8 */ 0x10, 0xF0, 0xE3, 0x78, +/* 0xB9 */ 0x2F, 0xB6, 0xDB, 0x6C, +/* 0xBA */ 0x79, 0x38, 0x61, 0x86, 0x1C, 0xDE, 0x00, 0x0F, 0xC0, +/* 0xBB */ 0x88, 0xC6, 0xE7, 0x21, 0x21, 0xE7, 0xC6, 0x88, +/* 0xBC */ 0x20, 0x08, 0x30, 0x0C, 0x38, 0x04, 0x0C, 0x06, 0x06, 0x02, 0x03, 0x02, 0x01, 0x81, 0x00, 0xC1, 0x06, 0x61, 0x87, 0x30, 0x83, 0x80, 0xC2, 0xC0, 0x42, 0x60, 0x43, 0x30, 0x21, 0xFC, 0x20, 0x0C, 0x30, 0x06, 0x10, 0x03, 0x00, +/* 0xBD */ 0x20, 0x00, 0x08, 0x02, 0x06, 0x01, 0x83, 0x80, 0x40, 0x60, 0x20, 0x18, 0x18, 0x06, 0x04, 0x01, 0x83, 0x00, 0x61, 0x9F, 0x98, 0x4E, 0x76, 0x33, 0x0C, 0x08, 0x03, 0x04, 0x03, 0x83, 0x01, 0x80, 0x81, 0x80, 0x60, 0xC0, 0x30, 0x3F, 0xC8, 0x0F, 0xF0, +/* 0xBE */ 0x7C, 0x00, 0x18, 0xC0, 0x43, 0x18, 0x18, 0x03, 0x02, 0x00, 0x60, 0xC0, 0x30, 0x10, 0x01, 0x84, 0x00, 0x31, 0x80, 0xC6, 0x20, 0xD8, 0xC8, 0x39, 0xF1, 0x07, 0x00, 0x41, 0x60, 0x18, 0x4C, 0x02, 0x11, 0x80, 0x83, 0xF8, 0x10, 0x06, 0x04, 0x00, 0xC1, 0x00, 0x18, +/* 0xBF */ 0x0C, 0x06, 0x00, 0x00, 0x00, 0xC0, 0x60, 0x60, 0x30, 0x30, 0x38, 0x38, 0x18, 0x0C, 0x06, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00, +/* 0xC0 */ 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 0xC1 */ 0x01, 0xC0, 0x0C, 0x00, 0x20, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 0xC2 */ 0x07, 0x00, 0x3E, 0x01, 0x8C, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 0xC3 */ 0x0E, 0x40, 0x7F, 0x01, 0x98, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30, +/* 0xC4 */ 0x0C, 0xC0, 0x33, 0x00, 0x00, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0xFC, 0x03, 0x30, 0x0C, 0xC0, 0x73, 0x81, 0x86, 0x06, 0x18, 0x38, 0x70, 0xC0, 0xC3, 0xFF, 0x1F, 0xFE, 0x60, 0x19, 0x80, 0x6E, 0x01, 0xF0, 0x03, 0xC0, 0x0C, +/* 0xC5 */ 0x03, 0x00, 0x1E, 0x00, 0xEC, 0x03, 0x30, 0x0F, 0xC0, 0x1E, 0x00, 0x78, 0x01, 0xE0, 0x07, 0x80, 0x3F, 0x00, 0xCC, 0x03, 0x30, 0x1C, 0xE0, 0x61, 0x81, 0x86, 0x0E, 0x1C, 0x30, 0x30, 0xFF, 0xC7, 0xFF, 0x98, 0x06, 0x60, 0x1B, 0x80, 0x7C, 0x00, 0xF0, 0x03, +/* 0xC6 */ 0x01, 0xFF, 0xFC, 0x07, 0xFF, 0xF0, 0x31, 0x80, 0x00, 0xC6, 0x00, 0x07, 0x18, 0x00, 0x18, 0x60, 0x00, 0x61, 0x80, 0x03, 0x86, 0x00, 0x0C, 0x1F, 0xF8, 0x70, 0x7F, 0xE1, 0x81, 0x80, 0x07, 0xFE, 0x00, 0x3F, 0xF8, 0x00, 0xC0, 0x60, 0x07, 0x01, 0x80, 0x1C, 0x06, 0x00, 0x60, 0x1F, 0xFF, 0x80, 0x7F, 0xF0, +/* 0xC7 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x66, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x01, 0xDC, 0x03, 0x1C, 0x1E, 0x1F, 0xF8, 0x0F, 0xC0, 0x08, 0x00, 0x1E, 0x00, 0x0C, 0x01, 0x18, 0x01, 0xE0, 0x00, +/* 0xC8 */ 0x1C, 0x00, 0xC0, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, +/* 0xC9 */ 0x07, 0x00, 0x60, 0x0C, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, +/* 0xCA */ 0x0E, 0x01, 0xF0, 0x31, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, +/* 0xCB */ 0x19, 0x81, 0x98, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFE, 0xFF, 0xEC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0, +/* 0xCC */ 0xE7, 0x10, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, +/* 0xCD */ 0x36, 0xC0, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, +/* 0xCE */ 0x39, 0xFC, 0x40, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, +/* 0xCF */ 0xC7, 0x8C, 0x01, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x00, +/* 0xD0 */ 0x7F, 0xE0, 0xFF, 0xE1, 0x80, 0xE3, 0x00, 0xE6, 0x00, 0xCC, 0x01, 0xD8, 0x01, 0xB0, 0x03, 0xFE, 0x07, 0xFC, 0x0D, 0x80, 0x1B, 0x00, 0x36, 0x00, 0x6C, 0x01, 0x98, 0x07, 0x30, 0x1C, 0x7F, 0xF0, 0xFF, 0xC0, +/* 0xD1 */ 0x08, 0xC0, 0xFE, 0x05, 0xE0, 0x00, 0x0E, 0x01, 0xF0, 0x0F, 0xC0, 0x7E, 0x03, 0xD8, 0x1E, 0xE0, 0xF3, 0x07, 0x9C, 0x3C, 0x61, 0xE1, 0x8F, 0x0E, 0x78, 0x33, 0xC1, 0xDE, 0x06, 0xF0, 0x1F, 0x80, 0xFC, 0x03, 0xE0, 0x1C, +/* 0xD2 */ 0x07, 0x00, 0x01, 0x80, 0x00, 0x60, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80, +/* 0xD3 */ 0x00, 0xE0, 0x00, 0x60, 0x00, 0x40, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80, +/* 0xD4 */ 0x03, 0xC0, 0x01, 0xE0, 0x01, 0x98, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80, +/* 0xD5 */ 0x07, 0x20, 0x03, 0xF0, 0x01, 0x38, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80, +/* 0xD6 */ 0x06, 0x30, 0x03, 0x18, 0x00, 0x00, 0x00, 0xFE, 0x01, 0xFF, 0xC1, 0xE0, 0xF0, 0xC0, 0x18, 0xC0, 0x06, 0x60, 0x03, 0x60, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x06, 0xC0, 0x06, 0x60, 0x03, 0x18, 0x03, 0x0F, 0x07, 0x83, 0xFF, 0x80, 0x7F, 0x00, +/* 0xD7 */ 0x81, 0xC3, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0xC3, 0x81, +/* 0xD8 */ 0x07, 0xF0, 0x8F, 0xFE, 0x8F, 0x07, 0xC6, 0x00, 0xE6, 0x00, 0xF3, 0x00, 0xDF, 0x00, 0xC7, 0x80, 0xC3, 0xC0, 0xC1, 0xE0, 0xC0, 0xF0, 0xC0, 0x78, 0xC0, 0x3E, 0xC0, 0x33, 0xC0, 0x19, 0xC0, 0x1C, 0xF8, 0x3C, 0xDF, 0xF8, 0x43, 0xF8, 0x00, +/* 0xD9 */ 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0, +/* 0xDA */ 0x03, 0x80, 0x18, 0x01, 0x80, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0, +/* 0xDB */ 0x07, 0x00, 0x7C, 0x06, 0x20, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0, +/* 0xDC */ 0x0C, 0xC0, 0x66, 0x00, 0x01, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0x01, 0xDC, 0x1C, 0x7F, 0xC1, 0xF8, 0x00, +/* 0xDD */ 0x01, 0x80, 0x0C, 0x00, 0x60, 0x00, 0x00, 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, +/* 0xDE */ 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xF8, 0xFF, 0xEC, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x6F, 0xFE, 0xFF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, +/* 0xDF */ 0x1F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0D, 0x81, 0xB0, 0x66, 0x38, 0xC7, 0xD8, 0x1B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x3E, 0x0E, 0xCF, 0x99, 0xE0, +/* 0xE0 */ 0x1C, 0x00, 0xC0, 0x06, 0x00, 0x20, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7, +/* 0xE1 */ 0x07, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7, +/* 0xE2 */ 0x0C, 0x01, 0xE0, 0x1B, 0x03, 0x30, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7, +/* 0xE3 */ 0x19, 0x83, 0xF0, 0x27, 0x00, 0x00, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7, +/* 0xE4 */ 0x19, 0x81, 0x98, 0x00, 0x00, 0x00, 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70, +/* 0xE5 */ 0x0E, 0x01, 0xF0, 0x1B, 0x81, 0xB8, 0x1F, 0x00, 0xE0, 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70, +/* 0xE6 */ 0x3F, 0x1F, 0x0F, 0xF7, 0xF3, 0x87, 0xC3, 0x60, 0x70, 0x30, 0x0C, 0x06, 0x3F, 0xFF, 0xDF, 0xFF, 0xFF, 0x06, 0x00, 0xC0, 0xC0, 0x18, 0x3C, 0x0F, 0x8F, 0xC7, 0x3F, 0x9F, 0xE3, 0xC1, 0xF0, +/* 0xE7 */ 0x1F, 0x0F, 0xE7, 0x1D, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x02, 0x00, 0xE0, 0x0C, 0x23, 0x07, 0x80, +/* 0xE8 */ 0x1C, 0x01, 0x80, 0x18, 0x01, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0, +/* 0xE9 */ 0x03, 0x00, 0xC0, 0x30, 0x04, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0, +/* 0xEA */ 0x0C, 0x03, 0xC0, 0x6C, 0x18, 0x80, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0, +/* 0xEB */ 0x31, 0x86, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x03, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x0C, 0x01, 0xC0, 0xDC, 0x31, 0xFC, 0x1F, 0x00, +/* 0xEC */ 0xC6, 0x31, 0x06, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, +/* 0xED */ 0x39, 0x99, 0x80, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x80, +/* 0xEE */ 0x71, 0xED, 0xA3, 0x00, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, +/* 0xEF */ 0xCF, 0x30, 0x00, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, +/* 0xF0 */ 0x20, 0x07, 0xE0, 0x70, 0x3B, 0x00, 0x30, 0x3F, 0x0F, 0xF3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0, +/* 0xF1 */ 0x19, 0x8F, 0xE2, 0x70, 0x00, 0x00, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30, +/* 0xF2 */ 0x1C, 0x01, 0x80, 0x18, 0x01, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0, +/* 0xF3 */ 0x07, 0x00, 0xC0, 0x30, 0x04, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0, +/* 0xF4 */ 0x0C, 0x03, 0xC0, 0xD8, 0x19, 0x80, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0, +/* 0xF5 */ 0x19, 0x87, 0xE0, 0x9C, 0x00, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0, +/* 0xF6 */ 0x31, 0x86, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x07, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00, +/* 0xF7 */ 0x06, 0x00, 0x60, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, +/* 0xF8 */ 0x1F, 0x27, 0xF5, 0xC7, 0x70, 0x7C, 0x17, 0x84, 0xF1, 0x1E, 0x43, 0xD0, 0x7C, 0x1D, 0xC7, 0x3F, 0xC9, 0xF0, +/* 0xF9 */ 0x18, 0x03, 0x00, 0x60, 0x08, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30, +/* 0xFA */ 0x03, 0x01, 0x80, 0x60, 0x30, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30, +/* 0xFB */ 0x0C, 0x07, 0x81, 0x20, 0xCC, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30, +/* 0xFC */ 0x31, 0x8C, 0x60, 0x00, 0x00, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0, +/* 0xFD */ 0x03, 0x80, 0x60, 0x18, 0x06, 0x00, 0x01, 0xC0, 0xD8, 0x1B, 0x06, 0x70, 0xC6, 0x18, 0xC6, 0x18, 0xC1, 0x98, 0x36, 0x06, 0xC0, 0x78, 0x0E, 0x01, 0xC0, 0x30, 0x06, 0x01, 0xC0, 0xF0, 0x1C, 0x00, +/* 0xFE */ 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xCF, 0x8F, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00, +/* 0xFF */ 0x19, 0x83, 0x30, 0x00, 0x00, 0x0E, 0x06, 0xC0, 0xD8, 0x33, 0x86, 0x30, 0xC6, 0x30, 0xC6, 0x0C, 0xC1, 0xB0, 0x36, 0x03, 0xC0, 0x70, 0x0E, 0x01, 0x80, 0x30, 0x0E, 0x07, 0x80, 0xE0, 0x00, +}; + +const GFXglyph FreeSans12pt_Win1252Glyphs[] PROGMEM = { +/* 0x01 */ { 0, 19, 20, 21, 1, -17 }, +/* 0x02 */ { 48, 19, 20, 21, 1, -17 }, +/* 0x03 */ { 96, 21, 20, 23, 1, -17 }, +/* 0x04 */ { 149, 21, 20, 23, 1, -17 }, +/* 0x05 */ { 202, 20, 20, 22, 1, -17 }, +/* 0x06 */ { 252, 20, 20, 22, 1, -17 }, +/* 0x07 */ { 302, 0, 0, 8, 0, 0 }, +/* 0x08 */ { 302, 23, 20, 25, 1, -17 }, +/* 0x09 */ { 360, 23, 16, 25, 1, -16 }, +/* 0x0A */ { 406, 0, 0, 8, 0, 0 }, +/* 0x0B */ { 406, 21, 20, 23, 1, -17 }, +/* 0x0C */ { 459, 19, 18, 21, 1, -15 }, +/* 0x0D */ { 502, 0, 0, 8, 0, 0 }, +/* 0x0E */ { 502, 20, 20, 22, 1, -17 }, +/* 0x0F */ { 552, 21, 21, 23, 1, -18 }, +/* 0x10 */ { 608, 19, 20, 21, 1, -17 }, +/* 0x11 */ { 656, 21, 20, 23, 1, -17 }, +/* 0x12 */ { 709, 20, 20, 22, 1, -17 }, +/* 0x13 */ { 759, 21, 20, 23, 1, -17 }, +/* 0x14 */ { 812, 21, 20, 23, 1, -17 }, +/* 0x15 */ { 865, 22, 20, 24, 1, -17 }, +/* 0x16 */ { 920, 16, 20, 18, 1, -17 }, +/* 0x17 */ { 960, 21, 20, 23, 1, -17 }, +/* 0x18 */ { 1013, 23, 20, 25, 1, -17 }, +/* 0x19 */ { 1071, 21, 20, 23, 1, -17 }, +/* 0x1A */ { 1124, 15, 19, 17, 1, -16 }, +/* 0x1B */ { 1160, 24, 21, 26, 1, -18 }, +/* 0x1C */ { 1223, 21, 20, 23, 1, -17 }, +/* 0x1D */ { 1276, 21, 21, 23, 1, -18 }, +/* 0x1E */ { 1332, 20, 20, 22, 1, -17 }, +/* 0x1F */ { 1382, 15, 20, 17, 1, -17 }, +/* ' ' 0x20 */ { 1420, 0, 0, 6, 0, 0 }, +/* '!' 0x21 */ { 1420, 2, 18, 8, 3, -16 }, +/* '"' 0x22 */ { 1425, 6, 6, 8, 1, -15 }, +/* '#' 0x23 */ { 1430, 13, 16, 13, 0, -14 }, +/* '$' 0x24 */ { 1456, 11, 20, 13, 1, -16 }, +/* '%' 0x25 */ { 1484, 20, 17, 21, 1, -15 }, +/* '&' 0x26 */ { 1527, 14, 17, 16, 1, -15 }, +/* ''' 0x27 */ { 1557, 2, 6, 5, 1, -15 }, +/* '(' 0x28 */ { 1559, 5, 23, 8, 2, -16 }, +/* ')' 0x29 */ { 1574, 5, 23, 8, 1, -16 }, +/* '*' 0x2A */ { 1589, 7, 7, 9, 1, -16 }, +/* '+' 0x2B */ { 1596, 10, 11, 14, 2, -9 }, +/* ',' 0x2C */ { 1610, 2, 6, 7, 2, 0 }, +/* '-' 0x2D */ { 1612, 6, 2, 8, 1, -6 }, +/* '.' 0x2E */ { 1614, 2, 2, 6, 2, 0 }, +/* '/' 0x2F */ { 1615, 7, 18, 7, 0, -16 }, +/* '0' 0x30 */ { 1631, 11, 17, 13, 1, -15 }, +/* '1' 0x31 */ { 1655, 5, 17, 13, 3, -15 }, +/* '2' 0x32 */ { 1666, 11, 17, 13, 1, -15 }, +/* '3' 0x33 */ { 1690, 11, 17, 13, 1, -15 }, +/* '4' 0x34 */ { 1714, 11, 17, 13, 1, -15 }, +/* '5' 0x35 */ { 1738, 11, 17, 13, 1, -15 }, +/* '6' 0x36 */ { 1762, 11, 17, 13, 1, -15 }, +/* '7' 0x37 */ { 1786, 11, 17, 13, 1, -15 }, +/* '8' 0x38 */ { 1810, 11, 17, 13, 1, -15 }, +/* '9' 0x39 */ { 1834, 11, 17, 13, 1, -15 }, +/* ':' 0x3A */ { 1858, 2, 13, 6, 2, -11 }, +/* ';' 0x3B */ { 1862, 2, 16, 6, 2, -10 }, +/* '<' 0x3C */ { 1866, 12, 11, 14, 1, -9 }, +/* '=' 0x3D */ { 1883, 12, 6, 14, 1, -7 }, +/* '>' 0x3E */ { 1892, 12, 11, 14, 1, -9 }, +/* '?' 0x3F */ { 1909, 10, 18, 13, 2, -16 }, +/* '@' 0x40 */ { 1932, 22, 21, 24, 1, -16 }, +/* 'A' 0x41 */ { 1990, 14, 18, 16, 1, -16 }, +/* 'B' 0x42 */ { 2022, 13, 18, 16, 2, -16 }, +/* 'C' 0x43 */ { 2052, 15, 18, 17, 1, -16 }, +/* 'D' 0x44 */ { 2086, 14, 18, 17, 2, -16 }, +/* 'E' 0x45 */ { 2118, 12, 18, 15, 2, -16 }, +/* 'F' 0x46 */ { 2145, 11, 18, 14, 2, -16 }, +/* 'G' 0x47 */ { 2170, 16, 18, 18, 1, -16 }, +/* 'H' 0x48 */ { 2206, 13, 18, 17, 2, -16 }, +/* 'I' 0x49 */ { 2236, 2, 18, 7, 2, -16 }, +/* 'J' 0x4A */ { 2241, 9, 18, 13, 1, -16 }, +/* 'K' 0x4B */ { 2262, 13, 18, 16, 2, -16 }, +/* 'L' 0x4C */ { 2292, 10, 18, 14, 2, -16 }, +/* 'M' 0x4D */ { 2315, 16, 18, 20, 2, -16 }, +/* 'N' 0x4E */ { 2351, 13, 18, 18, 2, -16 }, +/* 'O' 0x4F */ { 2381, 17, 18, 19, 1, -16 }, +/* 'P' 0x50 */ { 2420, 12, 18, 16, 2, -16 }, +/* 'Q' 0x51 */ { 2447, 17, 19, 19, 1, -16 }, +/* 'R' 0x52 */ { 2488, 14, 18, 17, 2, -16 }, +/* 'S' 0x53 */ { 2520, 14, 18, 16, 1, -16 }, +/* 'T' 0x54 */ { 2552, 12, 18, 15, 1, -16 }, +/* 'U' 0x55 */ { 2579, 13, 18, 17, 2, -16 }, +/* 'V' 0x56 */ { 2609, 14, 18, 15, 1, -16 }, +/* 'W' 0x57 */ { 2641, 22, 18, 22, 0, -16 }, +/* 'X' 0x58 */ { 2691, 14, 18, 16, 1, -16 }, +/* 'Y' 0x59 */ { 2723, 14, 18, 16, 1, -16 }, +/* 'Z' 0x5A */ { 2755, 13, 18, 15, 1, -16 }, +/* '[' 0x5B */ { 2785, 4, 23, 7, 2, -16 }, +/* '\' 0x5C */ { 2797, 7, 18, 7, 0, -16 }, +/* ']' 0x5D */ { 2813, 4, 23, 7, 1, -16 }, +/* '^' 0x5E */ { 2825, 9, 9, 11, 1, -15 }, +/* '_' 0x5F */ { 2836, 15, 1, 13, -1, 5 }, +/* '`' 0x60 */ { 2838, 5, 4, 6, 1, -16 }, +/* 'a' 0x61 */ { 2841, 12, 13, 13, 1, -11 }, +/* 'b' 0x62 */ { 2861, 12, 18, 13, 1, -16 }, +/* 'c' 0x63 */ { 2888, 10, 13, 12, 1, -11 }, +/* 'd' 0x64 */ { 2905, 11, 18, 13, 1, -16 }, +/* 'e' 0x65 */ { 2930, 11, 13, 13, 1, -11 }, +/* 'f' 0x66 */ { 2948, 5, 18, 7, 1, -16 }, +/* 'g' 0x67 */ { 2960, 11, 18, 13, 1, -11 }, +/* 'h' 0x68 */ { 2985, 10, 18, 13, 1, -16 }, +/* 'i' 0x69 */ { 3008, 2, 18, 5, 2, -16 }, +/* 'j' 0x6A */ { 3013, 4, 23, 6, 0, -16 }, +/* 'k' 0x6B */ { 3025, 10, 18, 12, 1, -16 }, +/* 'l' 0x6C */ { 3048, 2, 18, 5, 1, -16 }, +/* 'm' 0x6D */ { 3053, 17, 13, 19, 1, -11 }, +/* 'n' 0x6E */ { 3081, 10, 13, 13, 1, -11 }, +/* 'o' 0x6F */ { 3098, 11, 13, 13, 1, -11 }, +/* 'p' 0x70 */ { 3116, 12, 17, 13, 1, -11 }, +/* 'q' 0x71 */ { 3142, 11, 17, 13, 1, -11 }, +/* 'r' 0x72 */ { 3166, 6, 13, 8, 1, -11 }, +/* 's' 0x73 */ { 3176, 10, 13, 12, 1, -11 }, +/* 't' 0x74 */ { 3193, 5, 16, 7, 1, -14 }, +/* 'u' 0x75 */ { 3203, 10, 13, 13, 1, -11 }, +/* 'v' 0x76 */ { 3220, 11, 13, 12, 0, -11 }, +/* 'w' 0x77 */ { 3238, 17, 13, 17, 0, -11 }, +/* 'x' 0x78 */ { 3266, 10, 13, 11, 1, -11 }, +/* 'y' 0x79 */ { 3283, 11, 18, 11, 0, -11 }, +/* 'z' 0x7A */ { 3308, 10, 13, 12, 1, -11 }, +/* '{' 0x7B */ { 3325, 5, 23, 8, 1, -16 }, +/* '|' 0x7C */ { 3340, 2, 23, 6, 2, -16 }, +/* '}' 0x7D */ { 3346, 5, 23, 8, 2, -16 }, +/* '~' 0x7E */ { 3361, 10, 5, 12, 1, -9 }, +/* 0x7F */ { 3368, 0, 0, 0, 0, 0 }, +/* 0x80 */ { 3368, 14, 17, 16, 1, -15 }, +/* 0x81 */ { 3398, 0, 0, 8, 0, 0 }, +/* 0x82 */ { 3398, 2, 5, 6, 2, 0 }, +/* 0x83 */ { 3400, 6, 23, 7, 0, -16 }, +/* 0x84 */ { 3418, 6, 5, 10, 2, 0 }, +/* 0x85 */ { 3422, 12, 2, 16, 2, 0 }, +/* 0x86 */ { 3425, 10, 21, 13, 2, -15 }, +/* 0x87 */ { 3452, 10, 20, 13, 2, -15 }, +/* 0x88 */ { 3477, 7, 4, 8, 0, -16 }, +/* 0x89 */ { 3481, 23, 18, 24, 0, -16 }, +/* 0x8A */ { 3533, 14, 21, 16, 1, -19 }, +/* 0x8B */ { 3570, 3, 8, 6, 1, -9 }, +/* 0x8C */ { 3573, 22, 18, 24, 1, -16 }, +/* 0x8D */ { 3623, 0, 0, 8, 0, 0 }, +/* 0x8E */ { 3623, 13, 21, 15, 1, -19 }, +/* 0x8F */ { 3658, 0, 0, 8, 0, 0 }, +/* 0x90 */ { 3658, 0, 0, 8, 0, 0 }, +/* 0x91 */ { 3658, 2, 6, 6, 2, -16 }, +/* 0x92 */ { 3660, 2, 6, 6, 2, -16 }, +/* 0x93 */ { 3662, 6, 6, 10, 2, -16 }, +/* 0x94 */ { 3667, 6, 6, 10, 2, -16 }, +/* 0x95 */ { 3672, 6, 6, 10, 2, -9 }, +/* 0x96 */ { 3677, 10, 2, 12, 1, -6 }, +/* 0x97 */ { 3680, 22, 2, 24, 1, -6 }, +/* 0x98 */ { 3686, 7, 3, 8, 0, -16 }, +/* 0x99 */ { 3689, 22, 13, 24, 2, -16 }, +/* 0x9A */ { 3725, 10, 18, 12, 1, -16 }, +/* 0x9B */ { 3748, 3, 8, 6, 2, -8 }, +/* 0x9C */ { 3751, 20, 13, 22, 1, -11 }, +/* 0x9D */ { 3784, 0, 0, 8, 0, 0 }, +/* 0x9E */ { 3784, 10, 18, 12, 1, -16 }, +/* 0x9F */ { 3807, 14, 21, 16, 1, -19 }, +/* 0xA0 */ { 3844, 0, 0, 7, 0, 0 }, +/* 0xA1 */ { 3844, 2, 18, 8, 3, -11 }, +/* 0xA2 */ { 3849, 11, 17, 13, 1, -13 }, +/* 0xA3 */ { 3873, 12, 18, 13, 0, -16 }, +/* 0xA4 */ { 3900, 9, 9, 13, 2, -11 }, +/* 0xA5 */ { 3911, 12, 17, 13, 1, -15 }, +/* 0xA6 */ { 3937, 2, 23, 6, 2, -16 }, +/* 0xA7 */ { 3943, 11, 23, 13, 1, -16 }, +/* 0xA8 */ { 3975, 6, 2, 8, 1, -15 }, +/* 0xA9 */ { 3977, 18, 17, 19, 1, -15 }, +/* 0xAA */ { 4016, 7, 11, 9, 1, -16 }, +/* 0xAB */ { 4026, 8, 8, 12, 2, -9 }, +/* 0xAC */ { 4034, 12, 6, 14, 1, -7 }, +/* 0xAD */ { 4043, 6, 2, 8, 1, -6 }, +/* 0xAE */ { 4045, 18, 17, 19, 1, -15 }, +/* 0xAF */ { 4084, 6, 2, 8, 1, -15 }, +/* 0xB0 */ { 4086, 7, 8, 15, 4, -15 }, +/* 0xB1 */ { 4093, 12, 15, 14, 1, -13 }, +/* 0xB2 */ { 4116, 7, 10, 8, 1, -17 }, +/* 0xB3 */ { 4125, 7, 10, 8, 1, -17 }, +/* 0xB4 */ { 4134, 5, 4, 8, 2, -16 }, +/* 0xB5 */ { 4137, 12, 17, 13, 2, -11 }, +/* 0xB6 */ { 4163, 11, 21, 13, 2, -16 }, +/* 0xB7 */ { 4192, 2, 2, 6, 2, -6 }, +/* 0xB8 */ { 4193, 6, 5, 8, 1, 2 }, +/* 0xB9 */ { 4197, 3, 10, 8, 3, -18 }, +/* 0xBA */ { 4201, 6, 11, 9, 1, -16 }, +/* 0xBB */ { 4210, 8, 8, 12, 2, -8 }, +/* 0xBC */ { 4218, 17, 17, 21, 3, -15 }, +/* 0xBD */ { 4255, 18, 18, 21, 3, -16 }, +/* 0xBE */ { 4296, 19, 18, 21, 1, -16 }, +/* 0xBF */ { 4339, 9, 18, 13, 3, -11 }, +/* 0xC0 */ { 4360, 14, 22, 16, 1, -20 }, +/* 0xC1 */ { 4399, 14, 22, 16, 1, -20 }, +/* 0xC2 */ { 4438, 14, 22, 16, 1, -20 }, +/* 0xC3 */ { 4477, 14, 22, 16, 1, -20 }, +/* 0xC4 */ { 4516, 14, 21, 16, 1, -19 }, +/* 0xC5 */ { 4553, 14, 24, 16, 1, -22 }, +/* 0xC6 */ { 4595, 22, 18, 24, 0, -16 }, +/* 0xC7 */ { 4645, 15, 23, 17, 1, -16 }, +/* 0xC8 */ { 4689, 12, 22, 15, 2, -20 }, +/* 0xC9 */ { 4722, 12, 22, 15, 2, -20 }, +/* 0xCA */ { 4755, 12, 22, 15, 2, -20 }, +/* 0xCB */ { 4788, 12, 21, 15, 2, -19 }, +/* 0xCC */ { 4820, 4, 22, 7, 0, -20 }, +/* 0xCD */ { 4831, 4, 22, 7, 1, -20 }, +/* 0xCE */ { 4842, 6, 22, 7, 0, -20 }, +/* 0xCF */ { 4859, 7, 21, 7, 0, -19 }, +/* 0xD0 */ { 4878, 15, 18, 17, 1, -16 }, +/* 0xD1 */ { 4912, 13, 22, 18, 2, -20 }, +/* 0xD2 */ { 4948, 17, 22, 19, 1, -20 }, +/* 0xD3 */ { 4995, 17, 22, 19, 1, -20 }, +/* 0xD4 */ { 5042, 17, 22, 19, 1, -20 }, +/* 0xD5 */ { 5089, 17, 22, 19, 1, -20 }, +/* 0xD6 */ { 5136, 17, 21, 19, 1, -19 }, +/* 0xD7 */ { 5181, 8, 9, 14, 3, -8 }, +/* 0xD8 */ { 5190, 17, 18, 19, 1, -16 }, +/* 0xD9 */ { 5229, 13, 22, 17, 2, -20 }, +/* 0xDA */ { 5265, 13, 22, 17, 2, -20 }, +/* 0xDB */ { 5301, 13, 22, 17, 2, -20 }, +/* 0xDC */ { 5337, 13, 21, 17, 2, -19 }, +/* 0xDD */ { 5372, 14, 22, 16, 1, -20 }, +/* 0xDE */ { 5411, 12, 18, 15, 2, -16 }, +/* 0xDF */ { 5438, 11, 18, 14, 2, -16 }, +/* 0xE0 */ { 5463, 12, 18, 13, 1, -16 }, +/* 0xE1 */ { 5490, 12, 18, 13, 1, -16 }, +/* 0xE2 */ { 5517, 12, 18, 13, 1, -16 }, +/* 0xE3 */ { 5544, 12, 18, 13, 1, -16 }, +/* 0xE4 */ { 5571, 12, 17, 13, 1, -15 }, +/* 0xE5 */ { 5597, 12, 19, 13, 1, -17 }, +/* 0xE6 */ { 5626, 19, 13, 21, 1, -11 }, +/* 0xE7 */ { 5657, 10, 18, 12, 1, -11 }, +/* 0xE8 */ { 5680, 11, 18, 13, 1, -16 }, +/* 0xE9 */ { 5705, 11, 18, 13, 1, -16 }, +/* 0xEA */ { 5730, 11, 18, 13, 1, -16 }, +/* 0xEB */ { 5755, 11, 17, 13, 1, -15 }, +/* 0xEC */ { 5779, 4, 18, 5, 1, -16 }, +/* 0xED */ { 5788, 5, 18, 5, 0, -16 }, +/* 0xEE */ { 5800, 6, 18, 6, 0, -16 }, +/* 0xEF */ { 5814, 6, 17, 6, 0, -15 }, +/* 0xF0 */ { 5827, 11, 18, 13, 1, -16 }, +/* 0xF1 */ { 5852, 10, 18, 13, 1, -16 }, +/* 0xF2 */ { 5875, 11, 18, 13, 1, -16 }, +/* 0xF3 */ { 5900, 11, 18, 13, 1, -16 }, +/* 0xF4 */ { 5925, 11, 18, 13, 1, -16 }, +/* 0xF5 */ { 5950, 11, 18, 13, 1, -16 }, +/* 0xF6 */ { 5975, 11, 17, 13, 1, -15 }, +/* 0xF7 */ { 5999, 12, 11, 14, 1, -9 }, +/* 0xF8 */ { 6016, 11, 13, 13, 1, -11 }, +/* 0xF9 */ { 6034, 10, 18, 13, 1, -16 }, +/* 0xFA */ { 6057, 10, 18, 13, 1, -16 }, +/* 0xFB */ { 6080, 10, 18, 13, 1, -16 }, +/* 0xFC */ { 6103, 10, 17, 13, 1, -15 }, +/* 0xFD */ { 6125, 11, 23, 11, 0, -16 }, +/* 0xFE */ { 6157, 12, 21, 13, 1, -15 }, +/* 0xFF */ { 6189, 11, 22, 11, 0, -15 }, +}; + +const GFXfont FreeSans12pt_Win1252 PROGMEM = { +(uint8_t*)FreeSans12pt_Win1252Bitmaps, +(GFXglyph*)FreeSans12pt_Win1252Glyphs, +0x01, 0xFF, 19 +}; diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/InkHUD/Applet.cpp index 362a50d16..1e89ebe1b 100644 --- a/src/graphics/niche/InkHUD/Applet.cpp +++ b/src/graphics/niche/InkHUD/Applet.cpp @@ -8,8 +8,9 @@ using namespace NicheGraphics; -InkHUD::AppletFont InkHUD::Applet::fontLarge; // General purpose font. Set by setDefaultFonts -InkHUD::AppletFont InkHUD::Applet::fontSmall; // General purpose font. Set by setDefaultFonts +InkHUD::AppletFont InkHUD::Applet::fontLarge; // General purpose fonts. Set in nicheGraphics.h +InkHUD::AppletFont InkHUD::Applet::fontMedium; +InkHUD::AppletFont InkHUD::Applet::fontSmall; constexpr float InkHUD::Applet::LOGO_ASPECT_RATIO; // Ratio of the Meshtastic logo InkHUD::Applet::Applet() : GFX(0, 0) diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h index c6a8a8aad..802186e6e 100644 --- a/src/graphics/niche/InkHUD/Applet.h +++ b/src/graphics/niche/InkHUD/Applet.h @@ -95,7 +95,7 @@ class Applet : public GFX static uint16_t getHeaderHeight(); // How tall the "standard" applet header is - static AppletFont fontSmall, fontLarge; // The general purpose fonts, used by all applets + static AppletFont fontSmall, fontMedium, fontLarge; // The general purpose fonts, used by all applets const char *name = nullptr; // Shown in applet selection menu. Also used as an identifier by InkHUD::getSystemApplet diff --git a/src/graphics/niche/InkHUD/AppletFont.h b/src/graphics/niche/InkHUD/AppletFont.h index 67348b8d3..e1fe37974 100644 --- a/src/graphics/niche/InkHUD/AppletFont.h +++ b/src/graphics/niche/InkHUD/AppletFont.h @@ -61,20 +61,26 @@ class AppletFont // Line padding has been adjusted manually, to compensate for a few *extra tall* diacritics // Central European +#include "graphics/niche/Fonts/FreeSans12pt_Win1250.h" #include "graphics/niche/Fonts/FreeSans6pt_Win1250.h" #include "graphics/niche/Fonts/FreeSans9pt_Win1250.h" +#define FREESANS_12PT_WIN1250 InkHUD::AppletFont(FreeSans12pt_Win1250, InkHUD::AppletFont::WINDOWS_1250, -3, 1) #define FREESANS_9PT_WIN1250 InkHUD::AppletFont(FreeSans9pt_Win1250, InkHUD::AppletFont::WINDOWS_1250, -1, -1) #define FREESANS_6PT_WIN1250 InkHUD::AppletFont(FreeSans6pt_Win1250, InkHUD::AppletFont::WINDOWS_1250, -1, -2) // Cyrillic +#include "graphics/niche/Fonts/FreeSans12pt_Win1251.h" #include "graphics/niche/Fonts/FreeSans6pt_Win1251.h" #include "graphics/niche/Fonts/FreeSans9pt_Win1251.h" +#define FREESANS_12PT_WIN1251 InkHUD::AppletFont(FreeSans12pt_Win1251, InkHUD::AppletFont::WINDOWS_1251, -3, 1) #define FREESANS_9PT_WIN1251 InkHUD::AppletFont(FreeSans9pt_Win1251, InkHUD::AppletFont::WINDOWS_1251, -2, -1) #define FREESANS_6PT_WIN1251 InkHUD::AppletFont(FreeSans6pt_Win1251, InkHUD::AppletFont::WINDOWS_1251, -1, -2) // Western European +#include "graphics/niche/Fonts/FreeSans12pt_Win1252.h" #include "graphics/niche/Fonts/FreeSans6pt_Win1252.h" #include "graphics/niche/Fonts/FreeSans9pt_Win1252.h" +#define FREESANS_12PT_WIN1252 InkHUD::AppletFont(FreeSans12pt_Win1252, InkHUD::AppletFont::WINDOWS_1252, -3, 1) #define FREESANS_9PT_WIN1252 InkHUD::AppletFont(FreeSans9pt_Win1252, InkHUD::AppletFont::WINDOWS_1252, -2, -1) #define FREESANS_6PT_WIN1252 InkHUD::AppletFont(FreeSans6pt_Win1252, InkHUD::AppletFont::WINDOWS_1252, -1, -2) diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp index 7fa31b244..1b0bfa9d0 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp @@ -168,11 +168,11 @@ void InkHUD::NodeListApplet::onRender() // Define two lines of text for the card // We will center our text on these lines - uint16_t lineAY = cardTopY + (fontLarge.lineHeight() / 2); - uint16_t lineBY = cardTopY + fontLarge.lineHeight() + (fontSmall.lineHeight() / 2); + uint16_t lineAY = cardTopY + (fontMedium.lineHeight() / 2); + uint16_t lineBY = cardTopY + fontMedium.lineHeight() + (fontSmall.lineHeight() / 2); // Print the short name - setFont(fontLarge); + setFont(fontMedium); printAt(0, lineAY, shortName, LEFT, MIDDLE); // Print the distance @@ -182,8 +182,8 @@ void InkHUD::NodeListApplet::onRender() // If we have a direct connection to the node, draw the signal indicator if (hopsAway == 0 && signal != SIGNAL_UNKNOWN) { uint16_t signalW = getTextWidth("Xkm"); // Indicator should be similar width to distance label - uint16_t signalH = fontLarge.lineHeight() * 0.75; - int16_t signalY = lineAY + (fontLarge.lineHeight() / 2) - (fontLarge.lineHeight() * 0.75); + uint16_t signalH = fontMedium.lineHeight() * 0.75; + int16_t signalY = lineAY + (fontMedium.lineHeight() / 2) - (fontMedium.lineHeight() * 0.75); int16_t signalX = width() - signalW; drawSignalIndicator(signalX, signalY, signalW, signalH, signal); } diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h index 0abcad824..c2340027b 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h @@ -65,8 +65,8 @@ class NodeListApplet : public Applet, public MeshModule // Card Dimensions // - for rendering and for maxCards calc - const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards - const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card + uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards + uint16_t cardH = fontMedium.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp index d9a3bd2dd..ecaa7cea3 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp @@ -15,7 +15,7 @@ InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet") // This behavior assists manufacturers during mass production, and should not be modified without good reason if (!settings->tips.safeShutdownSeen) { meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); - fontTitle = fontLarge; + fontTitle = fontMedium; textLeft = xstr(APP_VERSION_SHORT); textRight = parseShortName(ourNode); textTitle = "Meshtastic"; @@ -52,6 +52,40 @@ void InkHUD::LogoApplet::onRender() setTextColor(WHITE); } +#ifdef USERPREFS_OEM_IMAGE_DATA // Custom boot screen, if defined in userPrefs.jsonc + + // Only show the custom screen at startup + // This allows us to draw the usual Meshtastic logo at shutdown + // The effect is similar to the two-stage userPrefs boot screen used by BaseUI + if (millis() < 10 * 1000UL) { + + // Draw the custom logo + const uint8_t logo[] = USERPREFS_OEM_IMAGE_DATA; + drawXBitmap(logoCX - (USERPREFS_OEM_IMAGE_WIDTH / 2), // Left + logoCY - (USERPREFS_OEM_IMAGE_HEIGHT / 2), // Top + logo, // XBM data + USERPREFS_OEM_IMAGE_WIDTH, // Width + USERPREFS_OEM_IMAGE_HEIGHT, // Height + inverted ? WHITE : BLACK // Color + ); + + // Select the largest font which will still comfortably fit the custom text + setFont(fontLarge); + if (getTextWidth(USERPREFS_OEM_TEXT) > 0.8 * width()) + setFont(fontMedium); + if (getTextWidth(USERPREFS_OEM_TEXT) > 0.8 * width()) + setFont(fontSmall); + + // Draw custom text below logo + int16_t logoB = logoCY + (USERPREFS_OEM_IMAGE_HEIGHT / 2); // Bottom of the logo + printAt(X(0.5), logoB + Y(0.1), USERPREFS_OEM_TEXT, CENTER, TOP); + + // Don't draw the normal boot screen, we've already drawn our custom version + return; + } + +#endif + drawLogo(logoCX, logoCY, logoW, logoH, inverted ? WHITE : BLACK); if (!textLeft.empty()) { @@ -116,7 +150,7 @@ void InkHUD::LogoApplet::onShutdown() textLeft = ""; textRight = ""; textTitle = parseShortName(ourNode); - fontTitle = fontLarge; + fontTitle = fontMedium; // This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete } diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h index f42b9dc2c..c84ee09e0 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h @@ -33,6 +33,7 @@ enum MenuAction { LAYOUT, TOGGLE_BATTERY_ICON, TOGGLE_NOTIFICATIONS, + TOGGLE_INVERT_COLOR, TOGGLE_12H_CLOCK, }; diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 69965972f..7876276a8 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -205,6 +205,15 @@ void InkHUD::MenuApplet::execute(MenuItem item) settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications; break; + case TOGGLE_INVERT_COLOR: + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) + config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; + else + config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + break; + case SET_RECENTS: // Set value of settings.recentlyActiveSeconds // Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file) @@ -214,7 +223,7 @@ void InkHUD::MenuApplet::execute(MenuItem item) case SHUTDOWN: LOG_INFO("Shutting down from menu"); - power->shutdown(); + shutdownAtMsec = millis(); // Menu is then sent to background via onShutdown break; @@ -316,6 +325,10 @@ void InkHUD::MenuApplet::showPage(MenuPage page) &settings->optionalFeatures.notifications)); items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, &settings->optionalFeatures.batteryIcon)); + + invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); + items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors)); + items.push_back( MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::OPTIONS, &config.display.use_12h_clock)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); @@ -654,11 +667,11 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t // ==================== std::string clockString = getTimeString(); if (clockString.length() > 0) { - setFont(fontLarge); + setFont(fontMedium); printAt(width / 2, top, clockString, CENTER, TOP); - height += fontLarge.lineHeight(); - height += fontLarge.lineHeight() * 0.1; // Padding below clock + height += fontMedium.lineHeight(); + height += fontMedium.lineHeight() * 0.1; // Padding below clock } // Stats diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h index 4c974672a..8f9280e6f 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h @@ -91,6 +91,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread } cm; Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu + + bool invertedColors = false; // Helper to display current state of config.display.displaymode in InkHUD options }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp index 3f51c7f88..09931f109 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp @@ -12,13 +12,13 @@ InkHUD::PairingApplet::PairingApplet() void InkHUD::PairingApplet::onRender() { // Header - setFont(fontLarge); + setFont(fontMedium); printAt(X(0.5), Y(0.25), "Bluetooth", CENTER, BOTTOM); setFont(fontSmall); printAt(X(0.5), Y(0.25), "Enter this code", CENTER, TOP); // Passkey - setFont(fontLarge); + setFont(fontMedium); printThick(X(0.5), Y(0.5), passkey.substr(0, 3) + " " + passkey.substr(3), 3, 2); // Device's bluetooth name, if it will fit diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp index 82a196cb1..ade44ab65 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp @@ -50,11 +50,11 @@ void InkHUD::TipsApplet::onRender() break; case Tip::FINISH_SETUP: { - setFont(fontLarge); + setFont(fontMedium); printAt(0, 0, "Tip: Finish Setup"); setFont(fontSmall); - int16_t cursorY = fontLarge.lineHeight() * 1.5; + int16_t cursorY = fontMedium.lineHeight() * 1.5; printAt(0, cursorY, "- connect antenna"); cursorY += fontSmall.lineHeight() * 1.2; @@ -80,7 +80,7 @@ void InkHUD::TipsApplet::onRender() } break; case Tip::SAFE_SHUTDOWN: { - setFont(fontLarge); + setFont(fontMedium); printAt(0, 0, "Tip: Shutdown"); setFont(fontSmall); @@ -88,29 +88,29 @@ void InkHUD::TipsApplet::onRender() shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n"; shutdown += "\n"; shutdown += "This ensures data is saved."; - printWrapped(0, fontLarge.lineHeight() * 1.5, width(), shutdown); + printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); } break; case Tip::CUSTOMIZATION: { - setFont(fontLarge); + setFont(fontMedium); printAt(0, 0, "Tip: Customization"); setFont(fontSmall); - printWrapped(0, fontLarge.lineHeight() * 1.5, width(), + printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more."); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); } break; case Tip::BUTTONS: { - setFont(fontLarge); + setFont(fontMedium); printAt(0, 0, "Tip: Buttons"); setFont(fontSmall); - int16_t cursorY = fontLarge.lineHeight() * 1.5; + int16_t cursorY = fontMedium.lineHeight() * 1.5; printAt(0, cursorY, "User Button"); cursorY += fontSmall.lineHeight() * 1.2; @@ -123,11 +123,11 @@ void InkHUD::TipsApplet::onRender() } break; case Tip::ROTATION: { - setFont(fontLarge); + setFont(fontMedium); printAt(0, 0, "Tip: Rotation"); setFont(fontSmall); - printWrapped(0, fontLarge.lineHeight() * 1.5, width(), + printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate."); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); @@ -155,7 +155,7 @@ void InkHUD::TipsApplet::renderWelcome() uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit); // Title size - setFont(fontLarge); + setFont(fontMedium); std::string title; if (width() >= 200) // Future proofing: hide if *tiny* display title = "meshtastic.org"; diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp index 17d724aee..7c6232f3b 100644 --- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp @@ -101,15 +101,25 @@ void InkHUD::AllMessageApplet::onRender() // Extra gap below the header int16_t textTop = headerDivY + padDivH; - // Determine size if printed large + // Attempt to print with fontLarge + uint32_t textHeight; setFont(fontLarge); - uint32_t textHeight = getWrappedTextHeight(0, width(), text); + textHeight = getWrappedTextHeight(0, width(), text); + if (textHeight <= (uint32_t)height()) { + printWrapped(0, textTop, width(), text); + return; + } - // If too large, swap to small font - if (textHeight + textTop > (uint32_t)height()) // (compare signed and unsigned) - setFont(fontSmall); + // Fallback (too large): attempt to print with fontMedium + setFont(fontMedium); + textHeight = getWrappedTextHeight(0, width(), text); + if (textHeight <= (uint32_t)height()) { + printWrapped(0, textTop, width(), text); + return; + } - // Print text + // Fallback (too large): print with fontSmall + setFont(fontSmall); printWrapped(0, textTop, width(), text); } diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp index dbf5c08fb..a3b9615a5 100644 --- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp @@ -97,15 +97,25 @@ void InkHUD::DMApplet::onRender() // Extra gap below the header int16_t textTop = headerDivY + padDivH; - // Determine size if printed large + // Attempt to print with fontLarge + uint32_t textHeight; setFont(fontLarge); - uint32_t textHeight = getWrappedTextHeight(0, width(), text); + textHeight = getWrappedTextHeight(0, width(), text); + if (textHeight <= (uint32_t)height()) { + printWrapped(0, textTop, width(), text); + return; + } - // If too large, swap to small font - if (textHeight + textTop > (uint32_t)height()) // (compare signed and unsigned) - setFont(fontSmall); + // Fallback (too large): attempt to print with fontMedium + setFont(fontMedium); + textHeight = getWrappedTextHeight(0, width(), text); + if (textHeight <= (uint32_t)height()) { + printWrapped(0, textTop, width(), text); + return; + } - // Print text + // Fallback (too large): print with fontSmall + setFont(fontSmall); printWrapped(0, textTop, width(), text); } diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp index 2abe30793..cdda1638d 100644 --- a/src/graphics/niche/InkHUD/Events.cpp +++ b/src/graphics/niche/InkHUD/Events.cpp @@ -39,8 +39,8 @@ void InkHUD::Events::begin() void InkHUD::Events::onButtonShort() { // Audio feedback (via buzzer) - // Short low tone - playBoop(); + // Short tone + playChirp(); // Cancel any beeping, buzzing, blinking // Some button handling suppressed if we are dismissing an external notification (see below) bool dismissedExt = dismissExternalNotification(); @@ -64,8 +64,8 @@ void InkHUD::Events::onButtonShort() void InkHUD::Events::onButtonLong() { // Audio feedback (via buzzer) - // Low tone, longer than playBoop - playBeep(); + // Slightly longer than playChirp + playBoop(); // Check which system applet wants to handle the button press (if any) SystemApplet *consumer = nullptr; diff --git a/src/graphics/niche/InkHUD/PlatformioConfig.ini b/src/graphics/niche/InkHUD/PlatformioConfig.ini index e5a0e67df..80984f399 100644 --- a/src/graphics/niche/InkHUD/PlatformioConfig.ini +++ b/src/graphics/niche/InkHUD/PlatformioConfig.ini @@ -1,7 +1,6 @@ [inkhud] build_src_filter = +; Include the nicheGraphics directory - +<../variants/$PIOENV>; Include nicheGraphics.h from our variant folder build_flags = -D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics -D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI) diff --git a/src/graphics/niche/InkHUD/Renderer.cpp b/src/graphics/niche/InkHUD/Renderer.cpp index c058c4126..072e9dbd6 100644 --- a/src/graphics/niche/InkHUD/Renderer.cpp +++ b/src/graphics/niche/InkHUD/Renderer.cpp @@ -224,6 +224,13 @@ void InkHUD::Renderer::render(bool async) renderPlaceholders(); renderSystemApplets(); + // Invert Buffer if set by user + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) { + for (size_t i = 0; i < imageBufferWidth * imageBufferHeight; ++i) { + imageBuffer[i] = ~imageBuffer[i]; + } + } + // Tell display to begin process of drawing new image LOG_INFO("Updating display"); driver->update(imageBuffer, updateType); diff --git a/src/graphics/niche/InkHUD/docs/README.md b/src/graphics/niche/InkHUD/docs/README.md index c30d25845..e7821299e 100644 --- a/src/graphics/niche/InkHUD/docs/README.md +++ b/src/graphics/niche/InkHUD/docs/README.md @@ -312,18 +312,19 @@ As a general overview: ## Fonts -InkHUD uses AdafruitGFX fonts. The large and small font which are shared by all applets are set in nicheGraphics.h. +InkHUD uses AdafruitGFX fonts. Three shared fonts (small, medium, large) are available for use by all applets. These are set per-variant in nicheGraphics.h. ```cpp // Prepare fonts -InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; +InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; +InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Using a generic AdafruitGFX font instead: -// InkHUD::Applet::fontLarge = FreeSerif9pt7b; +// InkHUD::Applet::fontLarge = FreeSerif18pt7b; ``` -Any generic AdafruitGFX font may be used, but the fonts which are bundled with InkHUD have been customized with extended-ASCII character sets. +Any generic AdafruitGFX font may be used, but the fonts which are bundled with InkHUD have been customized with extended-ASCII character sets and emoji. ### Parsing Unicode Text @@ -351,10 +352,12 @@ InkHUD is bundled with extended-ASCII fonts for: The default builds use Windows-1252 encoding. This can be changed in nicheGraphics.h. ```cpp -InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1250; +InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1250; +InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1250; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1250; -InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1251; +InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1251; +InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1251; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1251; ``` diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp index ad667f003..233bbefe0 100644 --- a/src/input/ButtonThread.cpp +++ b/src/input/ButtonThread.cpp @@ -53,23 +53,21 @@ bool ButtonThread::initButton(const ButtonConfig &config) }, this); - if (config.longPress != INPUT_BROKER_NONE) { - _longPress = config.longPress; - userButton.attachLongPressStart( - [](void *callerThread) -> void { - ButtonThread *thread = (ButtonThread *)callerThread; - // if (millis() > 30000) // hold off 30s after boot - thread->btnEvent = BUTTON_EVENT_LONG_PRESSED; - }, - this); - userButton.attachLongPressStop( - [](void *callerThread) -> void { - ButtonThread *thread = (ButtonThread *)callerThread; - // if (millis() > 30000) // hold off 30s after boot - thread->btnEvent = BUTTON_EVENT_LONG_RELEASED; - }, - this); - } + _longPress = config.longPress; + userButton.attachLongPressStart( + [](void *callerThread) -> void { + ButtonThread *thread = (ButtonThread *)callerThread; + // if (millis() > 30000) // hold off 30s after boot + thread->btnEvent = BUTTON_EVENT_LONG_PRESSED; + }, + this); + userButton.attachLongPressStop( + [](void *callerThread) -> void { + ButtonThread *thread = (ButtonThread *)callerThread; + // if (millis() > 30000) // hold off 30s after boot + thread->btnEvent = BUTTON_EVENT_LONG_RELEASED; + }, + this); if (config.doublePress != INPUT_BROKER_NONE) { _doublePress = config.doublePress; @@ -202,11 +200,11 @@ int32_t ButtonThread::runOnce() break; } - - // Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event) - evt.inputEvent = _longPress; - this->notifyObservers(&evt); - + if (_longPress != INPUT_BROKER_NONE) { + // Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event) + evt.inputEvent = _longPress; + this->notifyObservers(&evt); + } // Reset combination tracking waitingForLongPress = false; @@ -253,7 +251,7 @@ int32_t ButtonThread::runOnce() // may wake the board immediatedly. case BUTTON_EVENT_LONG_RELEASED: { - LOG_INFO("LONG PRESS RELEASE"); + LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime); if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE && (millis() - buttonPressStartTime) >= _longLongPressTime) { evt.inputEvent = _longLongPress; diff --git a/src/input/ButtonThread.h b/src/input/ButtonThread.h index 949048de1..bbc8da2a7 100644 --- a/src/input/ButtonThread.h +++ b/src/input/ButtonThread.h @@ -18,13 +18,13 @@ struct ButtonConfig { uint16_t longPressTime = 500; input_broker_event doublePress = INPUT_BROKER_NONE; input_broker_event longLongPress = INPUT_BROKER_NONE; - uint16_t longLongPressTime = 5000; + uint16_t longLongPressTime = 3900; input_broker_event triplePress = INPUT_BROKER_NONE; input_broker_event shortLong = INPUT_BROKER_NONE; bool touchQuirk = false; // Constructor to set required parameter - ButtonConfig(uint8_t pin = 0) : pinNumber(pin) {} + explicit ButtonConfig(uint8_t pin = 0) : pinNumber(pin) {} }; #ifndef BUTTON_CLICK_MS @@ -62,7 +62,7 @@ class ButtonThread : public Observable, public concurrency:: BUTTON_EVENT_COMBO_SHORT_LONG, }; - ButtonThread(const char *name); + explicit ButtonThread(const char *name); int32_t runOnce() override; OneButton userButton; void attachButtonInterrupts(); diff --git a/src/input/ExpressLRSFiveWay.cpp b/src/input/ExpressLRSFiveWay.cpp index 53bcedc63..776b9001d 100644 --- a/src/input/ExpressLRSFiveWay.cpp +++ b/src/input/ExpressLRSFiveWay.cpp @@ -199,7 +199,7 @@ void ExpressLRSFiveWay::sendKey(input_broker_event key) void ExpressLRSFiveWay::toggleGPS() { #if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS - if (!config.device.disable_triple_click && (gps != nullptr)) { + if (gps != nullptr) { gps->toggleGpsMode(); screen->startAlert("GPS Toggled"); alerting = true; @@ -233,14 +233,7 @@ void ExpressLRSFiveWay::sendAdhocPing() // Contained as one method for easier remapping of buttons by user void ExpressLRSFiveWay::shutdown() { - LOG_INFO("Shutdown from long press"); - powerFSM.trigger(EVENT_PRESS); - screen->startAlert("Shutting Down..."); - // Don't set alerting = true. We don't want to auto-dismiss this alert. - - playShutdownMelody(); // In case user adds a buzzer - - shutdownAtMsec = millis() + 3000; + sendKey(INPUT_BROKER_SHUTDOWN); } void ExpressLRSFiveWay::click() diff --git a/src/input/SeesawRotary.cpp b/src/input/SeesawRotary.cpp new file mode 100644 index 000000000..c212773c4 --- /dev/null +++ b/src/input/SeesawRotary.cpp @@ -0,0 +1,83 @@ +#ifdef ARCH_PORTDUINO +#include "SeesawRotary.h" +#include "input/InputBroker.h" + +using namespace concurrency; + +SeesawRotary *seesawRotary; + +SeesawRotary::SeesawRotary(const char *name) : OSThread(name) +{ + _originName = name; +} + +bool SeesawRotary::init() +{ + if (inputBroker) + inputBroker->registerSource(this); + + if (!ss.begin(SEESAW_ADDR)) { + return false; + } + // attachButtonInterrupts(); + + uint32_t version = ((ss.getVersion() >> 16) & 0xFFFF); + if (version != 4991) { + LOG_WARN("Wrong firmware loaded? %u", version); + } else { + LOG_INFO("Found Product 4991"); + } + /* + #ifdef ARCH_ESP32 + // Register callbacks for before and after lightsleep + // Used to detach and reattach interrupts + lsObserver.observe(¬ifyLightSleep); + lsEndObserver.observe(¬ifyLightSleepEnd); + #endif + */ + ss.pinMode(SS_SWITCH, INPUT_PULLUP); + + // get starting position + encoder_position = ss.getEncoderPosition(); + + ss.setGPIOInterrupts((uint32_t)1 << SS_SWITCH, 1); + ss.enableEncoderInterrupt(); + canSleep = true; // Assume we should not keep the board awake + + return true; +} + +int32_t SeesawRotary::runOnce() +{ + InputEvent e; + e.inputEvent = INPUT_BROKER_NONE; + bool currentlyPressed = !ss.digitalRead(SS_SWITCH); + + if (currentlyPressed && !wasPressed) { + e.inputEvent = INPUT_BROKER_SELECT; + } + wasPressed = currentlyPressed; + + int32_t new_position = ss.getEncoderPosition(); + // did we move arounde? + if (encoder_position != new_position) { + if (encoder_position == 0 && new_position != 1) { + e.inputEvent = INPUT_BROKER_ALT_PRESS; + } else if (new_position == 0 && encoder_position != 1) { + e.inputEvent = INPUT_BROKER_USER_PRESS; + } else if (new_position > encoder_position) { + e.inputEvent = INPUT_BROKER_USER_PRESS; + } else { + e.inputEvent = INPUT_BROKER_ALT_PRESS; + } + encoder_position = new_position; + } + if (e.inputEvent != INPUT_BROKER_NONE) { + e.source = this->_originName; + e.kbchar = 0x00; + this->notifyObservers(&e); + } + + return 50; +} +#endif \ No newline at end of file diff --git a/src/input/SeesawRotary.h b/src/input/SeesawRotary.h new file mode 100644 index 000000000..3812b130a --- /dev/null +++ b/src/input/SeesawRotary.h @@ -0,0 +1,29 @@ +#pragma once +#ifdef ARCH_PORTDUINO + +#include "Adafruit_seesaw.h" +#include "InputBroker.h" +#include "concurrency/OSThread.h" +#include "configuration.h" + +#define SS_SWITCH 24 +#define SS_NEOPIX 6 + +#define SEESAW_ADDR 0x36 + +class SeesawRotary : public Observable, public concurrency::OSThread +{ + public: + const char *_originName; + bool init(); + SeesawRotary(const char *name); + int32_t runOnce() override; + + private: + Adafruit_seesaw ss; + int32_t encoder_position; + bool wasPressed = false; +}; + +extern SeesawRotary *seesawRotary; +#endif \ No newline at end of file diff --git a/src/input/TCA8418Keyboard.cpp b/src/input/TCA8418Keyboard.cpp index d99379b23..bd8338acf 100644 --- a/src/input/TCA8418Keyboard.cpp +++ b/src/input/TCA8418Keyboard.cpp @@ -1,116 +1,18 @@ -// Based on the MPR121 Keyboard and Adafruit TCA8418 library - #include "TCA8418Keyboard.h" -#include "configuration.h" - -#include - -// REGISTERS -// #define _TCA8418_REG_RESERVED 0x00 -#define _TCA8418_REG_CFG 0x01 // Configuration register -#define _TCA8418_REG_INT_STAT 0x02 // Interrupt status -#define _TCA8418_REG_KEY_LCK_EC 0x03 // Key lock and event counter -#define _TCA8418_REG_KEY_EVENT_A 0x04 // Key event register A -#define _TCA8418_REG_KEY_EVENT_B 0x05 // Key event register B -#define _TCA8418_REG_KEY_EVENT_C 0x06 // Key event register C -#define _TCA8418_REG_KEY_EVENT_D 0x07 // Key event register D -#define _TCA8418_REG_KEY_EVENT_E 0x08 // Key event register E -#define _TCA8418_REG_KEY_EVENT_F 0x09 // Key event register F -#define _TCA8418_REG_KEY_EVENT_G 0x0A // Key event register G -#define _TCA8418_REG_KEY_EVENT_H 0x0B // Key event register H -#define _TCA8418_REG_KEY_EVENT_I 0x0C // Key event register I -#define _TCA8418_REG_KEY_EVENT_J 0x0D // Key event register J -#define _TCA8418_REG_KP_LCK_TIMER 0x0E // Keypad lock1 to lock2 timer -#define _TCA8418_REG_UNLOCK_1 0x0F // Unlock register 1 -#define _TCA8418_REG_UNLOCK_2 0x10 // Unlock register 2 -#define _TCA8418_REG_GPIO_INT_STAT_1 0x11 // GPIO interrupt status 1 -#define _TCA8418_REG_GPIO_INT_STAT_2 0x12 // GPIO interrupt status 2 -#define _TCA8418_REG_GPIO_INT_STAT_3 0x13 // GPIO interrupt status 3 -#define _TCA8418_REG_GPIO_DAT_STAT_1 0x14 // GPIO data status 1 -#define _TCA8418_REG_GPIO_DAT_STAT_2 0x15 // GPIO data status 2 -#define _TCA8418_REG_GPIO_DAT_STAT_3 0x16 // GPIO data status 3 -#define _TCA8418_REG_GPIO_DAT_OUT_1 0x17 // GPIO data out 1 -#define _TCA8418_REG_GPIO_DAT_OUT_2 0x18 // GPIO data out 2 -#define _TCA8418_REG_GPIO_DAT_OUT_3 0x19 // GPIO data out 3 -#define _TCA8418_REG_GPIO_INT_EN_1 0x1A // GPIO interrupt enable 1 -#define _TCA8418_REG_GPIO_INT_EN_2 0x1B // GPIO interrupt enable 2 -#define _TCA8418_REG_GPIO_INT_EN_3 0x1C // GPIO interrupt enable 3 -#define _TCA8418_REG_KP_GPIO_1 0x1D // Keypad/GPIO select 1 -#define _TCA8418_REG_KP_GPIO_2 0x1E // Keypad/GPIO select 2 -#define _TCA8418_REG_KP_GPIO_3 0x1F // Keypad/GPIO select 3 -#define _TCA8418_REG_GPI_EM_1 0x20 // GPI event mode 1 -#define _TCA8418_REG_GPI_EM_2 0x21 // GPI event mode 2 -#define _TCA8418_REG_GPI_EM_3 0x22 // GPI event mode 3 -#define _TCA8418_REG_GPIO_DIR_1 0x23 // GPIO data direction 1 -#define _TCA8418_REG_GPIO_DIR_2 0x24 // GPIO data direction 2 -#define _TCA8418_REG_GPIO_DIR_3 0x25 // GPIO data direction 3 -#define _TCA8418_REG_GPIO_INT_LVL_1 0x26 // GPIO edge/level detect 1 -#define _TCA8418_REG_GPIO_INT_LVL_2 0x27 // GPIO edge/level detect 2 -#define _TCA8418_REG_GPIO_INT_LVL_3 0x28 // GPIO edge/level detect 3 -#define _TCA8418_REG_DEBOUNCE_DIS_1 0x29 // Debounce disable 1 -#define _TCA8418_REG_DEBOUNCE_DIS_2 0x2A // Debounce disable 2 -#define _TCA8418_REG_DEBOUNCE_DIS_3 0x2B // Debounce disable 3 -#define _TCA8418_REG_GPIO_PULL_1 0x2C // GPIO pull-up disable 1 -#define _TCA8418_REG_GPIO_PULL_2 0x2D // GPIO pull-up disable 2 -#define _TCA8418_REG_GPIO_PULL_3 0x2E // GPIO pull-up disable 3 -// #define _TCA8418_REG_RESERVED 0x2F - -// FIELDS CONFIG REGISTER 1 -#define _TCA8418_REG_CFG_AI 0x80 // Auto-increment for read/write -#define _TCA8418_REG_CFG_GPI_E_CGF 0x40 // Event mode config -#define _TCA8418_REG_CFG_OVR_FLOW_M 0x20 // Overflow mode enable -#define _TCA8418_REG_CFG_INT_CFG 0x10 // Interrupt config -#define _TCA8418_REG_CFG_OVR_FLOW_IEN 0x08 // Overflow interrupt enable -#define _TCA8418_REG_CFG_K_LCK_IEN 0x04 // Keypad lock interrupt enable -#define _TCA8418_REG_CFG_GPI_IEN 0x02 // GPI interrupt enable -#define _TCA8418_REG_CFG_KE_IEN 0x01 // Key events interrupt enable - -// FIELDS INT_STAT REGISTER 2 -#define _TCA8418_REG_STAT_CAD_INT 0x10 // Ctrl-alt-del seq status -#define _TCA8418_REG_STAT_OVR_FLOW_INT 0x08 // Overflow interrupt status -#define _TCA8418_REG_STAT_K_LCK_INT 0x04 // Key lock interrupt status -#define _TCA8418_REG_STAT_GPI_INT 0x02 // GPI interrupt status -#define _TCA8418_REG_STAT_K_INT 0x01 // Key events interrupt status - -// FIELDS KEY_LCK_EC REGISTER 3 -#define _TCA8418_REG_LCK_EC_K_LCK_EN 0x40 // Key lock enable -#define _TCA8418_REG_LCK_EC_LCK_2 0x20 // Keypad lock status 2 -#define _TCA8418_REG_LCK_EC_LCK_1 0x10 // Keypad lock status 1 -#define _TCA8418_REG_LCK_EC_KLEC_3 0x08 // Key event count bit 3 -#define _TCA8418_REG_LCK_EC_KLEC_2 0x04 // Key event count bit 2 -#define _TCA8418_REG_LCK_EC_KLEC_1 0x02 // Key event count bit 1 -#define _TCA8418_REG_LCK_EC_KLEC_0 0x01 // Key event count bit 0 - -// Pin IDs for matrix rows/columns -enum { - _TCA8418_ROW0, // Pin ID for row 0 - _TCA8418_ROW1, // Pin ID for row 1 - _TCA8418_ROW2, // Pin ID for row 2 - _TCA8418_ROW3, // Pin ID for row 3 - _TCA8418_ROW4, // Pin ID for row 4 - _TCA8418_ROW5, // Pin ID for row 5 - _TCA8418_ROW6, // Pin ID for row 6 - _TCA8418_ROW7, // Pin ID for row 7 - _TCA8418_COL0, // Pin ID for column 0 - _TCA8418_COL1, // Pin ID for column 1 - _TCA8418_COL2, // Pin ID for column 2 - _TCA8418_COL3, // Pin ID for column 3 - _TCA8418_COL4, // Pin ID for column 4 - _TCA8418_COL5, // Pin ID for column 5 - _TCA8418_COL6, // Pin ID for column 6 - _TCA8418_COL7, // Pin ID for column 7 - _TCA8418_COL8, // Pin ID for column 8 - _TCA8418_COL9 // Pin ID for column 9 -}; #define _TCA8418_COLS 3 #define _TCA8418_ROWS 4 #define _TCA8418_NUM_KEYS 12 -uint8_t TCA8418TapMod[_TCA8418_NUM_KEYS] = {13, 7, 7, 7, 7, 7, - 9, 7, 9, 2, 2, 2}; // Num chars per key, Modulus for rotating through characters +#define _TCA8418_LONG_PRESS_THRESHOLD 2000 +#define _TCA8418_MULTI_TAP_THRESHOLD 750 -unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = { +using Key = TCA8418KeyboardBase::TCA8418Key; + +// Num chars per key, Modulus for rotating through characters +static uint8_t TCA8418TapMod[_TCA8418_NUM_KEYS] = {13, 7, 7, 7, 7, 7, 9, 7, 9, 2, 2, 2}; + +static unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = { {'1', '.', ',', '?', '!', ':', ';', '-', '_', '\\', '/', '(', ')'}, // 1 {'2', 'a', 'b', 'c', 'A', 'B', 'C'}, // 2 {'3', 'd', 'e', 'f', 'D', 'E', 'F'}, // 3 @@ -125,176 +27,35 @@ unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = { {'#', '@'}, // # }; -unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = { - _TCA8418_ESC, // 1 - _TCA8418_UP, // 2 - _TCA8418_NONE, // 3 - _TCA8418_LEFT, // 4 - _TCA8418_NONE, // 5 - _TCA8418_RIGHT, // 6 - _TCA8418_NONE, // 7 - _TCA8418_DOWN, // 8 - _TCA8418_NONE, // 9 - _TCA8418_BSP, // * - _TCA8418_NONE, // 0 - _TCA8418_NONE, // # +static unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = { + Key::ESC, // 1 + Key::UP, // 2 + Key::NONE, // 3 + Key::LEFT, // 4 + Key::NONE, // 5 + Key::RIGHT, // 6 + Key::NONE, // 7 + Key::DOWN, // 8 + Key::NONE, // 9 + Key::BSP, // * + Key::NONE, // 0 + Key::NONE, // # }; -#define _TCA8418_LONG_PRESS_THRESHOLD 2000 -#define _TCA8418_MULTI_TAP_THRESHOLD 750 - -TCA8418Keyboard::TCA8418Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr) +TCA8418Keyboard::TCA8418Keyboard() + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(-1), next_key(-1), last_tap(0L), char_idx(0), tap_interval(0), + should_backspace(false) { - state = Init; - last_key = -1; - should_backspace = false; - last_tap = 0L; - char_idx = 0; - tap_interval = 0; - backlight_on = true; - queue = ""; -} - -void TCA8418Keyboard::begin(uint8_t addr, TwoWire *wire) -{ - m_addr = addr; - m_wire = wire; - - m_wire->begin(); - - reset(); -} - -void TCA8418Keyboard::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr) -{ - m_addr = addr; - m_wire = nullptr; - writeCallback = w; - readCallback = r; - reset(); } void TCA8418Keyboard::reset() { - LOG_DEBUG("TCA8418 Reset"); - // GPIO - // set default all GIO pins to INPUT - writeRegister(_TCA8418_REG_GPIO_DIR_1, 0x00); - writeRegister(_TCA8418_REG_GPIO_DIR_2, 0x00); + TCA8418KeyboardBase::reset(); + // Set COL9 as GPIO output - writeRegister(_TCA8418_REG_GPIO_DIR_3, 0x02); + writeRegister(TCA8418_REG_GPIO_DIR_3, 0x02); // Switch off keyboard backlight (COL9 = LOW) - writeRegister(_TCA8418_REG_GPIO_DAT_OUT_3, 0x00); - - // add all pins to key events - writeRegister(_TCA8418_REG_GPI_EM_1, 0xFF); - writeRegister(_TCA8418_REG_GPI_EM_2, 0xFF); - writeRegister(_TCA8418_REG_GPI_EM_3, 0xFF); - - // set all pins to FALLING interrupts - writeRegister(_TCA8418_REG_GPIO_INT_LVL_1, 0x00); - writeRegister(_TCA8418_REG_GPIO_INT_LVL_2, 0x00); - writeRegister(_TCA8418_REG_GPIO_INT_LVL_3, 0x00); - - // add all pins to interrupts - writeRegister(_TCA8418_REG_GPIO_INT_EN_1, 0xFF); - writeRegister(_TCA8418_REG_GPIO_INT_EN_2, 0xFF); - writeRegister(_TCA8418_REG_GPIO_INT_EN_3, 0xFF); - - // Set keyboard matrix size - matrix(_TCA8418_ROWS, _TCA8418_COLS); - enableDebounce(); - flush(); - state = Idle; -} - -bool TCA8418Keyboard::matrix(uint8_t rows, uint8_t columns) -{ - if ((rows > 8) || (columns > 10)) - return false; - - // Skip zero size matrix - if ((rows != 0) && (columns != 0)) { - // Setup the keypad matrix. - uint8_t mask = 0x00; - for (int r = 0; r < rows; r++) { - mask <<= 1; - mask |= 1; - } - writeRegister(_TCA8418_REG_KP_GPIO_1, mask); - - mask = 0x00; - for (int c = 0; c < columns && c < 8; c++) { - mask <<= 1; - mask |= 1; - } - writeRegister(_TCA8418_REG_KP_GPIO_2, mask); - - if (columns > 8) { - if (columns == 9) - mask = 0x01; - else - mask = 0x03; - writeRegister(_TCA8418_REG_KP_GPIO_3, mask); - } - } - - return true; -} - -uint8_t TCA8418Keyboard::keyCount() const -{ - uint8_t eventCount = readRegister(_TCA8418_REG_KEY_LCK_EC); - eventCount &= 0x0F; // lower 4 bits only - return eventCount; -} - -bool TCA8418Keyboard::hasEvent() -{ - return queue.length() > 0; -} - -void TCA8418Keyboard::queueEvent(char next) -{ - if (next == _TCA8418_NONE) { - return; - } - queue.concat(next); -} - -char TCA8418Keyboard::dequeueEvent() -{ - if (queue.length() < 1) { - return _TCA8418_NONE; - } - char next = queue.charAt(0); - queue.remove(0, 1); - return next; -} - -void TCA8418Keyboard::trigger() -{ - if (keyCount() == 0) { - return; - } - if (state != Init) { - // Read the key register - uint8_t k = readRegister(_TCA8418_REG_KEY_EVENT_A); - uint8_t key = k & 0x7F; - if (k & 0x80) { - if (state == Idle) - pressed(key); - return; - } else { - if (state == Held) { - released(); - } - state = Idle; - return; - } - } else { - reset(); - } + writeRegister(TCA8418_REG_GPIO_DAT_OUT_3, 0x00); } void TCA8418Keyboard::pressed(uint8_t key) @@ -354,7 +115,7 @@ void TCA8418Keyboard::released() int32_t held_interval = now - last_tap; last_tap = now; if (tap_interval < _TCA8418_MULTI_TAP_THRESHOLD && should_backspace) { - queueEvent(_TCA8418_BSP); + queueEvent(BSP); } if (held_interval > _TCA8418_LONG_PRESS_THRESHOLD) { queueEvent(TCA8418LongPressMap[last_key]); @@ -366,195 +127,11 @@ void TCA8418Keyboard::released() } } -uint8_t TCA8418Keyboard::flush() -{ - // Flush key events - uint8_t count = 0; - while (readRegister(_TCA8418_REG_KEY_EVENT_A) != 0) - count++; - // Flush gpio events - readRegister(_TCA8418_REG_GPIO_INT_STAT_1); - readRegister(_TCA8418_REG_GPIO_INT_STAT_2); - readRegister(_TCA8418_REG_GPIO_INT_STAT_3); - // Clear INT_STAT register - writeRegister(_TCA8418_REG_INT_STAT, 3); - return count; -} - -uint8_t TCA8418Keyboard::digitalRead(uint8_t pinnum) const -{ - if (pinnum > _TCA8418_COL9) - return 0xFF; - - uint8_t reg = _TCA8418_REG_GPIO_DAT_STAT_1 + pinnum / 8; - uint8_t mask = (1 << (pinnum % 8)); - - // Level 0 = low other = high - uint8_t value = readRegister(reg); - if (value & mask) - return HIGH; - return LOW; -} - -bool TCA8418Keyboard::digitalWrite(uint8_t pinnum, uint8_t level) -{ - if (pinnum > _TCA8418_COL9) - return false; - - uint8_t reg = _TCA8418_REG_GPIO_DAT_OUT_1 + pinnum / 8; - uint8_t mask = (1 << (pinnum % 8)); - - // Level 0 = low other = high - uint8_t value = readRegister(reg); - if (level == LOW) - value &= ~mask; - else - value |= mask; - writeRegister(reg, value); - return true; -} - -bool TCA8418Keyboard::pinMode(uint8_t pinnum, uint8_t mode) -{ - if (pinnum > _TCA8418_COL9) - return false; - - uint8_t idx = pinnum / 8; - uint8_t reg = _TCA8418_REG_GPIO_DIR_1 + idx; - uint8_t mask = (1 << (pinnum % 8)); - - // Mode 0 = input 1 = output - uint8_t value = readRegister(reg); - if (mode == OUTPUT) - value |= mask; - else - value &= ~mask; - writeRegister(reg, value); - - // Pullup 0 = enabled 1 = disabled - reg = _TCA8418_REG_GPIO_PULL_1 + idx; - value = readRegister(reg); - if (mode == INPUT_PULLUP) - value &= ~mask; - else - value |= mask; - writeRegister(reg, value); - - return true; -} - -bool TCA8418Keyboard::pinIRQMode(uint8_t pinnum, uint8_t mode) -{ - if (pinnum > _TCA8418_COL9) - return false; - if ((mode != RISING) && (mode != FALLING)) - return false; - - // Mode 0 = falling 1 = rising - uint8_t idx = pinnum / 8; - uint8_t reg = _TCA8418_REG_GPIO_INT_LVL_1 + idx; - uint8_t mask = (1 << (pinnum % 8)); - - uint8_t value = readRegister(reg); - if (mode == RISING) - value |= mask; - else - value &= ~mask; - writeRegister(reg, value); - - // Enable interrupt - reg = _TCA8418_REG_GPIO_INT_EN_1 + idx; - value = readRegister(reg); - value |= mask; - writeRegister(reg, value); - - return true; -} - -void TCA8418Keyboard::enableInterrupts() -{ - uint8_t value = readRegister(_TCA8418_REG_CFG); - value |= (_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN); - writeRegister(_TCA8418_REG_CFG, value); -}; - -void TCA8418Keyboard::disableInterrupts() -{ - uint8_t value = readRegister(_TCA8418_REG_CFG); - value &= ~(_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN); - writeRegister(_TCA8418_REG_CFG, value); -}; - -void TCA8418Keyboard::enableMatrixOverflow() -{ - uint8_t value = readRegister(_TCA8418_REG_CFG); - value |= _TCA8418_REG_CFG_OVR_FLOW_M; - writeRegister(_TCA8418_REG_CFG, value); -}; - -void TCA8418Keyboard::disableMatrixOverflow() -{ - uint8_t value = readRegister(_TCA8418_REG_CFG); - value &= ~_TCA8418_REG_CFG_OVR_FLOW_M; - writeRegister(_TCA8418_REG_CFG, value); -}; - -void TCA8418Keyboard::enableDebounce() -{ - writeRegister(_TCA8418_REG_DEBOUNCE_DIS_1, 0x00); - writeRegister(_TCA8418_REG_DEBOUNCE_DIS_2, 0x00); - writeRegister(_TCA8418_REG_DEBOUNCE_DIS_3, 0x00); -} - -void TCA8418Keyboard::disableDebounce() -{ - writeRegister(_TCA8418_REG_DEBOUNCE_DIS_1, 0xFF); - writeRegister(_TCA8418_REG_DEBOUNCE_DIS_2, 0xFF); - writeRegister(_TCA8418_REG_DEBOUNCE_DIS_3, 0xFF); -} - void TCA8418Keyboard::setBacklight(bool on) { if (on) { - digitalWrite(_TCA8418_COL9, HIGH); + digitalWrite(TCA8418_COL9, HIGH); } else { - digitalWrite(_TCA8418_COL9, LOW); + digitalWrite(TCA8418_COL9, LOW); } } - -uint8_t TCA8418Keyboard::readRegister(uint8_t reg) const -{ - if (m_wire) { - m_wire->beginTransmission(m_addr); - m_wire->write(reg); - m_wire->endTransmission(); - - m_wire->requestFrom(m_addr, (uint8_t)1); - if (m_wire->available() < 1) - return 0; - - return m_wire->read(); - } - if (readCallback) { - uint8_t data; - readCallback(m_addr, reg, &data, 1); - return data; - } - return 0; -} - -void TCA8418Keyboard::writeRegister(uint8_t reg, uint8_t value) -{ - uint8_t data[2]; - data[0] = reg; - data[1] = value; - - if (m_wire) { - m_wire->beginTransmission(m_addr); - m_wire->write(data, sizeof(uint8_t) * 2); - m_wire->endTransmission(); - } - if (writeCallback) { - writeCallback(m_addr, data[0], &(data[1]), 1); - } -} \ No newline at end of file diff --git a/src/input/TCA8418Keyboard.h b/src/input/TCA8418Keyboard.h index 5c53452a4..b76916643 100644 --- a/src/input/TCA8418Keyboard.h +++ b/src/input/TCA8418Keyboard.h @@ -1,82 +1,23 @@ -// Based on the MPR121 Keyboard and Adafruit TCA8418 library -#include "configuration.h" -#include +#include "TCA8418KeyboardBase.h" -#define _TCA8418_NONE 0x00 -#define _TCA8418_REBOOT 0x90 -#define _TCA8418_LEFT 0xb4 -#define _TCA8418_UP 0xb5 -#define _TCA8418_DOWN 0xb6 -#define _TCA8418_RIGHT 0xb7 -#define _TCA8418_ESC 0x1b -#define _TCA8418_BSP 0x08 -#define _TCA8418_SELECT 0x0d - -class TCA8418Keyboard +/** + * @brief 3x4 keypad with 3 columns and 4 rows + */ +class TCA8418Keyboard : public TCA8418KeyboardBase { public: - typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len); + TCA8418Keyboard(); + void reset(void) override; + void setBacklight(bool on) override; - enum KeyState { Init = 0, Idle, Held, Busy }; + protected: + void pressed(uint8_t key) override; + void released(void) override; - KeyState state; int8_t last_key; - bool should_backspace; + int8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; - bool backlight_on; - - String queue; - - TCA8418Keyboard(); - - void begin(uint8_t addr = XPOWERS_AXP192_AXP2101_ADDRESS, TwoWire *wire = &Wire); - void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = XPOWERS_AXP192_AXP2101_ADDRESS); - - void reset(void); - // Configure the size of the keypad. - // All other rows and columns are set as inputs. - bool matrix(uint8_t rows, uint8_t columns); - - // Flush all events in the FIFO buffer + GPIO events. - uint8_t flush(void); - - // Key events available in the internal FIFO buffer. - uint8_t keyCount(void) const; - - void trigger(void); - void pressed(uint8_t key); - void released(void); - bool hasEvent(void); - char dequeueEvent(void); - void queueEvent(char); - - uint8_t digitalRead(uint8_t pinnum) const; - bool digitalWrite(uint8_t pinnum, uint8_t level); - bool pinMode(uint8_t pinnum, uint8_t mode); - bool pinIRQMode(uint8_t pinnum, uint8_t mode); // MODE FALLING or RISING - - // enable / disable interrupts for matrix and GPI pins - void enableInterrupts(); - void disableInterrupts(); - - // ignore key events when FIFO buffer is full or not. - void enableMatrixOverflow(); - void disableMatrixOverflow(); - - // debounce keys. - void enableDebounce(); - void disableDebounce(); - - void setBacklight(bool on); - - uint8_t readRegister(uint8_t reg) const; - void writeRegister(uint8_t reg, uint8_t value); - - private: - TwoWire *m_wire; - uint8_t m_addr; - i2c_com_fptr_t readCallback; - i2c_com_fptr_t writeCallback; + bool should_backspace; }; diff --git a/src/input/TCA8418KeyboardBase.cpp b/src/input/TCA8418KeyboardBase.cpp new file mode 100644 index 000000000..aafc4c36c --- /dev/null +++ b/src/input/TCA8418KeyboardBase.cpp @@ -0,0 +1,372 @@ +// Based on the MPR121 Keyboard and Adafruit TCA8418 library + +#include "TCA8418KeyboardBase.h" +#include "configuration.h" + +#include + +// FIELDS CONFIG REGISTER 1 +#define _TCA8418_REG_CFG_AI 0x80 // Auto-increment for read/write +#define _TCA8418_REG_CFG_GPI_E_CGF 0x40 // Event mode config +#define _TCA8418_REG_CFG_OVR_FLOW_M 0x20 // Overflow mode enable +#define _TCA8418_REG_CFG_INT_CFG 0x10 // Interrupt config +#define _TCA8418_REG_CFG_OVR_FLOW_IEN 0x08 // Overflow interrupt enable +#define _TCA8418_REG_CFG_K_LCK_IEN 0x04 // Keypad lock interrupt enable +#define _TCA8418_REG_CFG_GPI_IEN 0x02 // GPI interrupt enable +#define _TCA8418_REG_CFG_KE_IEN 0x01 // Key events interrupt enable + +// FIELDS INT_STAT REGISTER 2 +#define _TCA8418_REG_STAT_CAD_INT 0x10 // Ctrl-alt-del seq status +#define _TCA8418_REG_STAT_OVR_FLOW_INT 0x08 // Overflow interrupt status +#define _TCA8418_REG_STAT_K_LCK_INT 0x04 // Key lock interrupt status +#define _TCA8418_REG_STAT_GPI_INT 0x02 // GPI interrupt status +#define _TCA8418_REG_STAT_K_INT 0x01 // Key events interrupt status + +// FIELDS KEY_LCK_EC REGISTER 3 +#define _TCA8418_REG_LCK_EC_K_LCK_EN 0x40 // Key lock enable +#define _TCA8418_REG_LCK_EC_LCK_2 0x20 // Keypad lock status 2 +#define _TCA8418_REG_LCK_EC_LCK_1 0x10 // Keypad lock status 1 +#define _TCA8418_REG_LCK_EC_KLEC_3 0x08 // Key event count bit 3 +#define _TCA8418_REG_LCK_EC_KLEC_2 0x04 // Key event count bit 2 +#define _TCA8418_REG_LCK_EC_KLEC_1 0x02 // Key event count bit 1 +#define _TCA8418_REG_LCK_EC_KLEC_0 0x01 // Key event count bit 0 + +TCA8418KeyboardBase::TCA8418KeyboardBase(uint8_t rows, uint8_t columns) + : rows(rows), columns(columns), state(Init), queue(""), m_wire(nullptr), m_addr(0), readCallback(nullptr), + writeCallback(nullptr) +{ +} + +void TCA8418KeyboardBase::begin(uint8_t addr, TwoWire *wire) +{ + m_addr = addr; + m_wire = wire; + m_wire->begin(); + reset(); +} + +void TCA8418KeyboardBase::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr) +{ + m_addr = addr; + m_wire = nullptr; + writeCallback = w; + readCallback = r; + reset(); +} + +void TCA8418KeyboardBase::reset() +{ + LOG_DEBUG("TCA8418 Reset"); + // GPIO + // set default all GIO pins to INPUT + writeRegister(TCA8418_REG_GPIO_DIR_1, 0x00); + writeRegister(TCA8418_REG_GPIO_DIR_2, 0x00); + writeRegister(TCA8418_REG_GPIO_DIR_3, 0x00); + + // add all pins to key events + writeRegister(TCA8418_REG_GPI_EM_1, 0xFF); + writeRegister(TCA8418_REG_GPI_EM_2, 0xFF); + writeRegister(TCA8418_REG_GPI_EM_3, 0xFF); + + // set all pins to FALLING interrupts + writeRegister(TCA8418_REG_GPIO_INT_LVL_1, 0x00); + writeRegister(TCA8418_REG_GPIO_INT_LVL_2, 0x00); + writeRegister(TCA8418_REG_GPIO_INT_LVL_3, 0x00); + + // add all pins to interrupts + writeRegister(TCA8418_REG_GPIO_INT_EN_1, 0xFF); + writeRegister(TCA8418_REG_GPIO_INT_EN_2, 0xFF); + writeRegister(TCA8418_REG_GPIO_INT_EN_3, 0xFF); + + // Set keyboard matrix size + matrix(rows, columns); + enableDebounce(); + flush(); + state = Idle; +} + +bool TCA8418KeyboardBase::matrix(uint8_t rows, uint8_t columns) +{ + if (rows < 1 || rows > 8 || columns < 1 || columns > 10) + return false; + + // Setup the keypad matrix. + uint8_t mask = 0x00; + for (int r = 0; r < rows; r++) { + mask <<= 1; + mask |= 1; + } + writeRegister(TCA8418_REG_KP_GPIO_1, mask); + + mask = 0x00; + for (int c = 0; c < columns && c < 8; c++) { + mask <<= 1; + mask |= 1; + } + writeRegister(TCA8418_REG_KP_GPIO_2, mask); + + if (columns > 8) { + if (columns == 9) + mask = 0x01; + else + mask = 0x03; + writeRegister(TCA8418_REG_KP_GPIO_3, mask); + } + + return true; +} + +uint8_t TCA8418KeyboardBase::keyCount() const +{ + uint8_t eventCount = readRegister(TCA8418_REG_KEY_LCK_EC); + eventCount &= 0x0F; // lower 4 bits only + return eventCount; +} + +bool TCA8418KeyboardBase::hasEvent() const +{ + return queue.length() > 0; +} + +void TCA8418KeyboardBase::queueEvent(char next) +{ + if (next == NONE) { + return; + } + queue.concat(next); +} + +char TCA8418KeyboardBase::dequeueEvent() +{ + if (queue.length() < 1) { + return NONE; + } + char next = queue.charAt(0); + queue.remove(0, 1); + return next; +} + +void TCA8418KeyboardBase::trigger() +{ + if (keyCount() == 0) { + return; + } + if (state != Init) { + // Read the key register + uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A); + uint8_t key = k & 0x7F; + if (k & 0x80) { + if (state == Idle) + pressed(key); + return; + } else { + if (state == Held) { + released(); + } + state = Idle; + return; + } + } else { + reset(); + } +} + +void TCA8418KeyboardBase::pressed(uint8_t key) +{ + // must be defined in derived class + LOG_ERROR("pressed() not implemented in derived class"); +} + +void TCA8418KeyboardBase::released() +{ + // must be defined in derived class + LOG_ERROR("released() not implemented in derived class"); +} + +uint8_t TCA8418KeyboardBase::flush() +{ + // Flush key events + uint8_t count = 0; + while (readRegister(TCA8418_REG_KEY_EVENT_A) != 0) + count++; + + // Flush gpio events + readRegister(TCA8418_REG_GPIO_INT_STAT_1); + readRegister(TCA8418_REG_GPIO_INT_STAT_2); + readRegister(TCA8418_REG_GPIO_INT_STAT_3); + + // Clear INT_STAT register + writeRegister(TCA8418_REG_INT_STAT, 3); + return count; +} + +uint8_t TCA8418KeyboardBase::digitalRead(uint8_t pinnum) const +{ + if (pinnum > TCA8418_COL9) + return 0xFF; + + uint8_t reg = TCA8418_REG_GPIO_DAT_STAT_1 + pinnum / 8; + uint8_t mask = (1 << (pinnum % 8)); + + // Level 0 = low other = high + uint8_t value = readRegister(reg); + if (value & mask) + return HIGH; + return LOW; +} + +bool TCA8418KeyboardBase::digitalWrite(uint8_t pinnum, uint8_t level) +{ + if (pinnum > TCA8418_COL9) + return false; + + uint8_t reg = TCA8418_REG_GPIO_DAT_OUT_1 + pinnum / 8; + uint8_t mask = (1 << (pinnum % 8)); + + // Level 0 = low other = high + uint8_t value = readRegister(reg); + if (level == LOW) + value &= ~mask; + else + value |= mask; + writeRegister(reg, value); + return true; +} + +bool TCA8418KeyboardBase::pinMode(uint8_t pinnum, uint8_t mode) +{ + if (pinnum > TCA8418_COL9) + return false; + + uint8_t idx = pinnum / 8; + uint8_t reg = TCA8418_REG_GPIO_DIR_1 + idx; + uint8_t mask = (1 << (pinnum % 8)); + + // Mode 0 = input 1 = output + uint8_t value = readRegister(reg); + if (mode == OUTPUT) + value |= mask; + else + value &= ~mask; + writeRegister(reg, value); + + // Pullup 0 = enabled 1 = disabled + reg = TCA8418_REG_GPIO_PULL_1 + idx; + value = readRegister(reg); + if (mode == INPUT_PULLUP) + value &= ~mask; + else + value |= mask; + writeRegister(reg, value); + + return true; +} + +bool TCA8418KeyboardBase::pinIRQMode(uint8_t pinnum, uint8_t mode) +{ + if (pinnum > TCA8418_COL9) + return false; + if ((mode != RISING) && (mode != FALLING)) + return false; + + // Mode 0 = falling 1 = rising + uint8_t idx = pinnum / 8; + uint8_t reg = TCA8418_REG_GPIO_INT_LVL_1 + idx; + uint8_t mask = (1 << (pinnum % 8)); + + uint8_t value = readRegister(reg); + if (mode == RISING) + value |= mask; + else + value &= ~mask; + writeRegister(reg, value); + + // Enable interrupt + reg = TCA8418_REG_GPIO_INT_EN_1 + idx; + value = readRegister(reg); + value |= mask; + writeRegister(reg, value); + + return true; +} + +void TCA8418KeyboardBase::enableInterrupts() +{ + uint8_t value = readRegister(TCA8418_REG_CFG); + value |= (_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN); + writeRegister(TCA8418_REG_CFG, value); +}; + +void TCA8418KeyboardBase::disableInterrupts() +{ + uint8_t value = readRegister(TCA8418_REG_CFG); + value &= ~(_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN); + writeRegister(TCA8418_REG_CFG, value); +}; + +void TCA8418KeyboardBase::enableMatrixOverflow() +{ + uint8_t value = readRegister(TCA8418_REG_CFG); + value |= _TCA8418_REG_CFG_OVR_FLOW_M; + writeRegister(TCA8418_REG_CFG, value); +}; + +void TCA8418KeyboardBase::disableMatrixOverflow() +{ + uint8_t value = readRegister(TCA8418_REG_CFG); + value &= ~_TCA8418_REG_CFG_OVR_FLOW_M; + writeRegister(TCA8418_REG_CFG, value); +}; + +void TCA8418KeyboardBase::enableDebounce() +{ + writeRegister(TCA8418_REG_DEBOUNCE_DIS_1, 0x00); + writeRegister(TCA8418_REG_DEBOUNCE_DIS_2, 0x00); + writeRegister(TCA8418_REG_DEBOUNCE_DIS_3, 0x00); +} + +void TCA8418KeyboardBase::disableDebounce() +{ + writeRegister(TCA8418_REG_DEBOUNCE_DIS_1, 0xFF); + writeRegister(TCA8418_REG_DEBOUNCE_DIS_2, 0xFF); + writeRegister(TCA8418_REG_DEBOUNCE_DIS_3, 0xFF); +} + +void TCA8418KeyboardBase::setBacklight(bool on) {} + +uint8_t TCA8418KeyboardBase::readRegister(uint8_t reg) const +{ + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(reg); + m_wire->endTransmission(); + + m_wire->requestFrom(m_addr, (uint8_t)1); + if (m_wire->available() < 1) + return 0; + + return m_wire->read(); + } + if (readCallback) { + uint8_t data; + readCallback(m_addr, reg, &data, 1); + return data; + } + return 0; +} + +void TCA8418KeyboardBase::writeRegister(uint8_t reg, uint8_t value) +{ + uint8_t data[2]; + data[0] = reg; + data[1] = value; + + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(data, sizeof(uint8_t) * 2); + m_wire->endTransmission(); + } + if (writeCallback) { + writeCallback(m_addr, data[0], &(data[1]), 1); + } +} \ No newline at end of file diff --git a/src/input/TCA8418KeyboardBase.h b/src/input/TCA8418KeyboardBase.h new file mode 100644 index 000000000..5d6c4f7e9 --- /dev/null +++ b/src/input/TCA8418KeyboardBase.h @@ -0,0 +1,170 @@ +// Based on the MPR121 Keyboard and Adafruit TCA8418 library +#include "configuration.h" +#include + +/** + * @brief TCA8418KeyboardBase is the base class for TCA8418 keyboard handling. + * It provides basic functionality for reading key events, managing the keyboard matrix, + * and handling key states. It is designed to be extended for specific keyboard implementations. + * It supports both I2C communication and function pointers for custom I2C operations. + */ +class TCA8418KeyboardBase +{ + public: + enum TCA8418Key : uint8_t { + NONE = 0x00, + BSP = 0x08, + TAB = 0x09, + SELECT = 0x0d, + ESC = 0x1b, + REBOOT = 0x90, + LEFT = 0xb4, + UP = 0xb5, + DOWN = 0xb6, + RIGHT = 0xb7, + BT_TOGGLE = 0xAA, + GPS_TOGGLE = 0x9E, + MUTE_TOGGLE = 0xAC, + SEND_PING = 0xAF, + BL_TOGGLE = 0xAB + }; + + typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len); + + TCA8418KeyboardBase(uint8_t rows, uint8_t columns); + + virtual void begin(uint8_t addr = TCA8418_KB_ADDR, TwoWire *wire = &Wire); + virtual void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = TCA8418_KB_ADDR); + + virtual void reset(void); + virtual void trigger(void); + + virtual void setBacklight(bool on); + + // Key events available + virtual bool hasEvent(void) const; + virtual char dequeueEvent(void); + + protected: + enum KeyState { Init, Idle, Held, Busy }; + + enum TCA8418Register : uint8_t { + TCA8418_REG_RESERVED = 0x00, + TCA8418_REG_CFG = 0x01, + TCA8418_REG_INT_STAT = 0x02, + TCA8418_REG_KEY_LCK_EC = 0x03, + TCA8418_REG_KEY_EVENT_A = 0x04, + TCA8418_REG_KEY_EVENT_B = 0x05, + TCA8418_REG_KEY_EVENT_C = 0x06, + TCA8418_REG_KEY_EVENT_D = 0x07, + TCA8418_REG_KEY_EVENT_E = 0x08, + TCA8418_REG_KEY_EVENT_F = 0x09, + TCA8418_REG_KEY_EVENT_G = 0x0A, + TCA8418_REG_KEY_EVENT_H = 0x0B, + TCA8418_REG_KEY_EVENT_I = 0x0C, + TCA8418_REG_KEY_EVENT_J = 0x0D, + TCA8418_REG_KP_LCK_TIMER = 0x0E, + TCA8418_REG_UNLOCK_1 = 0x0F, + TCA8418_REG_UNLOCK_2 = 0x10, + TCA8418_REG_GPIO_INT_STAT_1 = 0x11, + TCA8418_REG_GPIO_INT_STAT_2 = 0x12, + TCA8418_REG_GPIO_INT_STAT_3 = 0x13, + TCA8418_REG_GPIO_DAT_STAT_1 = 0x14, + TCA8418_REG_GPIO_DAT_STAT_2 = 0x15, + TCA8418_REG_GPIO_DAT_STAT_3 = 0x16, + TCA8418_REG_GPIO_DAT_OUT_1 = 0x17, + TCA8418_REG_GPIO_DAT_OUT_2 = 0x18, + TCA8418_REG_GPIO_DAT_OUT_3 = 0x19, + TCA8418_REG_GPIO_INT_EN_1 = 0x1A, + TCA8418_REG_GPIO_INT_EN_2 = 0x1B, + TCA8418_REG_GPIO_INT_EN_3 = 0x1C, + TCA8418_REG_KP_GPIO_1 = 0x1D, + TCA8418_REG_KP_GPIO_2 = 0x1E, + TCA8418_REG_KP_GPIO_3 = 0x1F, + TCA8418_REG_GPI_EM_1 = 0x20, + TCA8418_REG_GPI_EM_2 = 0x21, + TCA8418_REG_GPI_EM_3 = 0x22, + TCA8418_REG_GPIO_DIR_1 = 0x23, + TCA8418_REG_GPIO_DIR_2 = 0x24, + TCA8418_REG_GPIO_DIR_3 = 0x25, + TCA8418_REG_GPIO_INT_LVL_1 = 0x26, + TCA8418_REG_GPIO_INT_LVL_2 = 0x27, + TCA8418_REG_GPIO_INT_LVL_3 = 0x28, + TCA8418_REG_DEBOUNCE_DIS_1 = 0x29, + TCA8418_REG_DEBOUNCE_DIS_2 = 0x2A, + TCA8418_REG_DEBOUNCE_DIS_3 = 0x2B, + TCA8418_REG_GPIO_PULL_1 = 0x2C, + TCA8418_REG_GPIO_PULL_2 = 0x2D, + TCA8418_REG_GPIO_PULL_3 = 0x2E + }; + + // Pin IDs for matrix rows/columns + enum TCA8418PinId : uint8_t { + TCA8418_ROW0, // Pin ID for row 0 + TCA8418_ROW1, // Pin ID for row 1 + TCA8418_ROW2, // Pin ID for row 2 + TCA8418_ROW3, // Pin ID for row 3 + TCA8418_ROW4, // Pin ID for row 4 + TCA8418_ROW5, // Pin ID for row 5 + TCA8418_ROW6, // Pin ID for row 6 + TCA8418_ROW7, // Pin ID for row 7 + TCA8418_COL0, // Pin ID for column 0 + TCA8418_COL1, // Pin ID for column 1 + TCA8418_COL2, // Pin ID for column 2 + TCA8418_COL3, // Pin ID for column 3 + TCA8418_COL4, // Pin ID for column 4 + TCA8418_COL5, // Pin ID for column 5 + TCA8418_COL6, // Pin ID for column 6 + TCA8418_COL7, // Pin ID for column 7 + TCA8418_COL8, // Pin ID for column 8 + TCA8418_COL9 // Pin ID for column 9 + }; + + virtual void pressed(uint8_t key); + virtual void released(void); + + virtual void queueEvent(char); + + virtual ~TCA8418KeyboardBase() {} + + protected: + // Set the size of the keypad matrix + // All other rows and columns are set as inputs. + bool matrix(uint8_t rows, uint8_t columns); + + uint8_t keyCount(void) const; + + // Flush all events in the FIFO buffer + GPIO events. + uint8_t flush(void); + + // debounce keys. + void enableDebounce(); + void disableDebounce(); + + // enable / disable interrupts for matrix and GPI pins + void enableInterrupts(); + void disableInterrupts(); + + // ignore key events when FIFO buffer is full or not. + void enableMatrixOverflow(); + void disableMatrixOverflow(); + + uint8_t digitalRead(uint8_t pinnum) const; + bool digitalWrite(uint8_t pinnum, uint8_t level); + bool pinMode(uint8_t pinnum, uint8_t mode); + bool pinIRQMode(uint8_t pinnum, uint8_t mode); // MODE FALLING or RISING + uint8_t readRegister(uint8_t reg) const; + void writeRegister(uint8_t reg, uint8_t value); + + protected: + uint8_t rows; + uint8_t columns; + KeyState state; + String queue; + + private: + TwoWire *m_wire; + uint8_t m_addr; + i2c_com_fptr_t readCallback; + i2c_com_fptr_t writeCallback; +}; diff --git a/src/input/TDeckProKeyboard.cpp b/src/input/TDeckProKeyboard.cpp new file mode 100644 index 000000000..098e0804a --- /dev/null +++ b/src/input/TDeckProKeyboard.cpp @@ -0,0 +1,196 @@ +#if defined(T_DECK_PRO) + +#include "TDeckProKeyboard.h" + +#define _TCA8418_COLS 10 +#define _TCA8418_ROWS 4 +#define _TCA8418_NUM_KEYS 35 + +#define _TCA8418_MULTI_TAP_THRESHOLD 1500 + +using Key = TCA8418KeyboardBase::TCA8418Key; + +constexpr uint8_t modifierRightShiftKey = 31 - 1; // keynum -1 +constexpr uint8_t modifierRightShift = 0b0001; +constexpr uint8_t modifierLeftShiftKey = 35 - 1; +constexpr uint8_t modifierLeftShift = 0b0001; +constexpr uint8_t modifierSymKey = 32 - 1; +constexpr uint8_t modifierSym = 0b0010; +constexpr uint8_t modifierAltKey = 30 - 1; +constexpr uint8_t modifierAlt = 0b0100; + +// Num chars per key, Modulus for rotating through characters +static uint8_t TDeckProTapMod[_TCA8418_NUM_KEYS] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}; + +static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = { + {'p', 'P', '@', 0x00, Key::SEND_PING}, + {'o', 'O', '+'}, + {'i', 'I', '-'}, + {'u', 'U', '_'}, + {'y', 'Y', ')'}, + {'t', 'T', '(', 0x00, Key::TAB}, + {'r', 'R', '3'}, + {'e', 'E', '2', 0x00, Key::UP}, + {'w', 'W', '1'}, + {'q', 'Q', '#', 0x00, Key::ESC}, // p, o, i, u, y, t, r, e, w, q + {Key::BSP, 0x00, 0x00}, + {'l', 'L', '"'}, + {'k', 'K', '\''}, + {'j', 'J', ';'}, + {'h', 'H', ':'}, + {'g', 'G', '/', 0x00, Key::GPS_TOGGLE}, + {'f', 'F', '6', 0x00, Key::RIGHT}, + {'d', 'D', '5'}, + {'s', 'S', '4', 0x00, Key::LEFT}, + {'a', 'A', '*'}, // bsp, l, k, j, h, g, f, d, s, a + {0x0d, 0x00, 0x00}, + {'$', 0x00, 0x00}, + {'m', 'M', '.', 0x00, Key::MUTE_TOGGLE}, + {'n', 'N', ','}, + {'b', 'B', '!', 0x00, Key::BL_TOGGLE}, + {'v', 'V', '?'}, + {'c', 'C', '9'}, + {'x', 'X', '8', 0x00, Key::DOWN}, + {'z', 'Z', '7'}, + {0x00, 0x00, 0x00}, // Ent, $, m, n, b, v, c, x, z, alt + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00}, + {0x20, 0x00, 0x00}, + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift +}; + +TDeckProKeyboard::TDeckProKeyboard() + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), + last_tap(0L), char_idx(0), tap_interval(0) +{ +} + +void TDeckProKeyboard::reset() +{ + TCA8418KeyboardBase::reset(); + pinMode(KB_BL_PIN, OUTPUT); + setBacklight(false); +} + +// handle multi-key presses (shift and alt) +void TDeckProKeyboard::trigger() +{ + uint8_t count = keyCount(); + if (count == 0) + return; + for (uint8_t i = 0; i < count; ++i) { + uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i); + uint8_t key = k & 0x7F; + if (k & 0x80) { + pressed(key); + } else { + released(); + state = Idle; + } + } +} + +void TDeckProKeyboard::pressed(uint8_t key) +{ + if (state == Init || state == Busy) { + return; + } + if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { + modifierFlag = 0; + } + + uint8_t next_key = 0; + int row = (key - 1) / 10; + int col = (key - 1) % 10; + + if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { + return; // Invalid key + } + + next_key = row * _TCA8418_COLS + col; + state = Held; + + uint32_t now = millis(); + tap_interval = now - last_tap; + + updateModifierFlag(next_key); + if (isModifierKey(next_key)) { + last_modifier_time = now; + } + + if (tap_interval < 0) { + last_tap = 0; + state = Busy; + return; + } + + if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { + char_idx = 0; + } else { + char_idx += 1; + } + + last_key = next_key; + last_tap = now; +} + +void TDeckProKeyboard::released() +{ + if (state != Held) { + return; + } + + if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { + last_key = -1; + state = Idle; + return; + } + + uint32_t now = millis(); + last_tap = now; + + if (TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]] == Key::BL_TOGGLE) { + toggleBacklight(); + return; + } + + queueEvent(TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]]); + if (isModifierKey(last_key) == false) + modifierFlag = 0; +} + +void TDeckProKeyboard::setBacklight(bool on) +{ + if (on) { + digitalWrite(KB_BL_PIN, HIGH); + } else { + digitalWrite(KB_BL_PIN, LOW); + } +} + +void TDeckProKeyboard::toggleBacklight(void) +{ + digitalWrite(KB_BL_PIN, !digitalRead(KB_BL_PIN)); +} + +void TDeckProKeyboard::updateModifierFlag(uint8_t key) +{ + if (key == modifierRightShiftKey) { + modifierFlag ^= modifierRightShift; + } else if (key == modifierLeftShiftKey) { + modifierFlag ^= modifierLeftShift; + } else if (key == modifierSymKey) { + modifierFlag ^= modifierSym; + } else if (key == modifierAltKey) { + modifierFlag ^= modifierAlt; + } +} + +bool TDeckProKeyboard::isModifierKey(uint8_t key) +{ + return (key == modifierRightShiftKey || key == modifierLeftShiftKey || key == modifierAltKey || key == modifierSymKey); +} + +#endif // T_DECK_PRO \ No newline at end of file diff --git a/src/input/TDeckProKeyboard.h b/src/input/TDeckProKeyboard.h new file mode 100644 index 000000000..617f3f20b --- /dev/null +++ b/src/input/TDeckProKeyboard.h @@ -0,0 +1,27 @@ +#include "TCA8418KeyboardBase.h" + +class TDeckProKeyboard : public TCA8418KeyboardBase +{ + public: + TDeckProKeyboard(); + void reset(void) override; + void trigger(void) override; + void setBacklight(bool on) override; + + protected: + void pressed(uint8_t key) override; + void released(void) override; + + void updateModifierFlag(uint8_t key); + bool isModifierKey(uint8_t key); + void toggleBacklight(void); + + private: + uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed + uint32_t last_modifier_time; // Timestamp of the last modifier key press + int8_t last_key; + int8_t next_key; + uint32_t last_tap; + uint8_t char_idx; + int32_t tap_interval; +}; diff --git a/src/input/TLoraPagerKeyboard.h b/src/input/TLoraPagerKeyboard.h new file mode 100644 index 000000000..d31b05978 --- /dev/null +++ b/src/input/TLoraPagerKeyboard.h @@ -0,0 +1,12 @@ +#include "TCA8418KeyboardBase.h" + +class TLoraPagerKeyboard : public TCA8418KeyboardBase +{ + public: + TLoraPagerKeyboard(); + void setBacklight(bool on) override{}; + + protected: + void pressed(uint8_t key) override{}; + void released(void) override{}; +}; diff --git a/src/input/TrackballInterruptBase.h b/src/input/TrackballInterruptBase.h index 2397839b9..92db8720e 100644 --- a/src/input/TrackballInterruptBase.h +++ b/src/input/TrackballInterruptBase.h @@ -4,8 +4,13 @@ #include "mesh/NodeDB.h" #ifndef TB_DIRECTION +#if ARCH_PORTDUINO +#include "PortduinoGlue.h" +#define TB_DIRECTION (PinStatus) settingsMap[tbDirection] +#else #define TB_DIRECTION RISING #endif +#endif class TrackballInterruptBase : public Observable, public concurrency::OSThread { diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 21ecf381a..fcbdd0a3f 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -67,4 +67,5 @@ void CardKbI2cImpl::init() } #endif inputBroker->registerSource(this); + kb_found = true; } \ No newline at end of file diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 5cc069816..5db1e39a9 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -3,10 +3,26 @@ #include "detect/ScanI2C.h" #include "detect/ScanI2CTwoWire.h" +#if defined(T_DECK_PRO) +#include "TDeckProKeyboard.h" +#elif defined(T_LORA_PAGER) +#include "TLoraPagerKeyboard.h" +#else +#include "TCA8418Keyboard.h" +#endif + extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; -KbI2cBase::KbI2cBase(const char *name) : concurrency::OSThread(name) +KbI2cBase::KbI2cBase(const char *name) + : concurrency::OSThread(name), +#if defined(T_DECK_PRO) + TCAKeyboard(*(new TDeckProKeyboard())) +#elif defined(T_LORA_PAGER) + TCAKeyboard(*(new TLoraPagerKeyboard())) +#else + TCAKeyboard(*(new TCA8418Keyboard())) +#endif { this->_originName = name; } @@ -43,8 +59,8 @@ int32_t KbI2cBase::runOnce() if (cardkb_found.address == MPR121_KB_ADDR) { MPRkeyboard.begin(MPR121_KB_ADDR, &Wire1); } - if (cardkb_found.address == XPOWERS_AXP192_AXP2101_ADDRESS) { - TCAKeyboard.begin(XPOWERS_AXP192_AXP2101_ADDRESS, &Wire1); + if (cardkb_found.address == TCA8418_KB_ADDR) { + TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire1); } break; #endif @@ -58,8 +74,8 @@ int32_t KbI2cBase::runOnce() if (cardkb_found.address == MPR121_KB_ADDR) { MPRkeyboard.begin(MPR121_KB_ADDR, &Wire); } - if (cardkb_found.address == XPOWERS_AXP192_AXP2101_ADDRESS) { - TCAKeyboard.begin(XPOWERS_AXP192_AXP2101_ADDRESS, &Wire); + if (cardkb_found.address == TCA8418_KB_ADDR) { + TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire); } break; case ScanI2C::NO_I2C: @@ -241,41 +257,65 @@ int32_t KbI2cBase::runOnce() e.kbchar = 0x00; e.source = this->_originName; switch (nextEvent) { - case _TCA8418_NONE: + case TCA8418KeyboardBase::NONE: e.inputEvent = INPUT_BROKER_NONE; e.kbchar = 0x00; break; - case _TCA8418_REBOOT: + case TCA8418KeyboardBase::REBOOT: e.inputEvent = INPUT_BROKER_ANYKEY; e.kbchar = INPUT_BROKER_MSG_REBOOT; break; - case _TCA8418_LEFT: + case TCA8418KeyboardBase::LEFT: e.inputEvent = INPUT_BROKER_LEFT; e.kbchar = 0x00; break; - case _TCA8418_UP: + case TCA8418KeyboardBase::UP: e.inputEvent = INPUT_BROKER_UP; e.kbchar = 0x00; break; - case _TCA8418_DOWN: + case TCA8418KeyboardBase::DOWN: e.inputEvent = INPUT_BROKER_DOWN; e.kbchar = 0x00; break; - case _TCA8418_RIGHT: + case TCA8418KeyboardBase::RIGHT: e.inputEvent = INPUT_BROKER_RIGHT; e.kbchar = 0x00; break; - case _TCA8418_BSP: + case TCA8418KeyboardBase::BSP: e.inputEvent = INPUT_BROKER_BACK; e.kbchar = 0x08; break; - case _TCA8418_SELECT: + case TCA8418KeyboardBase::SELECT: e.inputEvent = INPUT_BROKER_SELECT; e.kbchar = 0x00; break; - case _TCA8418_ESC: + case TCA8418KeyboardBase::ESC: e.inputEvent = INPUT_BROKER_CANCEL; - e.kbchar = 0; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::GPS_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_GPS_TOGGLE; + break; + case TCA8418KeyboardBase::SEND_PING: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_SEND_PING; + break; + case TCA8418KeyboardBase::MUTE_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE; + break; + case TCA8418KeyboardBase::BT_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE; + break; + case TCA8418KeyboardBase::BL_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE; + break; + case TCA8418KeyboardBase::TAB: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_TAB; break; default: if (nextEvent > 127) { @@ -291,6 +331,7 @@ int32_t KbI2cBase::runOnce() LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); this->notifyObservers(&e); } + TCAKeyboard.trigger(); } break; } diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h index 8193433fe..af7602979 100644 --- a/src/input/kbI2cBase.h +++ b/src/input/kbI2cBase.h @@ -3,10 +3,11 @@ #include "BBQ10Keyboard.h" #include "InputBroker.h" #include "MPR121Keyboard.h" -#include "TCA8418Keyboard.h" #include "Wire.h" #include "concurrency/OSThread.h" +class TCA8418KeyboardBase; + class KbI2cBase : public Observable, public concurrency::OSThread { public: @@ -22,6 +23,6 @@ class KbI2cBase : public Observable, public concurrency::OST BBQ10Keyboard Q10keyboard; MPR121Keyboard MPRkeyboard; - TCA8418Keyboard TCAKeyboard; + TCA8418KeyboardBase &TCAKeyboard; bool is_sym = false; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9e0985a3a..1868d98c7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,6 @@ #include "mesh/generated/meshtastic/config.pb.h" #include "meshUtils.h" #include "modules/Modules.h" -#include "shutdown.h" #include "sleep.h" #include "target_specific.h" #include @@ -286,7 +285,7 @@ void lateInitVariant() {} */ void printInfo() { - LOG_INFO("S:B:%d,%s", HW_VENDOR, optstr(APP_VERSION)); + LOG_INFO("S:B:%d,%s,%s,%s", HW_VENDOR, optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO)); } #ifndef PIO_UNIT_TESTING void setup() @@ -335,6 +334,15 @@ void setup() pinMode(TFT_CS, OUTPUT); digitalWrite(TFT_CS, HIGH); delay(100); +#elif defined(T_DECK_PRO) + pinMode(LORA_EN, OUTPUT); + digitalWrite(LORA_EN, HIGH); + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(PIN_EINK_CS, OUTPUT); + digitalWrite(PIN_EINK_CS, HIGH); #endif concurrency::hasBeenSetup = true; @@ -515,25 +523,11 @@ void setup() LOG_INFO("Scan for i2c devices"); #endif -#if defined(I2C_SDA1) && defined(ARCH_RP2040) - Wire1.setSDA(I2C_SDA1); - Wire1.setSCL(I2C_SCL1); - Wire1.begin(); - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1); -#elif defined(I2C_SDA1) && !defined(ARCH_RP2040) - Wire1.begin(I2C_SDA1, I2C_SCL1); - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1); -#elif defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2) +#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)) i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1); #endif -#if defined(I2C_SDA) && defined(ARCH_RP2040) - Wire.setSDA(I2C_SDA); - Wire.setSCL(I2C_SCL); - Wire.begin(); - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); -#elif defined(I2C_SDA) && !defined(ARCH_RP2040) - Wire.begin(I2C_SDA, I2C_SCL); +#if defined(I2C_SDA) i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); #elif defined(ARCH_PORTDUINO) if (settingsStrings[i2cdev] != "") { @@ -1056,8 +1050,9 @@ void setup() mainDelay.interruptFromISR(&higherWake); }; userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS; - userConfigNoScreen.longPress = INPUT_BROKER_SHUTDOWN; - userConfigNoScreen.longPressTime = 5000; + userConfigNoScreen.longPress = INPUT_BROKER_NONE; + userConfigNoScreen.longPressTime = 500; + userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN; userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING; userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE; UserButtonThread->initButton(userConfigNoScreen); @@ -1363,7 +1358,7 @@ void setup() if (!rIf->reconfigure()) { LOG_WARN("Reconfigure failed, rebooting"); if (screen) { - screen->showOverlayBanner("Rebooting..."); + screen->showSimpleBanner("Rebooting..."); } rebootAtMsec = millis() + 5000; } @@ -1436,6 +1431,9 @@ void setup() LOG_DEBUG("Free heap : %7d bytes", ESP.getFreeHeap()); LOG_DEBUG("Free PSRAM : %7d bytes", ESP.getFreePsram()); #endif + + // We manually run this to update the NodeStatus + nodeDB->notifyObservers(true); } #endif @@ -1531,7 +1529,7 @@ void loop() #ifdef ARCH_NRF52 nrf52Loop(); #endif - powerCommandsCheck(); + power->powerCommandsCheck(); #ifdef DEBUG_STACK static uint32_t lastPrint = 0; diff --git a/src/memGet.cpp b/src/memGet.cpp index ef1102f1e..e8cd177dd 100644 --- a/src/memGet.cpp +++ b/src/memGet.cpp @@ -10,6 +10,10 @@ #include "memGet.h" #include "configuration.h" +#ifdef ARCH_STM32WL +#include +#endif + MemGet memGet; /** @@ -24,6 +28,9 @@ uint32_t MemGet::getFreeHeap() return dbgHeapFree(); #elif defined(ARCH_RP2040) return rp2040.getFreeHeap(); +#elif defined(ARCH_STM32WL) + struct mallinfo m = mallinfo(); + return m.fordblks; // Total free space (bytes) #else // this platform does not have heap management function implemented return UINT32_MAX; @@ -42,6 +49,9 @@ uint32_t MemGet::getHeapSize() return dbgHeapTotal(); #elif defined(ARCH_RP2040) return rp2040.getTotalHeap(); +#elif defined(ARCH_STM32WL) + struct mallinfo m = mallinfo(); + return m.arena; // Non-mmapped space allocated (bytes) #else // this platform does not have heap management function implemented return UINT32_MAX; diff --git a/src/mesh/Default.h b/src/mesh/Default.h index 7a38e21f1..2f05da98d 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -24,6 +24,11 @@ #define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour #define min_neighbor_info_broadcast_secs 4 * 60 * 60 #define default_map_publish_interval_secs 60 * 60 +#ifdef USERPREFS_RINGTONE_NAG_SECS +#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS +#else +#define default_ringtone_nag_secs 60 +#endif #define default_mqtt_address "mqtt.meshtastic.org" #define default_mqtt_username "meshdev" diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index f8af81321..a64678a7f 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -121,7 +121,7 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool t bool MeshPacketQueue::find(const NodeNum from, const PacketId id) { for (auto it = queue.begin(); it != queue.end(); it++) { - const auto p = (*it); + const auto *p = *it; if (getFrom(p) == from && p->id == id) { return true; } diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 297c7b2ed..2cc4197c1 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -16,6 +16,7 @@ #include "meshUtils.h" #include "modules/NodeInfoModule.h" #include "modules/PositionModule.h" +#include "modules/RoutingModule.h" #include "power.h" #include #include @@ -333,6 +334,21 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage fromNum++; } +void MeshService::sendRoutingErrorResponse(meshtastic_Routing_Error error, const meshtastic_MeshPacket *mp) +{ + if (!mp) { + LOG_WARN("Cannot send routing error response: null packet"); + return; + } + + // Use the routing module to send the error response + if (routingModule) { + routingModule->sendAckNak(error, mp->from, mp->id, mp->channel); + } else { + LOG_ERROR("Cannot send routing error response: no routing module"); + } +} + void MeshService::sendClientNotification(meshtastic_ClientNotification *n) { LOG_DEBUG("Send client notification to phone"); diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index e2e430c03..f7d79366e 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -146,7 +146,10 @@ class MeshService virtual void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m); /// Send a ClientNotification to the phone - void sendClientNotification(meshtastic_ClientNotification *cn); + virtual void sendClientNotification(meshtastic_ClientNotification *cn); + + /// Send an error response to the phone + void sendRoutingErrorResponse(meshtastic_Routing_Error error, const meshtastic_MeshPacket *mp); bool isToPhoneQueueEmpty(); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index bd4911a9b..881dc6ab7 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -344,6 +344,22 @@ NodeDB::NodeDB() config.device.node_info_broadcast_secs = MAX_INTERVAL; if (config.position.position_broadcast_secs > MAX_INTERVAL) config.position.position_broadcast_secs = MAX_INTERVAL; + if (config.position.gps_update_interval > MAX_INTERVAL) + config.position.gps_update_interval = MAX_INTERVAL; + if (config.position.gps_attempt_time > MAX_INTERVAL) + config.position.gps_attempt_time = MAX_INTERVAL; + if (config.position.position_flags > MAX_INTERVAL) + config.position.position_flags = MAX_INTERVAL; + if (config.position.rx_gpio > MAX_INTERVAL) + config.position.rx_gpio = MAX_INTERVAL; + if (config.position.tx_gpio > MAX_INTERVAL) + config.position.tx_gpio = MAX_INTERVAL; + if (config.position.broadcast_smart_minimum_distance > MAX_INTERVAL) + config.position.broadcast_smart_minimum_distance = MAX_INTERVAL; + if (config.position.broadcast_smart_minimum_interval_secs > MAX_INTERVAL) + config.position.broadcast_smart_minimum_interval_secs = MAX_INTERVAL; + if (config.position.gps_en_gpio > MAX_INTERVAL) + config.position.gps_en_gpio = MAX_INTERVAL; if (moduleConfig.neighbor_info.update_interval > MAX_INTERVAL) moduleConfig.neighbor_info.update_interval = MAX_INTERVAL; if (moduleConfig.telemetry.device_update_interval > MAX_INTERVAL) @@ -369,6 +385,14 @@ NodeDB::NodeDB() config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY; } +#if !HAS_TFT + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { + // On a device without MUI, this display mode makes no sense, and will break logic. + config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; + config.bluetooth.enabled = true; + } +#endif + if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate))) saveWhat |= SEGMENT_DEVICESTATE; if (nodeDatabaseCRC != crc32Buffer(&nodeDatabase, sizeof(nodeDatabase))) @@ -382,6 +406,9 @@ NodeDB::NodeDB() config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; config.position.gps_enabled = 0; } +#ifdef USERPREFS_FIRMWARE_EDITION + myNodeInfo.firmware_edition = USERPREFS_FIRMWARE_EDITION; +#endif #ifdef USERPREFS_FIXED_GPS if (myNodeInfo.reboot_count == 1) { // Check if First boot ever or after Factory Reset. meshtastic_Position fixedGPS = meshtastic_Position_init_default; @@ -407,6 +434,7 @@ NodeDB::NodeDB() #endif } #endif + sortMeshDB(); saveToDisk(saveWhat); } @@ -603,11 +631,6 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) #ifdef PIN_GPS_EN config.position.gps_en_gpio = PIN_GPS_EN; #endif -#ifdef GPS_POWER_TOGGLE - config.device.disable_triple_click = false; -#else - config.device.disable_triple_click = true; -#endif #if defined(USERPREFS_CONFIG_GPS_MODE) config.position.gps_mode = USERPREFS_CONFIG_GPS_MODE; #elif !HAS_GPS || GPS_DEFAULT_NOT_PRESENT @@ -710,7 +733,6 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.display.screen_on_secs = 30; config.display.wake_on_tap_or_motion = true; #endif - #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI if (WiFiOTA::isUpdated()) { WiFiOTA::recoverConfig(&config.network); @@ -769,16 +791,23 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_buzzer = PIN_BUZZER; moduleConfig.external_notification.use_pwm = true; moduleConfig.external_notification.alert_message_buzzer = true; - moduleConfig.external_notification.nag_timeout = 60; + moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif -#if defined(RAK4630) || defined(RAK11310) +#if defined(PIN_VIBRATION) + moduleConfig.external_notification.enabled = true; + moduleConfig.external_notification.output_vibra = PIN_VIBRATION; + moduleConfig.external_notification.alert_message_vibra = true; + moduleConfig.external_notification.output_ms = 500; + moduleConfig.external_notification.nag_timeout = 2; +#endif +#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) // Default to RAK led pin 2 (blue) moduleConfig.external_notification.enabled = true; moduleConfig.external_notification.output = PIN_LED2; moduleConfig.external_notification.active = true; moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 1000; - moduleConfig.external_notification.nag_timeout = 60; + moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif #ifdef HAS_I2S @@ -787,10 +816,10 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.use_i2s_as_buzzer = true; moduleConfig.external_notification.alert_message_buzzer = true; #if HAS_TFT - if (moduleConfig.external_notification.nag_timeout == 60) + if (moduleConfig.external_notification.nag_timeout == default_ringtone_nag_secs) moduleConfig.external_notification.nag_timeout = 0; #else - moduleConfig.external_notification.nag_timeout = 60; + moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif #endif #ifdef NANO_G2_ULTRA @@ -1000,7 +1029,10 @@ void NodeDB::cleanupMeshDB() meshNodes->at(i).user.public_key.size = 0; } } - meshNodes->at(newPos++) = meshNodes->at(i); + if (newPos != i) + meshNodes->at(newPos++) = meshNodes->at(i); + else + newPos++; } else { removed++; } @@ -1087,8 +1119,8 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t if (f) { LOG_INFO("Load %s", filename); pb_istream_t stream = {&readcb, &f, protoSize}; - - memset(dest_struct, 0, objSize); + if (fields != &meshtastic_NodeDatabase_msg) // contains a vector object + memset(dest_struct, 0, objSize); if (!pb_decode(&stream, fields, dest_struct)) { LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream)); state = LoadFileResult::DECODE_FAILED; @@ -1156,7 +1188,7 @@ void NodeDB::loadFromDisk() LOG_WARN("Node count %d exceeds MAX_NUM_NODES %d, truncating", numMeshNodes, MAX_NUM_NODES); numMeshNodes = MAX_NUM_NODES; } - meshNodes->resize(MAX_NUM_NODES + 1); // The rp2040, rp2035, and maybe other targets, have a problem doing a sort() when full + meshNodes->resize(MAX_NUM_NODES); // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM state = loadProto(deviceStateFileName, meshtastic_DeviceState_size, sizeof(meshtastic_DeviceState), @@ -1297,6 +1329,13 @@ void NodeDB::loadFromDisk() saveToDisk(SEGMENT_MODULECONFIG); } +#if ARCH_PORTDUINO + // set any config overrides + if (settingsMap[has_configDisplayMode]) { + config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)settingsMap[configDisplayMode]; + } + +#endif } /** Save a protobuf from a file, return true for success */ @@ -1690,26 +1729,48 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) } } +void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId) +{ + meshtastic_NodeInfoLite *lite = getMeshNode(nodeId); + if (lite && lite->is_favorite != is_favorite) { + lite->is_favorite = is_favorite; + sortMeshDB(); + saveNodeDatabaseToDisk(); + } +} + +void NodeDB::pause_sort(bool paused) +{ + sortingIsPaused = paused; +} + void NodeDB::sortMeshDB() { - if (!Throttle::isWithinTimespanMs(lastSort, 1000 * 5)) { + if (!sortingIsPaused && (lastSort == 0 || !Throttle::isWithinTimespanMs(lastSort, 1000 * 5))) { lastSort = millis(); - std::sort(meshNodes->begin(), meshNodes->begin() + numMeshNodes, - [](const meshtastic_NodeInfoLite &a, const meshtastic_NodeInfoLite &b) { - if (a.num == myNodeInfo.my_node_num && b.num == myNodeInfo.my_node_num) // in theory impossible - return false; - if (a.num == myNodeInfo.my_node_num) { - return true; - } - if (b.num == myNodeInfo.my_node_num) { - return false; - } - bool aFav = a.is_favorite; - bool bFav = b.is_favorite; - if (aFav != bFav) - return aFav; - return a.last_heard > b.last_heard; - }); + bool changed = true; + while (changed) { // dumb reverse bubble sort, but probably not bad for what we're doing + changed = false; + for (int i = numMeshNodes - 1; i > 0; i--) { // lowest case this should examine is i == 1 + if (meshNodes->at(i - 1).num == getNodeNum()) { + // noop + } else if (meshNodes->at(i).num == + getNodeNum()) { // in the oddball case our own node num is not at location 0, put it there + // TODO: Look for at(i-1) also matching own node num, and throw the DB in the trash + std::swap(meshNodes->at(i), meshNodes->at(i - 1)); + changed = true; + } else if (meshNodes->at(i).is_favorite && !meshNodes->at(i - 1).is_favorite) { + std::swap(meshNodes->at(i), meshNodes->at(i - 1)); + changed = true; + } else if (!meshNodes->at(i).is_favorite && meshNodes->at(i - 1).is_favorite) { + // noop + } else if (meshNodes->at(i).last_heard > meshNodes->at(i - 1).last_heard) { + std::swap(meshNodes->at(i), meshNodes->at(i - 1)); + changed = true; + } + } + } + LOG_INFO("Sort took %u milliseconds", millis() - lastSort); } } @@ -1811,7 +1872,7 @@ UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum) return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed; } -bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t keyToTest) +bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest) { if (keyToTest.size == 32) { uint8_t keyHash[32] = {0}; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index b6e4d600b..19fb67b73 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -191,6 +191,16 @@ class NodeDB */ bool updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex = 0); + /* + * Sets a node either favorite or unfavorite + */ + void set_favorite(bool is_favorite, uint32_t nodeId); + + /** + * Other functions like the node picker can request a pause in the node sorting + */ + void pause_sort(bool paused); + /// @return our node number NodeNum getNodeNum() { return myNodeInfo.my_node_num; } @@ -208,9 +218,6 @@ class NodeDB their denial?) */ - /// pick a provisional nodenum we hope no one is using - void pickNewNodeNum(); - // get channel channel index we heard a nodeNum on, defaults to 0 if not found uint8_t getMeshNodeChannel(NodeNum n); @@ -272,12 +279,20 @@ class NodeDB bool hasValidPosition(const meshtastic_NodeInfoLite *n); - bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t keyToTest); + bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest); bool backupPreferences(meshtastic_AdminMessage_BackupLocation location); bool restorePreferences(meshtastic_AdminMessage_BackupLocation location, int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS); + /// Notify observers of changes to the DB + void notifyObservers(bool forceUpdate = false) + { + // Notify observers of the current node state + const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate); + newStatus.notifyObservers(&status); + } + private: bool duplicateWarned = false; uint32_t lastNodeDbSave = 0; // when we last saved our db to flash @@ -286,13 +301,13 @@ class NodeDB /// Find a node in our DB, create an empty NodeInfoLite if missing meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n); - /// Notify observers of changes to the DB - void notifyObservers(bool forceUpdate = false) - { - // Notify observers of the current node state - const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate); - newStatus.notifyObservers(&status); - } + /* + * Internal boolean to track sorting paused + */ + bool sortingIsPaused = false; + + /// pick a provisional nodenum we hope no one is using + void pickNewNodeNum(); /// read our db from flash void loadFromDisk(); diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index f42b151c8..3902c1057 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -181,7 +181,7 @@ PacketHistory::PacketRecord *PacketHistory::find(NodeNum sender, PacketId id) } /** Insert/Replace oldest PacketRecord in recentPackets. */ -void PacketHistory::insert(PacketRecord &r) +void PacketHistory::insert(const PacketRecord &r) { uint32_t now_millis = millis(); // Should not jump with time changes uint32_t OldtrxTimeMsec = 0; @@ -246,8 +246,10 @@ void PacketHistory::insert(PacketRecord &r) #if RECENT_WARN_AGE > 0 if (tu->rxTimeMsec && (OldtrxTimeMsec < RECENT_WARN_AGE)) { if (!(tu->id == r.id && tu->sender == r.sender)) { +#if VERBOSE_PACKET_HISTORY LOG_WARN("Packet History - insert: Reusing slot aged %ds < %ds RECENT_WARN_AGE", OldtrxTimeMsec / 1000, RECENT_WARN_AGE / 1000); +#endif } else { // debug only #if VERBOSE_PACKET_HISTORY @@ -275,7 +277,9 @@ void PacketHistory::insert(PacketRecord &r) #endif if (r.rxTimeMsec == 0) { +#if VERBOSE_PACKET_HISTORY LOG_WARN("Packet History - insert: I will not store packet with rxTimeMsec = 0."); +#endif return; // Return early if we can't update the history } @@ -304,7 +308,7 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const N return false; } - PacketRecord *found = find(sender, id); + const PacketRecord *found = find(sender, id); if (found == NULL) { #if VERBOSE_PACKET_HISTORY @@ -323,7 +327,7 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const N /* Check if a certain node was a relayer of a packet in the history given iterator * @return true if node was indeed a relayer, false if not */ -bool PacketHistory::wasRelayer(const uint8_t relayer, PacketRecord &r) +bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r) { for (uint8_t i = 0; i < NUM_RELAYERS; i++) { if (r.relayed_by[i] == relayer) { diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index d06c9bd2f..9f14a4cf0 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -31,11 +31,11 @@ class PacketHistory /** Insert/Replace oldest PacketRecord in mx_recentPackets. * @param r PacketRecord to insert or replace */ - void insert(PacketRecord &r); // Insert or replace a packet record in the history + void insert(const PacketRecord &r); // Insert or replace a packet record in the history /* Check if a certain node was a relayer of a packet in the history given iterator * @return true if node was indeed a relayer, false if not */ - bool wasRelayer(const uint8_t relayer, PacketRecord &r); + bool wasRelayer(const uint8_t relayer, const PacketRecord &r); PacketHistory(const PacketHistory &); // non construction-copyable PacketHistory &operator=(const PacketHistory &); // non copyable diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 287de38fa..83becb037 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -205,6 +205,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // app not to send locations on our behalf. fromRadioScratch.which_payload_variant = meshtastic_FromRadio_my_info_tag; strncpy(myNodeInfo.pio_env, optstr(APP_ENV), sizeof(myNodeInfo.pio_env)); + myNodeInfo.nodedb_count = static_cast(nodeDB->getNumMeshNodes()); fromRadioScratch.my_info = myNodeInfo; state = STATE_SEND_UIDATA; @@ -686,7 +687,8 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) LOG_WARN("Rate limit portnum %d", p.decoded.portnum); meshtastic_QueueStatus qs = router->getQueueStatus(); service->sendQueueStatusToPhone(qs, 0, p.id); - sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds"); + service->sendRoutingErrorResponse(meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED, &p); + // sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds"); return false; } lastPortNumToRadio[p.decoded.portnum] = millis(); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 91a4d0632..7590ac34d 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -12,6 +12,12 @@ #include #include +// Calculate 2^n without calling pow() +uint32_t pow_of_2(uint32_t n) +{ + return 1 << n; +} + #define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \ { \ meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \ @@ -61,6 +67,7 @@ const RegionInfo regions[] = { /* https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf + Also used in Brazil. */ RDEF(ANZ, 915.0f, 928.0f, 100, 0, 30, true, false, false), @@ -155,6 +162,29 @@ const RegionInfo regions[] = { RDEF(PH_433, 433.0f, 434.7f, 100, 0, 10, true, false, false), RDEF(PH_868, 868.0f, 869.4f, 100, 0, 14, true, false, false), RDEF(PH_915, 915.0f, 918.0f, 100, 0, 24, true, false, false), + /* + Kazakhstan + 433.075 - 434.775 MHz <10 mW EIRP, Low Powered Devices (LPD) + 863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields + https://github.com/meshtastic/firmware/issues/7204 + */ + RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 10, true, false, false), RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 30, true, false, true), + + + /* + Nepal + 865 MHz to 868 MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use, specifically in non-cellular mode. + https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf + */ + RDEF(NP_865, 865.0f, 868.0f, 100, 0, 30, true, false, false), + + /* + Brazil + 902 - 907.5 MHz , 1W power limit, no duty cycle restrictions + https://github.com/meshtastic/firmware/issues/3741 + */ + RDEF(BR_902, 902.0f, 907.5f, 100, 0, 30, true, false, false), + /* 2.4 GHZ WLAN Band equivalent. Only for SX128x chips. */ @@ -246,7 +276,7 @@ uint32_t RadioInterface::getRetransmissionMsec(const meshtastic_MeshPacket *p) float channelUtil = airTime->channelUtilizationPercent(); uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax); // Assuming we pick max. of CWsize and there will be a client with SNR at half the range - return 2 * packetAirtime + (pow(2, CWsize) + 2 * CWmax + pow(2, int((CWmax + CWmin) / 2))) * slotTimeMsec + + return 2 * packetAirtime + (pow_of_2(CWsize) + 2 * CWmax + pow_of_2(int((CWmax + CWmin) / 2))) * slotTimeMsec + PROCESSING_TIME_MSEC; } @@ -259,7 +289,7 @@ uint32_t RadioInterface::getTxDelayMsec() float channelUtil = airTime->channelUtilizationPercent(); uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax); // LOG_DEBUG("Current channel utilization is %f so setting CWsize to %d", channelUtil, CWsize); - return random(0, pow(2, CWsize)) * slotTimeMsec; + return random(0, pow_of_2(CWsize)) * slotTimeMsec; } /** The CW size to use when calculating SNR_based delays */ @@ -279,7 +309,7 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr) { uint8_t CWsize = getCWsize(snr); // offset the maximum delay for routers: (2 * CWmax * slotTimeMsec) - return (2 * CWmax * slotTimeMsec) + pow(2, CWsize) * slotTimeMsec; + return (2 * CWmax * slotTimeMsec) + pow_of_2(CWsize) * slotTimeMsec; } /** The delay to use when we want to flood a message */ @@ -296,7 +326,7 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay); } else { // offset the maximum delay for routers: (2 * CWmax * slotTimeMsec) - delay = (2 * CWmax * slotTimeMsec) + random(0, pow(2, CWsize)) * slotTimeMsec; + delay = (2 * CWmax * slotTimeMsec) + random(0, pow_of_2(CWsize)) * slotTimeMsec; LOG_DEBUG("rx_snr found in packet. Setting tx delay:%d", delay); } @@ -596,7 +626,7 @@ void RadioInterface::applyModemConfig() uint32_t RadioInterface::computeSlotTimeMsec() { float sumPropagationTurnaroundMACTime = 0.2 + 0.4 + 7; // in milliseconds - float symbolTime = pow(2, sf) / bw; // in milliseconds + float symbolTime = pow_of_2(sf) / bw; // in milliseconds if (myRegion->wideLora) { // CAD duration derived from AN1200.22 of SX1280 @@ -631,10 +661,6 @@ void RadioInterface::limitPower(int8_t loraMaxPower) if (power > loraMaxPower) // Clamp power to maximum defined level power = loraMaxPower; - if (TX_GAIN_LORA == 0) { // Setting power in config with defined TX_GAIN_LORA will cause decreasing power on each reboot - config.lora.tx_power = power; // Set limited power in config - } - LOG_INFO("Final Tx power: %d dBm", power); } @@ -675,4 +701,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} \ No newline at end of file +} diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 02968513c..a7508423a 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -224,9 +224,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) { float hourlyTxPercent = airTime->utilizationTXPercent(); if (hourlyTxPercent > myRegion->dutyCycle) { -#ifdef DEBUG_PORT uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle); + LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d mins", silentMinutes); + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); cn->has_reply_id = true; cn->reply_id = p->id; @@ -234,7 +235,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) cn->time = getValidTime(RTCQualityFromNet); sprintf(cn->message, "Duty cycle limit exceeded. You can send again in %d mins", silentMinutes); service->sendClientNotification(cn); -#endif + meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT; if (isFromUs(p)) { // only send NAK to API, not to the mesh abortSendAndNak(err, p); @@ -651,11 +652,12 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) shouldIgnoreNonstandardPorts = true; #endif if (shouldIgnoreNonstandardPorts && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && - IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_ATAK_FORWARDER, meshtastic_PortNum_ATAK_PLUGIN, - meshtastic_PortNum_PAXCOUNTER_APP, meshtastic_PortNum_IP_TUNNEL_APP, meshtastic_PortNum_AUDIO_APP, - meshtastic_PortNum_PRIVATE_APP, meshtastic_PortNum_DETECTION_SENSOR_APP, meshtastic_PortNum_RANGE_TEST_APP, - meshtastic_PortNum_REMOTE_HARDWARE_APP)) { - LOG_DEBUG("Ignore packet on blacklisted portnum for CORE_PORTNUMS_ONLY"); + !IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_TEXT_MESSAGE_APP, meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP, + meshtastic_PortNum_POSITION_APP, meshtastic_PortNum_NODEINFO_APP, meshtastic_PortNum_ROUTING_APP, + meshtastic_PortNum_TELEMETRY_APP, meshtastic_PortNum_ADMIN_APP, meshtastic_PortNum_ALERT_APP, + meshtastic_PortNum_KEY_VERIFICATION_APP, meshtastic_PortNum_WAYPOINT_APP, + meshtastic_PortNum_STORE_FORWARD_APP, meshtastic_PortNum_TRACEROUTE_APP)) { + LOG_DEBUG("Ignore packet on non-standard portnum for CORE_PORTNUMS_ONLY"); cancelSending(p->from, p->id); skipHandle = true; } diff --git a/src/mesh/aes-ccm.cpp b/src/mesh/aes-ccm.cpp index a650ba2fc..420d80e9a 100644 --- a/src/mesh/aes-ccm.cpp +++ b/src/mesh/aes-ccm.cpp @@ -10,6 +10,32 @@ #include "aes-ccm.h" #if !MESHTASTIC_EXCLUDE_PKI +/** + * Constant-time comparison of two byte arrays + * + * @param a First byte array to compare + * @param b Second byte array to compare + * @param len Number of bytes to compare + * @return 0 if arrays are equal, -1 if different or if inputs are invalid + */ +static int constant_time_compare(const void *a_, const void *b_, size_t len) +{ + /* Cast to volatile to prevent the compiler from optimizing out their comparison. */ + const volatile uint8_t *volatile a = (const volatile uint8_t *volatile)a_; + const volatile uint8_t *volatile b = (const volatile uint8_t *volatile)b_; + if (len == 0) + return 0; + if (a == NULL || b == NULL) + return -1; + size_t i; + volatile uint8_t d = 0U; + for (i = 0U; i < len; i++) { + d |= (a[i] ^ b[i]); + } + /* Constant time bit arithmetic to convert d > 0 to -1 and d = 0 to 0. */ + return (1 & ((d - 1) >> 8)) - 1; +} + static void WPA_PUT_BE16(uint8_t *a, uint16_t val) { a[0] = val >> 8; @@ -146,7 +172,7 @@ bool aes_ccm_ad(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t aes_ccm_encr(L, crypt, crypt_len, plain, a); aes_ccm_auth_start(M, L, nonce, aad, aad_len, crypt_len, x); aes_ccm_auth(plain, crypt_len, x); - if (memcmp(x, t, M) != 0) { // FIXME make const comp + if (constant_time_compare(x, t, M) != 0) { return false; } return true; diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp index 9c92a6c27..2b4f63512 100644 --- a/src/mesh/eth/ethClient.cpp +++ b/src/mesh/eth/ethClient.cpp @@ -68,6 +68,11 @@ static int32_t reconnectETH() initApiServer(); } #endif +#if HAS_UDP_MULTICAST + if (udpHandler && config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST) { + udpHandler->start(); + } +#endif ethStartupComplete = true; } diff --git a/src/mesh/eth/ethClient.h b/src/mesh/eth/ethClient.h index 9e1745b9f..3adf481d2 100644 --- a/src/mesh/eth/ethClient.h +++ b/src/mesh/eth/ethClient.h @@ -2,7 +2,6 @@ #include "configuration.h" #include -#include bool initEthernet(); bool isEthernetAvailable(); diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 071640b0d..bc0b780b9 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -7,9 +7,9 @@ #include "meshtastic/channel.pb.h" #include "meshtastic/config.pb.h" #include "meshtastic/connection_status.pb.h" +#include "meshtastic/device_ui.pb.h" #include "meshtastic/mesh.pb.h" #include "meshtastic/module_config.pb.h" -#include "meshtastic/device_ui.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index ed1849be8..8a68197f0 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -102,7 +102,11 @@ typedef enum _meshtastic_Config_DeviceConfig_BuzzerMode { meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY = 2, /* Non-notification system buzzer tones only. Buzzer is enabled only for non-notification tones such as button presses, startup, shutdown, but not for alerts. */ - meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY = 3 + meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY = 3, + /* Direct Message notifications only. + Buzzer is enabled only for direct messages and alerts, but not for button presses. + External notification config determines the specifics of the notification behavior. */ + meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY = 4 } meshtastic_Config_DeviceConfig_BuzzerMode; /* Bit field of boolean configuration options, indicating which optional @@ -285,7 +289,15 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode { /* Philippines 915mhz */ meshtastic_Config_LoRaConfig_RegionCode_PH_915 = 21, /* Australia / New Zealand 433MHz */ - meshtastic_Config_LoRaConfig_RegionCode_ANZ_433 = 22 + meshtastic_Config_LoRaConfig_RegionCode_ANZ_433 = 22, + /* Kazakhstan 433MHz */ + meshtastic_Config_LoRaConfig_RegionCode_KZ_433 = 23, + /* Kazakhstan 863MHz */ + meshtastic_Config_LoRaConfig_RegionCode_KZ_863 = 24, + /* Nepal 865MHz */ + meshtastic_Config_LoRaConfig_RegionCode_NP_865 = 25, + /* Brazil 902MHz */ + meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26 } meshtastic_Config_LoRaConfig_RegionCode; /* Standard predefined channel settings @@ -472,7 +484,8 @@ typedef struct _meshtastic_Config_DisplayConfig { /* Number of seconds the screen stays on after pressing the user button or receiving a message 0 for default of one minute MAXUINT for always on */ uint32_t screen_on_secs; - /* How the GPS coordinates are formatted on the OLED screen. */ + /* Deprecated in 2.7.4: Unused + How the GPS coordinates are formatted on the OLED screen. */ meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format; /* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds. Potentially useful for devices without user buttons. */ @@ -641,8 +654,8 @@ extern "C" { #define _meshtastic_Config_DeviceConfig_RebroadcastMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_RebroadcastMode)(meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY+1)) #define _meshtastic_Config_DeviceConfig_BuzzerMode_MIN meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED -#define _meshtastic_Config_DeviceConfig_BuzzerMode_MAX meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY -#define _meshtastic_Config_DeviceConfig_BuzzerMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_BuzzerMode)(meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY+1)) +#define _meshtastic_Config_DeviceConfig_BuzzerMode_MAX meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY +#define _meshtastic_Config_DeviceConfig_BuzzerMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_BuzzerMode)(meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY+1)) #define _meshtastic_Config_PositionConfig_PositionFlags_MIN meshtastic_Config_PositionConfig_PositionFlags_UNSET #define _meshtastic_Config_PositionConfig_PositionFlags_MAX meshtastic_Config_PositionConfig_PositionFlags_SPEED @@ -681,8 +694,8 @@ extern "C" { #define _meshtastic_Config_DisplayConfig_CompassOrientation_ARRAYSIZE ((meshtastic_Config_DisplayConfig_CompassOrientation)(meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED+1)) #define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET -#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_ANZ_433 -#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_ANZ_433+1)) +#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_BR_902 +#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_BR_902+1)) #define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST #define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index f78689cb2..ba1a52b27 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -6,10 +6,10 @@ #include #include #include "meshtastic/channel.pb.h" -#include "meshtastic/mesh.pb.h" -#include "meshtastic/telemetry.pb.h" #include "meshtastic/config.pb.h" #include "meshtastic/localonly.pb.h" +#include "meshtastic/mesh.pb.h" +#include "meshtastic/telemetry.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -362,7 +362,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size #define meshtastic_BackupPreferences_size 2271 #define meshtastic_ChannelFile_size 718 -#define meshtastic_DeviceState_size 1722 +#define meshtastic_DeviceState_size 1728 #define meshtastic_NodeInfoLite_size 196 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 98 diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 361d01b9a..85735357a 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -117,6 +117,8 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 9e0415198..8e6524042 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -6,11 +6,11 @@ #include #include "meshtastic/channel.pb.h" #include "meshtastic/config.pb.h" +#include "meshtastic/device_ui.pb.h" #include "meshtastic/module_config.pb.h" #include "meshtastic/portnums.pb.h" #include "meshtastic/telemetry.pb.h" #include "meshtastic/xmodem.pb.h" -#include "meshtastic/device_ui.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -247,32 +247,26 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO = 96, /* Elecrow CrowPanel Advance models, ESP32-S3 and TFT with SX1262 radio plugin */ meshtastic_HardwareModel_CROWPANEL = 97, - /* * - Lilygo LINK32 board with sensors */ + /* Lilygo LINK32 board with sensors */ meshtastic_HardwareModel_LINK_32 = 98, - /* * - Seeed Tracker L1 */ + /* Seeed Tracker L1 */ meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 = 99, - /* * - Seeed Tracker L1 EINK driver */ + /* Seeed Tracker L1 EINK driver */ meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK = 100, /* Reserved ID for future and past use */ meshtastic_HardwareModel_QWANTZ_TINY_ARMS = 101, - /* * - Lilygo T-Deck Pro */ + /* Lilygo T-Deck Pro */ meshtastic_HardwareModel_T_DECK_PRO = 102, - /* * - Lilygo TLora Pager */ + /* Lilygo TLora Pager */ meshtastic_HardwareModel_T_LORA_PAGER = 103, - /* * - GAT562 Mesh Trial Tracker */ + /* GAT562 Mesh Trial Tracker */ meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104, - /* * - RAKwireless WisMesh Tag */ + /* RAKwireless WisMesh Tag */ meshtastic_HardwareModel_WISMESH_TAG = 105, - /* * - RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */ + /* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */ meshtastic_HardwareModel_RAK3312 = 106, + /* Elecrow ThinkNode M5 https://www.elecrow.com/wiki/ThinkNode_M5_Meshtastic_LoRa_Signal_Transceiver_ESP32-S3.html */ + meshtastic_HardwareModel_THINKNODE_M5 = 107, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -331,6 +325,25 @@ typedef enum _meshtastic_CriticalErrorCode { meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE = 13 } meshtastic_CriticalErrorCode; +/* Enum to indicate to clients whether this firmware is a special firmware build, like an event. + The first 16 values are reserved for non-event special firmwares, like the Smart Citizen use case. */ +typedef enum _meshtastic_FirmwareEdition { + /* Vanilla firmware */ + meshtastic_FirmwareEdition_VANILLA = 0, + /* Firmware for use in the Smart Citizen environmental monitoring network */ + meshtastic_FirmwareEdition_SMART_CITIZEN = 1, + /* Open Sauce, the maker conference held yearly in CA */ + meshtastic_FirmwareEdition_OPEN_SAUCE = 16, + /* DEFCON, the yearly hacker conference */ + meshtastic_FirmwareEdition_DEFCON = 17, + /* Burning Man, the yearly hippie gathering in the desert */ + meshtastic_FirmwareEdition_BURNING_MAN = 18, + /* Hamvention, the Dayton amateur radio convention */ + meshtastic_FirmwareEdition_HAMVENTION = 19, + /* Placeholder for DIY and unofficial events */ + meshtastic_FirmwareEdition_DIY_EDITION = 127 +} meshtastic_FirmwareEdition; + /* Enum for modules excluded from a device's configuration. Each value represents a ModuleConfigType that can be toggled as excluded by setting its corresponding bit in the `excluded_modules` bitmask field. */ @@ -432,7 +445,10 @@ typedef enum _meshtastic_Routing_Error { /* Admin packet otherwise checks out, but uses a bogus or expired session key */ meshtastic_Routing_Error_ADMIN_BAD_SESSION_KEY = 36, /* Admin packet sent using PKC, but not from a public key on the admin key list */ - meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED = 37 + meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED = 37, + /* Airtime fairness rate limit exceeded for a packet + This typically enforced per portnum and is used to prevent a single node from monopolizing airtime */ + meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED = 38 } meshtastic_Routing_Error; /* The priority of this message for sending. @@ -917,6 +933,11 @@ typedef struct _meshtastic_MyNodeInfo { meshtastic_MyNodeInfo_device_id_t device_id; /* The PlatformIO environment used to build this firmware */ char pio_env[40]; + /* The indicator for whether this device is running event firmware and which */ + meshtastic_FirmwareEdition firmware_edition; + /* The number of nodes in the nodedb. + This is used by the phone to know how many NodeInfo packets to expect on want_config */ + uint16_t nodedb_count; } meshtastic_MyNodeInfo; /* Debug output from the device. @@ -1215,6 +1236,10 @@ extern "C" { #define _meshtastic_CriticalErrorCode_MAX meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE #define _meshtastic_CriticalErrorCode_ARRAYSIZE ((meshtastic_CriticalErrorCode)(meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE+1)) +#define _meshtastic_FirmwareEdition_MIN meshtastic_FirmwareEdition_VANILLA +#define _meshtastic_FirmwareEdition_MAX meshtastic_FirmwareEdition_DIY_EDITION +#define _meshtastic_FirmwareEdition_ARRAYSIZE ((meshtastic_FirmwareEdition)(meshtastic_FirmwareEdition_DIY_EDITION+1)) + #define _meshtastic_ExcludedModules_MIN meshtastic_ExcludedModules_EXCLUDED_NONE #define _meshtastic_ExcludedModules_MAX meshtastic_ExcludedModules_NETWORK_CONFIG #define _meshtastic_ExcludedModules_ARRAYSIZE ((meshtastic_ExcludedModules)(meshtastic_ExcludedModules_NETWORK_CONFIG+1)) @@ -1228,8 +1253,8 @@ extern "C" { #define _meshtastic_Position_AltSource_ARRAYSIZE ((meshtastic_Position_AltSource)(meshtastic_Position_AltSource_ALT_BAROMETRIC+1)) #define _meshtastic_Routing_Error_MIN meshtastic_Routing_Error_NONE -#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED -#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED+1)) +#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED +#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED+1)) #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX @@ -1261,6 +1286,7 @@ extern "C" { #define meshtastic_MeshPacket_delayed_ENUMTYPE meshtastic_MeshPacket_Delayed +#define meshtastic_MyNodeInfo_firmware_edition_ENUMTYPE meshtastic_FirmwareEdition #define meshtastic_LogRecord_level_ENUMTYPE meshtastic_LogRecord_Level @@ -1299,7 +1325,7 @@ extern "C" { #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0} #define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0} -#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, ""} +#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} #define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}} @@ -1330,7 +1356,7 @@ extern "C" { #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0} #define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0} -#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, ""} +#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} #define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}} @@ -1453,6 +1479,8 @@ extern "C" { #define meshtastic_MyNodeInfo_min_app_version_tag 11 #define meshtastic_MyNodeInfo_device_id_tag 12 #define meshtastic_MyNodeInfo_pio_env_tag 13 +#define meshtastic_MyNodeInfo_firmware_edition_tag 14 +#define meshtastic_MyNodeInfo_nodedb_count_tag 15 #define meshtastic_LogRecord_message_tag 1 #define meshtastic_LogRecord_time_tag 2 #define meshtastic_LogRecord_source_tag 3 @@ -1685,7 +1713,9 @@ X(a, STATIC, SINGULAR, UINT32, my_node_num, 1) \ X(a, STATIC, SINGULAR, UINT32, reboot_count, 8) \ X(a, STATIC, SINGULAR, UINT32, min_app_version, 11) \ X(a, STATIC, SINGULAR, BYTES, device_id, 12) \ -X(a, STATIC, SINGULAR, STRING, pio_env, 13) +X(a, STATIC, SINGULAR, STRING, pio_env, 13) \ +X(a, STATIC, SINGULAR, UENUM, firmware_edition, 14) \ +X(a, STATIC, SINGULAR, UINT32, nodedb_count, 15) #define meshtastic_MyNodeInfo_CALLBACK NULL #define meshtastic_MyNodeInfo_DEFAULT NULL @@ -1968,7 +1998,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LowEntropyKey_size 0 #define meshtastic_MeshPacket_size 378 #define meshtastic_MqttClientProxyMessage_size 501 -#define meshtastic_MyNodeInfo_size 77 +#define meshtastic_MyNodeInfo_size 83 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 #define meshtastic_NodeInfo_size 323 diff --git a/src/mesh/generated/meshtastic/powermon.pb.h b/src/mesh/generated/meshtastic/powermon.pb.h index 9d4d94193..3072b8ac5 100644 --- a/src/mesh/generated/meshtastic/powermon.pb.h +++ b/src/mesh/generated/meshtastic/powermon.pb.h @@ -11,7 +11,7 @@ /* Enum definitions */ /* Any significant power changing event in meshtastic should be tagged with a powermon state transition. -If you are making new meshtastic features feel free to add new entries at the end of this definition. */ + If you are making new meshtastic features feel free to add new entries at the end of this definition. */ typedef enum _meshtastic_PowerMon_State { meshtastic_PowerMon_State_None = 0, meshtastic_PowerMon_State_CPU_DeepSleep = 1, @@ -34,13 +34,13 @@ something like "S:PM:C,0x00001234,REASON" where the hex number is the bitmask of meshtastic_PowerMon_State_Screen_Drawing = 512, meshtastic_PowerMon_State_Wifi_On = 1024, /* GPS is actively trying to find our location -See GPSPowerState for more details */ + See GPSPowerState for more details */ meshtastic_PowerMon_State_GPS_Active = 2048 } meshtastic_PowerMon_State; /* What operation would we like the UUT to perform. -note: senders should probably set want_response in their request packets, so that they can know when the state -machine has started processing their request */ + note: senders should probably set want_response in their request packets, so that they can know when the state + machine has started processing their request */ typedef enum _meshtastic_PowerStressMessage_Opcode { /* Unset/unused */ meshtastic_PowerStressMessage_Opcode_UNSET = 0, @@ -67,7 +67,7 @@ typedef enum _meshtastic_PowerStressMessage_Opcode { /* Struct definitions */ /* Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs). -But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) */ + But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) */ typedef struct _meshtastic_PowerMon { char dummy_field; } meshtastic_PowerMon; diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 90b0d9d10..f758995c2 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -93,7 +93,13 @@ typedef enum _meshtastic_TelemetrySensorType { /* PCT2075 Temperature Sensor */ meshtastic_TelemetrySensorType_PCT2075 = 39, /* ADS1X15 ADC */ - meshtastic_TelemetrySensorType_ADS1X15 = 40 + meshtastic_TelemetrySensorType_ADS1X15 = 40, + /* ADS1X15 ADC_ALT */ + meshtastic_TelemetrySensorType_ADS1X15_ALT = 41, + /* Sensirion SFA30 Formaldehyde sensor */ + meshtastic_TelemetrySensorType_SFA30 = 42, + /* SEN5X PM SENSORS */ + meshtastic_TelemetrySensorType_SEN5X = 43 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -242,45 +248,81 @@ typedef struct _meshtastic_PowerMetrics { /* Air quality metrics */ typedef struct _meshtastic_AirQualityMetrics { - /* Concentration Units Standard PM1.0 */ + /* Concentration Units Standard PM1.0 in ug/m3 */ bool has_pm10_standard; uint32_t pm10_standard; - /* Concentration Units Standard PM2.5 */ + /* Concentration Units Standard PM2.5 in ug/m3 */ bool has_pm25_standard; uint32_t pm25_standard; - /* Concentration Units Standard PM10.0 */ + /* Concentration Units Standard PM10.0 in ug/m3 */ bool has_pm100_standard; uint32_t pm100_standard; - /* Concentration Units Environmental PM1.0 */ + /* Concentration Units Environmental PM1.0 in ug/m3 */ bool has_pm10_environmental; uint32_t pm10_environmental; - /* Concentration Units Environmental PM2.5 */ + /* Concentration Units Environmental PM2.5 in ug/m3 */ bool has_pm25_environmental; uint32_t pm25_environmental; - /* Concentration Units Environmental PM10.0 */ + /* Concentration Units Environmental PM10.0 in ug/m3 */ bool has_pm100_environmental; uint32_t pm100_environmental; - /* 0.3um Particle Count */ + /* 0.3um Particle Count in #/0.1l */ bool has_particles_03um; uint32_t particles_03um; - /* 0.5um Particle Count */ + /* 0.5um Particle Count in #/0.1l */ bool has_particles_05um; uint32_t particles_05um; - /* 1.0um Particle Count */ + /* 1.0um Particle Count in #/0.1l */ bool has_particles_10um; uint32_t particles_10um; - /* 2.5um Particle Count */ + /* 2.5um Particle Count in #/0.1l */ bool has_particles_25um; uint32_t particles_25um; - /* 5.0um Particle Count */ + /* 5.0um Particle Count in #/0.1l */ bool has_particles_50um; uint32_t particles_50um; - /* 10.0um Particle Count */ + /* 10.0um Particle Count in #/0.1l */ bool has_particles_100um; uint32_t particles_100um; /* CO2 concentration in ppm */ bool has_co2; uint32_t co2; + /* CO2 sensor temperature in degC */ + bool has_co2_temperature; + float co2_temperature; + /* CO2 sensor relative humidity in % */ + bool has_co2_humidity; + float co2_humidity; + /* Formaldehyde sensor formaldehyde concentration in ppb */ + bool has_form_formaldehyde; + float form_formaldehyde; + /* Formaldehyde sensor relative humidity in %RH */ + bool has_form_humidity; + float form_humidity; + /* Formaldehyde sensor temperature in degrees Celsius */ + bool has_form_temperature; + float form_temperature; + /* Concentration Units Standard PM4.0 in ug/m3 */ + bool has_pm40_standard; + uint32_t pm40_standard; + /* 4.0um Particle Count in #/0.1l */ + bool has_particles_40um; + uint32_t particles_40um; + /* PM Sensor Temperature */ + bool has_pm_temperature; + float pm_temperature; + /* PM Sensor humidity */ + bool has_pm_humidity; + float pm_humidity; + /* PM Sensor VOC Index */ + bool has_pm_voc_idx; + float pm_voc_idx; + /* PM Sensor NOx Index */ + bool has_pm_nox_idx; + float pm_nox_idx; + /* Typical Particle Size in um */ + bool has_particles_tps; + float particles_tps; } meshtastic_AirQualityMetrics; /* Local device mesh statistics */ @@ -392,8 +434,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_ADS1X15 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_ADS1X15+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SEN5X +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SEN5X+1)) @@ -409,7 +451,7 @@ extern "C" { #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} @@ -418,7 +460,7 @@ extern "C" { #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} @@ -482,6 +524,18 @@ extern "C" { #define meshtastic_AirQualityMetrics_particles_50um_tag 11 #define meshtastic_AirQualityMetrics_particles_100um_tag 12 #define meshtastic_AirQualityMetrics_co2_tag 13 +#define meshtastic_AirQualityMetrics_co2_temperature_tag 14 +#define meshtastic_AirQualityMetrics_co2_humidity_tag 15 +#define meshtastic_AirQualityMetrics_form_formaldehyde_tag 16 +#define meshtastic_AirQualityMetrics_form_humidity_tag 17 +#define meshtastic_AirQualityMetrics_form_temperature_tag 18 +#define meshtastic_AirQualityMetrics_pm40_standard_tag 19 +#define meshtastic_AirQualityMetrics_particles_40um_tag 20 +#define meshtastic_AirQualityMetrics_pm_temperature_tag 21 +#define meshtastic_AirQualityMetrics_pm_humidity_tag 22 +#define meshtastic_AirQualityMetrics_pm_voc_idx_tag 23 +#define meshtastic_AirQualityMetrics_pm_nox_idx_tag 24 +#define meshtastic_AirQualityMetrics_particles_tps_tag 25 #define meshtastic_LocalStats_uptime_seconds_tag 1 #define meshtastic_LocalStats_channel_utilization_tag 2 #define meshtastic_LocalStats_air_util_tx_tag 3 @@ -587,7 +641,19 @@ X(a, STATIC, OPTIONAL, UINT32, particles_10um, 9) \ X(a, STATIC, OPTIONAL, UINT32, particles_25um, 10) \ X(a, STATIC, OPTIONAL, UINT32, particles_50um, 11) \ X(a, STATIC, OPTIONAL, UINT32, particles_100um, 12) \ -X(a, STATIC, OPTIONAL, UINT32, co2, 13) +X(a, STATIC, OPTIONAL, UINT32, co2, 13) \ +X(a, STATIC, OPTIONAL, FLOAT, co2_temperature, 14) \ +X(a, STATIC, OPTIONAL, FLOAT, co2_humidity, 15) \ +X(a, STATIC, OPTIONAL, FLOAT, form_formaldehyde, 16) \ +X(a, STATIC, OPTIONAL, FLOAT, form_humidity, 17) \ +X(a, STATIC, OPTIONAL, FLOAT, form_temperature, 18) \ +X(a, STATIC, OPTIONAL, UINT32, pm40_standard, 19) \ +X(a, STATIC, OPTIONAL, UINT32, particles_40um, 20) \ +X(a, STATIC, OPTIONAL, FLOAT, pm_temperature, 21) \ +X(a, STATIC, OPTIONAL, FLOAT, pm_humidity, 22) \ +X(a, STATIC, OPTIONAL, FLOAT, pm_voc_idx, 23) \ +X(a, STATIC, OPTIONAL, FLOAT, pm_nox_idx, 24) \ +X(a, STATIC, OPTIONAL, FLOAT, particles_tps, 25) #define meshtastic_AirQualityMetrics_CALLBACK NULL #define meshtastic_AirQualityMetrics_DEFAULT NULL @@ -676,7 +742,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size -#define meshtastic_AirQualityMetrics_size 78 +#define meshtastic_AirQualityMetrics_size 150 #define meshtastic_DeviceMetrics_size 27 #define meshtastic_EnvironmentMetrics_size 113 #define meshtastic_HealthMetrics_size 11 diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h index d1cc1065c..d4e0eaa8c 100644 --- a/src/mesh/udp/UdpMulticastHandler.h +++ b/src/mesh/udp/UdpMulticastHandler.h @@ -4,8 +4,13 @@ #include "main.h" #include "mesh/Router.h" -#include +#if HAS_ETHERNET && defined(ARCH_NRF52) +#include "mesh/eth/ethClient.h" +#else #include +#endif + +#include #if HAS_ETHERNET && defined(USE_WS5500) #include @@ -22,11 +27,11 @@ class UdpMulticastHandler final void start() { if (udp.listenMulticast(udpIpAddress, UDP_MULTICAST_DEFAUL_PORT, 64)) { -#ifndef ARCH_PORTDUINO - // FIXME(PORTDUINO): arduino lacks IPAddress::toString() - LOG_DEBUG("UDP Listening on IP: %s", WiFi.localIP().toString().c_str()); +#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) + LOG_DEBUG("UDP Listening on IP: %u.%u.%u.%u:%u", udpIpAddress[0], udpIpAddress[1], udpIpAddress[2], udpIpAddress[3], + UDP_MULTICAST_DEFAUL_PORT); #else - LOG_DEBUG("UDP Listening"); + LOG_DEBUG("UDP Listening on IP: %s", WiFi.localIP().toString().c_str()); #endif udp.onPacket([this](AsyncUDPPacket packet) { onReceive(packet); }); } else { @@ -37,7 +42,10 @@ class UdpMulticastHandler final void onReceive(AsyncUDPPacket packet) { size_t packetLength = packet.length(); -#ifndef ARCH_PORTDUINO +#if defined(ARCH_NRF52) + IPAddress ip = packet.remoteIP(); + LOG_DEBUG("UDP broadcast from: %u.%u.%u.%u, len=%u", ip[0], ip[1], ip[2], ip[3], packetLength); +#elif !defined(ARCH_PORTDUINO) // FIXME(PORTDUINO): arduino lacks IPAddress::toString() LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength); #endif @@ -61,7 +69,11 @@ class UdpMulticastHandler final if (!mp || !udp) { return false; } -#ifndef ARCH_PORTDUINO +#if defined(ARCH_NRF52) + if (!isEthernetAvailable()) { + return false; + } +#elif !defined(ARCH_PORTDUINO) if (WiFi.status() != WL_CONNECTED) { return false; } diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 24be97ad7..7a56c258b 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -87,16 +87,18 @@ static void onNetworkConnected() // start mdns if (!MDNS.begin("Meshtastic")) { - LOG_ERROR("Error setting up MDNS responder!"); + LOG_ERROR("Error setting up mDNS responder!"); } else { LOG_INFO("mDNS Host: Meshtastic.local"); MDNS.addService("meshtastic", "tcp", SERVER_API_DEFAULT_PORT); +// ESPmDNS (ESP32) and SimpleMDNS (RP2040) have slightly different APIs for adding TXT records #ifdef ARCH_ESP32 - MDNS.addService("http", "tcp", 80); - MDNS.addService("https", "tcp", 443); + MDNS.addServiceTxt("meshtastic", "tcp", "shortname", String(owner.short_name)); + MDNS.addServiceTxt("meshtastic", "tcp", "id", String(owner.id)); // ESP32 prints obtained IP address in WiFiEvent #elif defined(ARCH_RP2040) - // ARCH_RP2040 does not support HTTPS + MDNS.addServiceTxt("meshtastic", "shortname", owner.short_name); + MDNS.addServiceTxt("meshtastic", "id", owner.id); LOG_INFO("Obtained IP address: %s", WiFi.localIP().toString().c_str()); #endif } diff --git a/src/meshUtils.h b/src/meshUtils.h index 35b88e8b2..9fcf6f8a8 100644 --- a/src/meshUtils.h +++ b/src/meshUtils.h @@ -13,8 +13,9 @@ template constexpr const T &clamp(const T &v, const T &lo, const T &hi #if HAS_SCREEN #define IF_SCREEN(X) \ - if (screen) \ - X; + if (screen) { \ + X; \ + } #else #define IF_SCREEN(...) #endif diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index aad7f5f06..1a9c3a7a7 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -43,6 +43,10 @@ #if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C #include "motion/AccelerometerThread.h" #endif +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) +#include "SerialModule.h" +#endif AdminModule *adminModule; bool hasOpenEditTransaction; @@ -596,7 +600,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) if (config.device.button_gpio == c.payload_variant.device.button_gpio && config.device.buzzer_gpio == c.payload_variant.device.buzzer_gpio && config.device.role == c.payload_variant.device.role && - config.device.disable_triple_click == c.payload_variant.device.disable_triple_click && config.device.rebroadcast_mode == c.payload_variant.device.rebroadcast_mode) { requiresReboot = false; } @@ -630,6 +633,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) #if USERPREFS_EVENT_MODE // If we're in event mode, nobody is a Router or Repeater if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; } @@ -638,7 +642,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) case meshtastic_Config_position_tag: LOG_INFO("Set config: Position"); config.has_position = true; + // If we have turned off the GPS (disabled or not present) and we're not using fixed position, + // clear the stored position since it may not get updated + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED && + c.payload_variant.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED && + config.position.fixed_position == false && c.payload_variant.position.fixed_position == false) { + nodeDB->clearLocalPosition(); + saveChanges(SEGMENT_NODEDATABASE | SEGMENT_CONFIG, false); + } config.position = c.payload_variant.position; + // Save nodedb as well in case we got a fixed position packet break; case meshtastic_Config_power_tag: @@ -798,8 +811,13 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) { - if (!hasOpenEditTransaction) + // If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth + // Otherwise, disable Bluetooth to prevent the phone from interfering with the config + if (!hasOpenEditTransaction && + !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) { disableBluetooth(); + } + switch (c.which_payload_variant) { case meshtastic_ModuleConfig_mqtt_tag: #if MESHTASTIC_EXCLUDE_MQTT @@ -810,12 +828,22 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) if (!MQTT::isValidConfig(c.payload_variant.mqtt)) { return false; } + // Disable Bluetooth to prevent interference during MQTT configuration + disableBluetooth(); moduleConfig.has_mqtt = true; moduleConfig.mqtt = c.payload_variant.mqtt; #endif break; case meshtastic_ModuleConfig_serial_tag: LOG_INFO("Set module config: Serial"); +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) + if (!SerialModule::isValidConfig(c.payload_variant.serial)) { + LOG_ERROR("Invalid serial config"); + return false; + } + disableBluetooth(); // Disable Bluetooth to prevent interference during Serial configuration +#endif moduleConfig.has_serial = true; moduleConfig.serial = c.payload_variant.serial; break; @@ -971,9 +999,10 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32 // So even if we internally use 0 to represent 'use default' we still need to send the value we are // using to the app (so that even old phone apps work with new device loads). // r.get_radio_response.preferences.ls_secs = getPref_ls_secs(); - // hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally private - // and useful for users to know current provisioning) hideSecret(r.get_radio_response.preferences.wifi_password); - // r.get_config_response.which_payloadVariant = Config_ModuleConfig_telemetry_tag; + // hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally + // private and useful for users to know current provisioning) + // hideSecret(r.get_radio_response.preferences.wifi_password); r.get_config_response.which_payloadVariant = + // Config_ModuleConfig_telemetry_tag; res.which_payload_variant = meshtastic_AdminMessage_get_config_response_tag; setPassKey(&res); myReply = allocDataProtobuf(res); @@ -1057,9 +1086,10 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const // So even if we internally use 0 to represent 'use default' we still need to send the value we are // using to the app (so that even old phone apps work with new device loads). // r.get_radio_response.preferences.ls_secs = getPref_ls_secs(); - // hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally private - // and useful for users to know current provisioning) hideSecret(r.get_radio_response.preferences.wifi_password); - // r.get_config_response.which_payloadVariant = Config_ModuleConfig_telemetry_tag; + // hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally + // private and useful for users to know current provisioning) + // hideSecret(r.get_radio_response.preferences.wifi_password); r.get_config_response.which_payloadVariant = + // Config_ModuleConfig_telemetry_tag; res.which_payload_variant = meshtastic_AdminMessage_get_module_config_response_tag; setPassKey(&res); myReply = allocDataProtobuf(res); @@ -1190,7 +1220,7 @@ void AdminModule::reboot(int32_t seconds) { LOG_INFO("Reboot in %d seconds", seconds); if (screen) - screen->showOverlayBanner("Rebooting...", 0); // stays on screen + screen->showSimpleBanner("Rebooting...", 0); // stays on screen rebootAtMsec = (seconds < 0) ? 0 : (millis() + seconds * 1000); } @@ -1326,12 +1356,6 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent LOG_DEBUG("Processing input event: event_code=%u, kb_char=%u, touch_x=%u, touch_y=%u", inputEvent.event_code, inputEvent.kb_char, inputEvent.touch_x, inputEvent.touch_y); - // Validate input parameters - if (inputEvent.event_code > INPUT_BROKER_ANYKEY) { - LOG_WARN("Invalid input event code: %u", inputEvent.event_code); - return; - } - // Create InputEvent for injection InputEvent event = {.inputEvent = (input_broker_event)inputEvent.event_code, .kbchar = (unsigned char)inputEvent.kb_char, diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 4d8d6ce4b..ed930db41 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -56,6 +56,7 @@ CannedMessageModule::CannedMessageModule() disable(); } else { LOG_INFO("CannedMessageModule is enabled"); + moduleConfig.canned_message.enabled = true; this->inputObserver.observe(inputBroker); } } @@ -442,25 +443,34 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event return 1; } - // UP - if (isUp && destIndex > 0) { - destIndex--; + if (isUp) { + if (destIndex > 0) { + destIndex--; + } else if (totalEntries > 0) { + destIndex = totalEntries - 1; + } + if ((destIndex / columns) < scrollIndex) scrollIndex = destIndex / columns; else if ((destIndex / columns) >= (scrollIndex + visibleRows)) scrollIndex = (destIndex / columns) - visibleRows + 1; - screen->forceDisplay(); + screen->forceDisplay(true); return 1; } - // DOWN - if (isDown && destIndex + 1 < totalEntries) { - destIndex++; + if (isDown) { + if (destIndex + 1 < totalEntries) { + destIndex++; + } else if (totalEntries > 0) { + destIndex = 0; + scrollIndex = 0; + } + if ((destIndex / columns) >= (scrollIndex + visibleRows)) scrollIndex = (destIndex / columns) - visibleRows + 1; - screen->forceDisplay(); + screen->forceDisplay(true); return 1; } @@ -482,7 +492,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event runState = returnToCannedList ? CANNED_MESSAGE_RUN_STATE_ACTIVE : CANNED_MESSAGE_RUN_STATE_FREETEXT; returnToCannedList = false; - screen->forceDisplay(); + screen->forceDisplay(true); return 1; } @@ -495,7 +505,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event // UIFrameEvent e; // e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // notifyObservers(&e); - screen->forceDisplay(); + screen->forceDisplay(true); return 1; } @@ -707,7 +717,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) } // Backspace - if (event->inputEvent == INPUT_BROKER_BACK) { + if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) { payload = 0x08; lastTouchMillis = millis(); runOnce(); @@ -730,7 +740,8 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) } // Cancel (dismiss freetext screen) - if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG) { + if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG || + (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() == 0)) { runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; freetext = ""; cursor = 0; @@ -840,7 +851,13 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha this->waitingForAck = true; // Log outgoing message - LOG_INFO("Send message id=%d, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); + LOG_INFO("Send message id=%u, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); + + if (p->to != 0xffffffff) { + LOG_INFO("Proactively adding %x as favorite node", p->to); + nodeDB->set_favorite(true, p->to); + screen->setFrames(graphics::Screen::FOCUS_PRESERVE); + } // Send to mesh and phone (even if no phone connected, to track ACKs) service->sendToMesh(p, RX_SRC_LOCAL, true); @@ -980,6 +997,7 @@ int32_t CannedMessageModule::runOnce() } this->cursor--; } + } else { } break; case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler @@ -1430,7 +1448,7 @@ void CannedMessageModule::drawEmotePickerScreen(OLEDDisplay *display, OLEDDispla int headerY = y; int listTop = headerY + headerFontHeight + headerMargin; - int visibleRows = (display->getHeight() - listTop - 2) / rowHeight; + int _visibleRows = (display->getHeight() - listTop - 2) / rowHeight; int numEmotes = graphics::numEmotes; // Clamp highlight index @@ -1440,11 +1458,11 @@ void CannedMessageModule::drawEmotePickerScreen(OLEDDisplay *display, OLEDDispla emotePickerIndex = numEmotes - 1; // Determine which emote is at the top - int topIndex = emotePickerIndex - visibleRows / 2; + int topIndex = emotePickerIndex - _visibleRows / 2; if (topIndex < 0) topIndex = 0; - if (topIndex > numEmotes - visibleRows) - topIndex = std::max(0, numEmotes - visibleRows); + if (topIndex > numEmotes - _visibleRows) + topIndex = std::max(0, numEmotes - _visibleRows); // Draw header/title display->setFont(FONT_SMALL); @@ -1692,7 +1710,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } else { // Text: split by words and wrap inside word if needed String text = token.second; - uint16_t pos = 0; + pos = 0; while (pos < text.length()) { // Find next space (or end) int spacePos = text.indexOf(' ', pos); @@ -1736,7 +1754,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st int yLine = inputY; for (auto &line : lines) { int nextX = x; - for (auto &token : line) { + for (const auto &token : line) { if (token.first) { const graphics::Emote *emote = nullptr; for (int j = 0; j < graphics::numEmotes; j++) { @@ -1772,19 +1790,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st int topMsg; std::vector rowHeights; - int visibleRows; + int _visibleRows; // Draw header (To: ...) drawHeader(display, x, y, buffer); // Shift message list upward by 3 pixels to reduce spacing between header and first message const int listYOffset = y + FONT_HEIGHT_SMALL - 3; - visibleRows = (display->getHeight() - listYOffset) / baseRowSpacing; + _visibleRows = (display->getHeight() - listYOffset) / baseRowSpacing; // Figure out which messages are visible and their needed heights - topMsg = - (messagesCount > visibleRows && currentMessageIndex >= visibleRows - 1) ? currentMessageIndex - visibleRows + 2 : 0; - int countRows = std::min(messagesCount, visibleRows); + topMsg = (messagesCount > _visibleRows && currentMessageIndex >= _visibleRows - 1) + ? currentMessageIndex - _visibleRows + 2 + : 0; + int countRows = std::min(messagesCount, _visibleRows); // --- Build per-row max height based on all emotes in line --- for (int i = 0; i < countRows; i++) { @@ -1811,7 +1830,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st int lineY = yCursor; const char *msg = getMessageByIndex(msgIdx); int rowHeight = rowHeights[vis]; - bool highlight = (msgIdx == currentMessageIndex); + bool _highlight = (msgIdx == currentMessageIndex); // --- Multi-emote tokenization --- std::vector> tokens; // (isEmote, token) @@ -1864,20 +1883,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st int textYOffset = (rowHeight - FONT_HEIGHT_SMALL) / 2; #ifdef USE_EINK - int nextX = x + (highlight ? 12 : 0); - if (highlight) + int nextX = x + (_highlight ? 12 : 0); + if (_highlight) display->drawString(x + 0, lineY + textYOffset, ">"); #else int scrollPadding = 8; - if (highlight) { + if (_highlight) { display->fillRect(x + 0, lineY, display->getWidth() - scrollPadding, rowHeight); display->setColor(BLACK); } - int nextX = x + (highlight ? 2 : 0); + int nextX = x + (_highlight ? 2 : 0); #endif // Draw all tokens left to right - for (auto &token : tokens) { + for (const auto &token : tokens) { if (token.first) { // Emote const graphics::Emote *emote = nullptr; @@ -1899,7 +1918,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } } #ifndef USE_EINK - if (highlight) + if (_highlight) display->setColor(WHITE); #endif @@ -1907,11 +1926,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } // Scrollbar - if (messagesCount > visibleRows) { + if (messagesCount > _visibleRows) { int scrollHeight = display->getHeight() - listYOffset; int scrollTrackX = display->getWidth() - 6; display->drawRect(scrollTrackX, listYOffset, 4, scrollHeight); - int barHeight = (scrollHeight * visibleRows) / messagesCount; + int barHeight = (scrollHeight * _visibleRows) / messagesCount; int scrollPos = listYOffset + (scrollHeight * topMsg) / messagesCount; display->fillRect(scrollTrackX, scrollPos, 4, barHeight); } @@ -2057,6 +2076,9 @@ void CannedMessageModule::handleSetCannedMessageModuleMessages(const char *from_ if (changed) { this->saveProtoForModule(); + if (splitConfiguredMessages()) { + moduleConfig.canned_message.enabled = true; + } } } @@ -2066,4 +2088,4 @@ String CannedMessageModule::drawWithCursor(String text, int cursor) return result; } -#endif \ No newline at end of file +#endif diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 956508ce5..1f871f87e 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -126,9 +126,11 @@ int32_t ExternalNotificationModule::runOnce() millis()) { setExternalState(1, !getExternal(1)); } - if (externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms + // Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL) + if (!moduleConfig.external_notification.use_pwm && + externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms : EXT_NOTIFICATION_MODULE_OUTPUT_MS) < - millis()) { + millis()) { LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms, millis()); setExternalState(2, !getExternal(2)); @@ -247,7 +249,8 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on) digitalWrite(moduleConfig.external_notification.output_vibra, on); break; case 2: - if (moduleConfig.external_notification.output_buzzer) + // Only control buzzer pin digitally if not using PWM mode + if (moduleConfig.external_notification.output_buzzer && !moduleConfig.external_notification.use_pwm) digitalWrite(moduleConfig.external_notification.output_buzzer, on); break; default: @@ -320,6 +323,11 @@ void ExternalNotificationModule::stopNow() #endif nagCycleCutoff = 1; // small value isNagging = false; + // Turn off all outputs + for (int i = 0; i < 3; i++) { + setExternalState(i, false); + externalTurnedOn[i] = 0; + } setIntervalFromNow(0); #ifdef T_WATCH_S3 drv.stop(); @@ -362,9 +370,8 @@ ExternalNotificationModule::ExternalNotificationModule() if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) { memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); - strncpy(rtttlConfig.ringtone, - "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p", - sizeof(rtttlConfig.ringtone)); + // The default ringtone is always loaded from userPrefs.jsonc + strncpy(rtttlConfig.ringtone, USERPREFS_RINGTONE_RTTTL, sizeof(rtttlConfig.ringtone)); } LOG_INFO("Init External Notification Module"); @@ -479,14 +486,17 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP if (containsBell) { LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)"); isNagging = true; - if (!moduleConfig.external_notification.use_pwm) { + if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { setExternalState(2, true); } else { #ifdef HAS_I2S - audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); -#else - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + if (moduleConfig.external_notification.use_i2s_as_buzzer) { + audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); + } else #endif + if (moduleConfig.external_notification.use_pwm) { + rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + } } if (moduleConfig.external_notification.nag_timeout) { nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; @@ -527,10 +537,11 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP #ifdef HAS_I2S if (moduleConfig.external_notification.use_i2s_as_buzzer) { audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); - } -#else - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + } else #endif + if (moduleConfig.external_notification.use_pwm) { + rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + } } if (moduleConfig.external_notification.nag_timeout) { nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; diff --git a/src/modules/KeyVerificationModule.cpp b/src/modules/KeyVerificationModule.cpp index c0972c155..3b8225763 100644 --- a/src/modules/KeyVerificationModule.cpp +++ b/src/modules/KeyVerificationModule.cpp @@ -2,7 +2,9 @@ #include "KeyVerificationModule.h" #include "MeshService.h" #include "RTC.h" +#include "graphics/draw/MenuHandler.h" #include "main.h" +#include "meshUtils.h" #include "modules/AdminModule.h" #include @@ -48,18 +50,22 @@ AdminMessageHandleResult KeyVerificationModule::handleAdminMessageForModule(cons bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_KeyVerification *r) { updateState(); - if (mp.pki_encrypted == false) + if (mp.pki_encrypted == false) { return false; - if (mp.from != currentRemoteNode) // because the inital connection request is handled in allocReply() + } + if (mp.from != currentRemoteNode) { // because the inital connection request is handled in allocReply() return false; + } if (currentState == KEY_VERIFICATION_IDLE) { return false; // if we're idle, the only acceptable message is an init, which should be handled by allocReply() + } - } else if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 && - r->hash1.size == 0) { + if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 && + r->hash1.size == 0) { memcpy(hash2, r->hash2.bytes, 32); - if (screen) - screen->showOverlayBanner("Enter Security Number", 30000); + IF_SCREEN(screen->showNumberPicker("Enter Security Number", 60000, 6, [](int number_picked) -> void { + keyVerificationModule->processSecurityNumber(number_picked); + });) meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); cn->level = meshtastic_LogRecord_Level_WARNING; @@ -79,16 +85,20 @@ bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket & memset(message, 0, sizeof(message)); sprintf(message, "Verification: \n"); generateVerificationCode(message + 15); - static const char *optionsArray[] = {"ACCEPT", "REJECT"}; LOG_INFO("Hash1 matches!"); - if (screen) { - screen->showOverlayBanner(message, 30000, optionsArray, 2, [=](int selected) { - if (selected == 0) { - auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode); - remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK; - } - }); - } + static const char *optionsArray[] = {"Reject", "Accept"}; + // Don't try to put the array definition in the macro. Does not work with curly braces. + IF_SCREEN(graphics::BannerOverlayOptions options; options.message = message; options.durationMs = 30000; + options.optionsArrayPtr = optionsArray; options.optionsCount = 2; + options.notificationType = graphics::notificationTypeEnum::selection_picker; + options.bannerCallback = + [=](int selected) { + if (selected == 1) { + auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode); + remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK; + } + }; + screen->showOverlayBanner(options);) meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); cn->level = meshtastic_LogRecord_Level_WARNING; sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message); @@ -113,6 +123,7 @@ bool KeyVerificationModule::sendInitialRequest(NodeNum remoteNode) // generate nonce updateState(); if (currentState != KEY_VERIFICATION_IDLE) { + IF_SCREEN(graphics::menuHandler::menuQueue = graphics::menuHandler::throttle_message;) return false; } currentNonce = random(); @@ -183,11 +194,8 @@ meshtastic_MeshPacket *KeyVerificationModule::allocReply() responsePacket = allocDataProtobuf(response); responsePacket->pki_encrypted = true; - if (screen) { - snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000); - screen->showOverlayBanner(message, 30000); - LOG_WARN("%s", message); - } + IF_SCREEN(snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000); + screen->showSimpleBanner(message, 30000); LOG_WARN("%s", message);) meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); cn->level = meshtastic_LogRecord_Level_WARNING; sprintf(cn->message, "Incoming Key Verification.\nSecurity Number\n%03u %03u", currentSecurityNumber / 1000, @@ -251,12 +259,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber) p->priority = meshtastic_MeshPacket_Priority_HIGH; service->sendToMesh(p, RX_SRC_LOCAL, true); currentState = KEY_VERIFICATION_SENDER_AWAITING_USER; - memset(message, 0, sizeof(message)); - sprintf(message, "Verification: \n"); - generateVerificationCode(message + 15); // send the toPhone packet - if (screen) { - screen->showOverlayBanner(message, 30000); - } + IF_SCREEN(screen->requestMenu(graphics::menuHandler::key_verification_final_prompt);) meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); cn->level = meshtastic_LogRecord_Level_WARNING; sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message); @@ -275,7 +278,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber) void KeyVerificationModule::updateState() { if (currentState != KEY_VERIFICATION_IDLE) { - // check for the 30 second timeout + // check for the 60 second timeout if (currentNonceTimestamp < getTime() - 60) { resetToIdle(); } else { diff --git a/src/modules/KeyVerificationModule.h b/src/modules/KeyVerificationModule.h index f659e961a..d5dba01d7 100644 --- a/src/modules/KeyVerificationModule.h +++ b/src/modules/KeyVerificationModule.h @@ -27,6 +27,8 @@ class KeyVerificationModule : public ProtobufModule }*/ virtual bool wantUIFrame() { return false; }; bool sendInitialRequest(NodeNum remoteNode); + void generateVerificationCode(char *); // fills char with the user readable verification code + uint32_t getCurrentRemoteNode() { return currentRemoteNode; } protected: /* Called to handle a particular incoming message @@ -56,9 +58,8 @@ class KeyVerificationModule : public ProtobufModule char message[40] = {0}; void processSecurityNumber(uint32_t); - void updateState(); // check the timeouts and maybe reset the state to idle - void resetToIdle(); // Zero out module state - void generateVerificationCode(char *); // fills char with the user readable verification code + void updateState(); // check the timeouts and maybe reset the state to idle + void resetToIdle(); // Zero out module state }; extern KeyVerificationModule *keyVerificationModule; \ No newline at end of file diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 2b3e89fb3..2f3da0fdd 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -53,6 +53,7 @@ #endif #if ARCH_PORTDUINO #include "input/LinuxInputImpl.h" +#include "input/SeesawRotary.h" #include "modules/Telemetry/HostMetrics.h" #if !MESHTASTIC_EXCLUDE_STOREFORWARD #include "modules/StoreForwardModule.h" @@ -163,7 +164,6 @@ void setupModules() // Example: Put your module here // new ReplyModule(); #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER && !MESHTASTIC_EXCLUDE_I2C - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); if (!rotaryEncoderInterruptImpl1->init()) { @@ -189,6 +189,11 @@ void setupModules() #endif // HAS_BUTTON #if ARCH_PORTDUINO if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { + seesawRotary = new SeesawRotary("SeesawRotary"); + if (!seesawRotary->init()) { + delete seesawRotary; + seesawRotary = nullptr; + } aLinuxInputImpl = new LinuxInputImpl(); aLinuxInputImpl->init(); } @@ -213,11 +218,14 @@ void setupModules() #if HAS_TELEMETRY new DeviceTelemetryModule(); #endif +// TODO: How to improve this? #if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR new EnvironmentTelemetryModule(); +#if __has_include("Adafruit_PM25AQI.h") if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { new AirQualityTelemetryModule(); } +#endif #if !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 || nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) { diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index cf9940e25..b6fee7703 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -14,6 +14,11 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes { auto p = *pptr; + if (p.is_licensed != owner.is_licensed) { + LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!"); + return true; + } + // Coerce user.id to be derived from the node number snprintf(p.id, sizeof(p.id), "!%08x", getFrom(&mp)); diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 93c65ecc1..8b6a9f19c 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -266,9 +266,11 @@ meshtastic_MeshPacket *PositionModule::allocPositionPacket() LOG_INFO("Position packet: time=%i lat=%i lon=%i", p.time, p.latitude_i, p.longitude_i); +#ifndef MESHTASTIC_EXCLUDE_ATAK // TAK Tracker devices should send their position in a TAK packet over the ATAK port if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) return allocAtakPli(); +#endif return allocDataProtobuf(p); } diff --git a/src/modules/ReplyModule.cpp b/src/modules/ReplyModule.cpp index 8892aaa97..434441d49 100644 --- a/src/modules/ReplyModule.cpp +++ b/src/modules/ReplyModule.cpp @@ -8,7 +8,7 @@ meshtastic_MeshPacket *ReplyModule::allocReply() { assert(currentRequest); // should always be !NULL -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) auto req = *currentRequest; auto &p = req.decoded; // The incoming message is in p.payload diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index f3921ef19..f3091e5bf 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -74,6 +74,26 @@ static Print *serialPrint = &Serial2; char serialBytes[512]; size_t serialPayloadSize; +bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config) +{ + if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA, + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO)) { + const char *warning = + "Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes."; + LOG_ERROR(warning); +#if !IS_RUNNING_TESTS + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->level = meshtastic_LogRecord_Level_ERROR; + cn->time = getValidTime(RTCQualityFromNet); + snprintf(cn->message, sizeof(cn->message), "%s", warning); + service->sendClientNotification(cn); +#endif + return false; + } + + return true; +} + SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio") { switch (moduleConfig.serial.mode) { diff --git a/src/modules/SerialModule.h b/src/modules/SerialModule.h index fa86db28f..1c74c927c 100644 --- a/src/modules/SerialModule.h +++ b/src/modules/SerialModule.h @@ -20,6 +20,8 @@ class SerialModule : public StreamAPI, private concurrency::OSThread public: SerialModule(); + static bool isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config); + protected: virtual int32_t runOnce() override; diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index 0a6e1b4c4..72ac99118 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -202,6 +202,8 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp) this->packetHistory[this->packetHistoryTotalCount].reply_id = p.reply_id; this->packetHistory[this->packetHistoryTotalCount].emoji = (bool)p.emoji; this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size; + this->packetHistory[this->packetHistoryTotalCount].rx_rssi = mp.rx_rssi; + this->packetHistory[this->packetHistoryTotalCount].rx_snr = mp.rx_snr; memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN); this->packetHistoryTotalCount++; @@ -252,6 +254,8 @@ meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t p->decoded.reply_id = this->packetHistory[i].reply_id; p->rx_time = this->packetHistory[i].time; p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji; + p->rx_rssi = this->packetHistory[i].rx_rssi; + p->rx_snr = this->packetHistory[i].rx_snr; // Let's assume that if the server received the S&F request that the client is in range. // TODO: Make this configurable. @@ -623,4 +627,4 @@ StoreForwardModule::StoreForwardModule() disable(); } #endif -} \ No newline at end of file +} diff --git a/src/modules/StoreForwardModule.h b/src/modules/StoreForwardModule.h index 30db1625c..25836eded 100644 --- a/src/modules/StoreForwardModule.h +++ b/src/modules/StoreForwardModule.h @@ -19,6 +19,8 @@ struct PacketHistoryStruct { bool emoji; uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN]; pb_size_t payload_size; + int32_t rx_rssi; + float rx_snr; }; class StoreForwardModule : private concurrency::OSThread, public ProtobufModule @@ -108,4 +110,4 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule< virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreAndForward *p); }; -extern StoreForwardModule *storeForwardModule; \ No newline at end of file +extern StoreForwardModule *storeForwardModule; diff --git a/src/modules/SystemCommandsModule.cpp b/src/modules/SystemCommandsModule.cpp index 08c87ec64..74b9678f4 100644 --- a/src/modules/SystemCommandsModule.cpp +++ b/src/modules/SystemCommandsModule.cpp @@ -47,7 +47,7 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event) bool isMuted = externalNotificationModule->getMute(); externalNotificationModule->setMute(!isMuted); IF_SCREEN(graphics::isMuted = !isMuted; if (!isMuted) externalNotificationModule->stopNow(); - screen->showOverlayBanner(isMuted ? "Notifications\nEnabled" : "Notifications\nDisabled", 3000);) + screen->showSimpleBanner(isMuted ? "Notifications\nEnabled" : "Notifications\nDisabled", 3000);) } return 0; // Bluetooth @@ -58,24 +58,24 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event) #if defined(ARDUINO_ARCH_NRF52) if (!config.bluetooth.enabled) { disableBluetooth(); - IF_SCREEN(screen->showOverlayBanner("Bluetooth OFF\nRebooting", 3000)); + IF_SCREEN(screen->showSimpleBanner("Bluetooth OFF\nRebooting", 3000)); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 2000; } else { - IF_SCREEN(screen->showOverlayBanner("Bluetooth ON\nRebooting", 3000)); + IF_SCREEN(screen->showSimpleBanner("Bluetooth ON\nRebooting", 3000)); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; } #else if (!config.bluetooth.enabled) { disableBluetooth(); - IF_SCREEN(screen->showOverlayBanner("Bluetooth OFF", 3000)); + IF_SCREEN(screen->showSimpleBanner("Bluetooth OFF", 3000)); } else { - IF_SCREEN(screen->showOverlayBanner("Bluetooth ON\nRebooting", 3000)); + IF_SCREEN(screen->showSimpleBanner("Bluetooth ON\nRebooting", 3000)); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; } #endif return 0; case INPUT_BROKER_MSG_REBOOT: - IF_SCREEN(screen->showOverlayBanner("Rebooting...", 0)); + IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0)); nodeDB->saveToDisk(); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; // runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; @@ -89,10 +89,15 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event) #if !MESHTASTIC_EXCLUDE_GPS if (gps) { LOG_WARN("GPS Toggle2"); + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED && + config.position.fixed_position == false) { + nodeDB->clearLocalPosition(); + nodeDB->saveToDisk(); + } gps->toggleGpsMode(); const char *msg = (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) ? "GPS Enabled" : "GPS Disabled"; - IF_SCREEN(screen->forceDisplay(); screen->showOverlayBanner(msg, 3000);) + IF_SCREEN(screen->forceDisplay(); screen->showSimpleBanner(msg, 3000);) } #endif return true; @@ -100,18 +105,14 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event) case INPUT_BROKER_SEND_PING: service->refreshLocalMeshNode(); if (service->trySendPosition(NODENUM_BROADCAST, true)) { - IF_SCREEN(screen->showOverlayBanner("Position\nSent", 3000)); + IF_SCREEN(screen->showSimpleBanner("Position\nSent", 3000)); } else { - IF_SCREEN(screen->showOverlayBanner("Node Info\nSent", 3000)); + IF_SCREEN(screen->showSimpleBanner("Node Info\nSent", 3000)); } return true; // Power control case INPUT_BROKER_SHUTDOWN: - LOG_ERROR("Shutting Down"); - IF_SCREEN(screen->showOverlayBanner("Shutting Down...")); - nodeDB->saveToDisk(); - shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; - // runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + shutdownAtMsec = millis(); return true; default: diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 2472b95b1..21a563b9d 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -121,7 +121,7 @@ int32_t AirQualityTelemetryModule::runOnce() bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) { if (t->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i", sender, diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 43c2dd84c..08fd09db0 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -49,7 +49,7 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & return false; if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); LOG_INFO("(Received from %s): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f", sender, diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 46a24a816..8926b171c 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -450,7 +450,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt if (isOwnTelemetry && bannerMsg && isCooldownOver) { LOG_INFO("drawFrame: IAQ %d (own) — showing banner: %s", m.iaq, bannerMsg); - screen->showOverlayBanner(bannerMsg, 3000); + screen->showSimpleBanner(bannerMsg, 3000); // Only buzz if IAQ is over 200 if (m.iaq > 200 && moduleConfig.external_notification.enabled && !externalNotificationModule->getMute()) { @@ -502,7 +502,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) { if (t->which_variant == meshtastic_Telemetry_environment_metrics_tag) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); LOG_INFO("(Received from %s): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, " diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp index 3a735b1fa..215e49c7a 100644 --- a/src/modules/Telemetry/HealthTelemetry.cpp +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -149,7 +149,7 @@ void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState * bool HealthTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) { if (t->which_variant == meshtastic_Telemetry_health_metrics_tag) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); LOG_INFO("(Received from %s): temperature=%f, heart_bpm=%d, spO2=%d,", sender, t->variant.health_metrics.temperature, diff --git a/src/modules/Telemetry/HostMetrics.cpp b/src/modules/Telemetry/HostMetrics.cpp index 6a92b15f8..8f10b9228 100644 --- a/src/modules/Telemetry/HostMetrics.cpp +++ b/src/modules/Telemetry/HostMetrics.cpp @@ -27,7 +27,7 @@ bool HostMetricsModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, return false; if (t->which_variant == meshtastic_Telemetry_host_metrics_tag) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); if (t->variant.host_metrics.has_user_string) t->variant.host_metrics.user_string[sizeof(t->variant.host_metrics.user_string) - 1] = '\0'; diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index a92013d01..35409edef 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -168,7 +168,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) { if (t->which_variant == meshtastic_Telemetry_power_metrics_tag) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); LOG_INFO("(Received from %s): ch1_voltage=%.1f, ch1_current=%.1f, ch2_voltage=%.1f, ch2_current=%.1f, " diff --git a/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp b/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp index 6ab96aa57..1a6792d3a 100644 --- a/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp +++ b/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp @@ -1,6 +1,6 @@ #include "MAX17048Sensor.h" -#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_STM32WL) && __has_include() +#if !MESHTASTIC_EXCLUDE_I2C && __has_include() MAX17048Singleton *MAX17048Singleton::GetInstance() { diff --git a/src/modules/Telemetry/Sensor/MAX17048Sensor.h b/src/modules/Telemetry/Sensor/MAX17048Sensor.h index 6f61421dc..d27169406 100644 --- a/src/modules/Telemetry/Sensor/MAX17048Sensor.h +++ b/src/modules/Telemetry/Sensor/MAX17048Sensor.h @@ -5,7 +5,7 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_STM32WL) && __has_include() +#if !MESHTASTIC_EXCLUDE_I2C && __has_include() // Samples to store in a buffer to determine if the battery is charging or discharging #define MAX17048_CHARGING_SAMPLES 3 diff --git a/src/modules/Telemetry/Sensor/nullSensor.cpp b/src/modules/Telemetry/Sensor/nullSensor.cpp index 9522c7fcc..c84b9d27f 100644 --- a/src/modules/Telemetry/Sensor/nullSensor.cpp +++ b/src/modules/Telemetry/Sensor/nullSensor.cpp @@ -20,4 +20,15 @@ bool NullSensor::getMetrics(meshtastic_Telemetry *measurement) { return false; } + +uint16_t NullSensor::getBusVoltageMv() +{ + return 0; +} + +int16_t NullSensor::getCurrentMa() +{ + return 0; +} + #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/nullSensor.h b/src/modules/Telemetry/Sensor/nullSensor.h index 94dbcc7f8..a400acf97 100644 --- a/src/modules/Telemetry/Sensor/nullSensor.h +++ b/src/modules/Telemetry/Sensor/nullSensor.h @@ -4,9 +4,12 @@ #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "TelemetrySensor.h" -class NullSensor : public TelemetrySensor +#include "CurrentSensor.h" +#include "TelemetrySensor.h" +#include "VoltageSensor.h" + +class NullSensor : public TelemetrySensor, VoltageSensor, CurrentSensor { protected: @@ -17,6 +20,9 @@ class NullSensor : public TelemetrySensor virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; int32_t runTrigger() { return 0; } + + virtual uint16_t getBusVoltageMv() override; + virtual int16_t getCurrentMa() override; }; #endif \ No newline at end of file diff --git a/src/modules/TextMessageModule.cpp b/src/modules/TextMessageModule.cpp index f1d01ad16..72df330c5 100644 --- a/src/modules/TextMessageModule.cpp +++ b/src/modules/TextMessageModule.cpp @@ -4,11 +4,12 @@ #include "PowerFSM.h" #include "buzz.h" #include "configuration.h" +#include "graphics/Screen.h" TextMessageModule *textMessageModule; ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) auto &p = mp.decoded; LOG_INFO("Received text msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes); #endif @@ -17,7 +18,10 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp devicestate.rx_text_message = mp; devicestate.has_rx_text_message = true; - powerFSM.trigger(EVENT_RECEIVED_MSG); + // Only trigger screen wake if configuration allows it + if (shouldWakeOnReceivedMessage()) { + powerFSM.trigger(EVENT_RECEIVED_MSG); + } notifyObservers(&mp); return ProcessMessage::CONTINUE; // Let others look at this message also if they want diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index 41cb35649..f4eccd667 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -1,6 +1,13 @@ #include "TraceRouteModule.h" #include "MeshService.h" +#include "graphics/Screen.h" +#include "graphics/ScreenFonts.h" +#include "graphics/SharedUIDisplay.h" +#include "mesh/Router.h" #include "meshUtils.h" +#include + +extern graphics::Screen *screen; TraceRouteModule *traceRouteModule; @@ -27,6 +34,123 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti // Set updated route to the payload of the to be flooded packet p.decoded.payload.size = pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, r); + + if (tracingNode != 0) { + // check isResponseFromTarget + bool isResponseFromTarget = (incoming.request_id != 0 && p.from == tracingNode); + bool isRequestToUs = (incoming.request_id == 0 && p.to == nodeDB->getNodeNum() && tracingNode != 0); + + // Check if this is a trace route response containing our target node + bool containsTargetNode = false; + for (uint8_t i = 0; i < r->route_count; i++) { + if (r->route[i] == tracingNode) { + containsTargetNode = true; + break; + } + } + for (uint8_t i = 0; i < r->route_back_count; i++) { + if (r->route_back[i] == tracingNode) { + containsTargetNode = true; + break; + } + } + + // Check if this response contains a complete route to our target + bool hasCompleteRoute = (r->route_count > 0 && r->route_back_count > 0) || + (containsTargetNode && (r->route_count > 0 || r->route_back_count > 0)); + + LOG_INFO("TracRoute packet analysis: tracingNode=0x%08x, p.from=0x%08x, p.to=0x%08x, request_id=0x%08x", tracingNode, + p.from, p.to, incoming.request_id); + LOG_INFO("TracRoute conditions: isResponseFromTarget=%d, isRequestToUs=%d, containsTargetNode=%d, hasCompleteRoute=%d", + isResponseFromTarget, isRequestToUs, containsTargetNode, hasCompleteRoute); + + if (isResponseFromTarget || isRequestToUs || (containsTargetNode && hasCompleteRoute)) { + LOG_INFO("TracRoute result detected: isResponseFromTarget=%d, isRequestToUs=%d", isResponseFromTarget, isRequestToUs); + + LOG_INFO("SNR arrays - towards_count=%d, back_count=%d", r->snr_towards_count, r->snr_back_count); + for (int i = 0; i < r->snr_towards_count; i++) { + LOG_INFO("SNR towards[%d] = %d (%.1fdB)", i, r->snr_towards[i], (float)r->snr_towards[i] / 4.0f); + } + for (int i = 0; i < r->snr_back_count; i++) { + LOG_INFO("SNR back[%d] = %d (%.1fdB)", i, r->snr_back[i], (float)r->snr_back[i] / 4.0f); + } + + String result = ""; + + // Show request path (from initiator to target) + if (r->route_count > 0) { + result += getNodeName(nodeDB->getNodeNum()); + for (uint8_t i = 0; i < r->route_count; i++) { + result += " > "; + const char *name = getNodeName(r->route[i]); + float snr = + (i < r->snr_towards_count && r->snr_towards[i] != INT8_MIN) ? ((float)r->snr_towards[i] / 4.0f) : 0.0f; + result += name; + if (snr != 0.0f) { + result += "("; + result += String(snr, 1); + result += "dB)"; + } + } + result += " > "; + result += getNodeName(tracingNode); + if (r->snr_towards_count > 0 && r->snr_towards[r->snr_towards_count - 1] != INT8_MIN) { + result += "("; + result += String((float)r->snr_towards[r->snr_towards_count - 1] / 4.0f, 1); + result += "dB)"; + } + result += "\n"; + } else { + // Direct connection (no intermediate hops) + result += getNodeName(nodeDB->getNodeNum()); + result += " > "; + result += getNodeName(tracingNode); + if (r->snr_towards_count > 0 && r->snr_towards[0] != INT8_MIN) { + result += "("; + result += String((float)r->snr_towards[0] / 4.0f, 1); + result += "dB)"; + } + result += "\n"; + } + + // Show response path (from target back to initiator) + if (r->route_back_count > 0) { + result += getNodeName(tracingNode); + for (int8_t i = r->route_back_count - 1; i >= 0; i--) { + result += " > "; + const char *name = getNodeName(r->route_back[i]); + float snr = (i < r->snr_back_count && r->snr_back[i] != INT8_MIN) ? ((float)r->snr_back[i] / 4.0f) : 0.0f; + result += name; + if (snr != 0.0f) { + result += "("; + result += String(snr, 1); + result += "dB)"; + } + } + // add initiator node + result += " > "; + result += getNodeName(nodeDB->getNodeNum()); + if (r->snr_back_count > 0 && r->snr_back[r->snr_back_count - 1] != INT8_MIN) { + result += "("; + result += String((float)r->snr_back[r->snr_back_count - 1] / 4.0f, 1); + result += "dB)"; + } + } else { + // Direct return path (no intermediate hops) + result += getNodeName(tracingNode); + result += " > "; + result += getNodeName(nodeDB->getNodeNum()); + if (r->snr_back_count > 0 && r->snr_back[0] != INT8_MIN) { + result += "("; + result += String((float)r->snr_back[0] / 4.0f, 1); + result += "dB)"; + } + } + + LOG_INFO("Trace route result: %s", result.c_str()); + handleTraceRouteResult(result); + } + } } void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination) @@ -108,7 +232,7 @@ void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, floa void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) std::string route = "Route traced:\n"; route += vformat("0x%x --> ", origin); for (uint8_t i = 0; i < r->route_count; i++) { @@ -173,8 +297,467 @@ meshtastic_MeshPacket *TraceRouteModule::allocReply() } TraceRouteModule::TraceRouteModule() - : ProtobufModule("traceroute", meshtastic_PortNum_TRACEROUTE_APP, &meshtastic_RouteDiscovery_msg) + : ProtobufModule("traceroute", meshtastic_PortNum_TRACEROUTE_APP, &meshtastic_RouteDiscovery_msg), OSThread("TraceRoute") { ourPortNum = meshtastic_PortNum_TRACEROUTE_APP; isPromiscuous = true; // We need to update the route even if it is not destined to us +} + +const char *TraceRouteModule::getNodeName(NodeNum node) +{ + meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node); + if (info && info->has_user) { + if (strlen(info->user.short_name) > 0) { + return info->user.short_name; + } + if (strlen(info->user.long_name) > 0) { + return info->user.long_name; + } + } + + static char fallback[12]; + snprintf(fallback, sizeof(fallback), "0x%08x", node); + return fallback; +} + +bool TraceRouteModule::startTraceRoute(NodeNum node) +{ + LOG_INFO("=== TraceRoute startTraceRoute CALLED: node=0x%08x ===", node); + unsigned long now = millis(); + + if (node == 0 || node == NODENUM_BROADCAST) { + LOG_ERROR("Invalid node number for trace route: 0x%08x", node); + runState = TRACEROUTE_STATE_RESULT; + resultText = "Invalid node"; + resultShowTime = millis(); + tracingNode = 0; + + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + return false; + } + + if (node == nodeDB->getNodeNum()) { + LOG_ERROR("Cannot trace route to self: 0x%08x", node); + runState = TRACEROUTE_STATE_RESULT; + resultText = "Cannot trace self"; + resultShowTime = millis(); + tracingNode = 0; + + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + return false; + } + + if (!initialized) { + lastTraceRouteTime = 0; + initialized = true; + LOG_INFO("TraceRoute initialized for first time"); + } + + if (runState == TRACEROUTE_STATE_TRACKING) { + LOG_INFO("TraceRoute already in progress"); + return false; + } + + if (initialized && lastTraceRouteTime > 0 && now - lastTraceRouteTime < cooldownMs) { + // Cooldown + unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000; + bannerText = String("Wait for ") + String(wait) + String("s"); + runState = TRACEROUTE_STATE_COOLDOWN; + + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + LOG_INFO("Cooldown active, please wait %lu seconds before starting a new trace route.", wait); + return false; + } + + tracingNode = node; + lastTraceRouteTime = now; + runState = TRACEROUTE_STATE_TRACKING; + bannerText = String("Tracing ") + getNodeName(node); + + LOG_INFO("TraceRoute UI: Starting trace route to node 0x%08x, requesting focus", node); + + // 请求焦点,然后触发UI更新事件 + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + + // 设置定时器来处理超时检查 + setIntervalFromNow(1000); // 每秒检查一次状态 + + meshtastic_RouteDiscovery req = meshtastic_RouteDiscovery_init_zero; + LOG_INFO("Creating RouteDiscovery protobuf..."); + + // Allocate a packet directly from router like the reference code + meshtastic_MeshPacket *p = router->allocForSending(); + if (p) { + // Set destination and port + p->to = node; + p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP; + p->decoded.want_response = true; + + // Manually encode the RouteDiscovery payload + p->decoded.payload.size = + pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req); + + LOG_INFO("Packet allocated successfully: to=0x%08x, portnum=%d, want_response=%d, payload_size=%d", p->to, + p->decoded.portnum, p->decoded.want_response, p->decoded.payload.size); + LOG_INFO("About to call service->sendToMesh..."); + + if (service) { + LOG_INFO("MeshService is available, sending packet..."); + service->sendToMesh(p, RX_SRC_USER); + LOG_INFO("sendToMesh called successfully for trace route to node 0x%08x", node); + } else { + LOG_ERROR("MeshService is NULL!"); + runState = TRACEROUTE_STATE_RESULT; + resultText = "Service unavailable"; + resultShowTime = millis(); + tracingNode = 0; + + requestFocus(); + UIFrameEvent e2; + e2.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e2); + return false; + } + } else { + LOG_ERROR("Failed to allocate TraceRoute packet from router"); + runState = TRACEROUTE_STATE_RESULT; + resultText = "Failed to send"; + resultShowTime = millis(); + tracingNode = 0; + + requestFocus(); + UIFrameEvent e2; + e2.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e2); + return false; + } + return true; +} + +void TraceRouteModule::launch(NodeNum node) +{ + if (node == 0 || node == NODENUM_BROADCAST) { + LOG_ERROR("Invalid node number for trace route: 0x%08x", node); + runState = TRACEROUTE_STATE_RESULT; + resultText = "Invalid node"; + resultShowTime = millis(); + tracingNode = 0; + + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + return; + } + + if (node == nodeDB->getNodeNum()) { + LOG_ERROR("Cannot trace route to self: 0x%08x", node); + runState = TRACEROUTE_STATE_RESULT; + resultText = "Cannot trace self"; + resultShowTime = millis(); + tracingNode = 0; + + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + return; + } + + if (!initialized) { + lastTraceRouteTime = 0; + initialized = true; + LOG_INFO("TraceRoute initialized for first time"); + } + + unsigned long now = millis(); + if (initialized && lastTraceRouteTime > 0 && now - lastTraceRouteTime < cooldownMs) { + unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000; + bannerText = String("Wait for ") + String(wait) + String("s"); + runState = TRACEROUTE_STATE_COOLDOWN; + + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + LOG_INFO("Cooldown active, please wait %lu seconds before starting a new trace route.", wait); + return; + } + + runState = TRACEROUTE_STATE_TRACKING; + tracingNode = node; + lastTraceRouteTime = now; + bannerText = String("Tracing ") + getNodeName(node); + + requestFocus(); + + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + + setIntervalFromNow(1000); + + meshtastic_RouteDiscovery req = meshtastic_RouteDiscovery_init_zero; + LOG_INFO("Creating RouteDiscovery protobuf..."); + + meshtastic_MeshPacket *p = router->allocForSending(); + if (p) { + p->to = node; + p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP; + p->decoded.want_response = true; + + p->decoded.payload.size = + pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req); + + LOG_INFO("Packet allocated successfully: to=0x%08x, portnum=%d, want_response=%d, payload_size=%d", p->to, + p->decoded.portnum, p->decoded.want_response, p->decoded.payload.size); + + if (service) { + service->sendToMesh(p, RX_SRC_USER); + LOG_INFO("sendToMesh called successfully for trace route to node 0x%08x", node); + } else { + LOG_ERROR("MeshService is NULL!"); + runState = TRACEROUTE_STATE_RESULT; + resultText = "Service unavailable"; + resultShowTime = millis(); + tracingNode = 0; + } + } else { + LOG_ERROR("Failed to allocate TraceRoute packet from router"); + runState = TRACEROUTE_STATE_RESULT; + resultText = "Failed to send"; + resultShowTime = millis(); + tracingNode = 0; + } +} + +void TraceRouteModule::handleTraceRouteResult(const String &result) +{ + resultText = result; + runState = TRACEROUTE_STATE_RESULT; + resultShowTime = millis(); + tracingNode = 0; + + LOG_INFO("TraceRoute result ready, requesting focus. Result: %s", result.c_str()); + + setIntervalFromNow(1000); + + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + + LOG_INFO("=== TraceRoute handleTraceRouteResult END ==="); +} + +bool TraceRouteModule::shouldDraw() +{ + bool draw = (runState != TRACEROUTE_STATE_IDLE); + static TraceRouteRunState lastLoggedState = TRACEROUTE_STATE_IDLE; + if (runState != lastLoggedState) { + LOG_INFO("TraceRoute shouldDraw: runState=%d, draw=%d", runState, draw); + lastLoggedState = runState; + } + return draw; +} +#if HAS_SCREEN +void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + LOG_DEBUG("TraceRoute drawFrame called: runState=%d", runState); + + display->setTextAlignment(TEXT_ALIGN_CENTER); + + if (runState == TRACEROUTE_STATE_TRACKING) { + display->setFont(FONT_MEDIUM); + int centerY = y + (display->getHeight() / 2) - (FONT_HEIGHT_MEDIUM / 2); + display->drawString(display->getWidth() / 2 + x, centerY, bannerText); + + } else if (runState == TRACEROUTE_STATE_RESULT) { + display->setFont(FONT_MEDIUM); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + display->drawString(x, y, "Route Result"); + + int contentStartY = y + FONT_HEIGHT_MEDIUM + 2; // Add more spacing after title + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + + if (resultText.length() > 0) { + std::vector lines; + String currentLine = ""; + int maxWidth = display->getWidth() - 4; + + int start = 0; + int newlinePos = resultText.indexOf('\n', start); + + while (newlinePos != -1 || start < resultText.length()) { + String segment; + if (newlinePos != -1) { + segment = resultText.substring(start, newlinePos); + start = newlinePos + 1; + newlinePos = resultText.indexOf('\n', start); + } else { + segment = resultText.substring(start); + start = resultText.length(); + } + + if (display->getStringWidth(segment) <= maxWidth) { + lines.push_back(segment); + } else { + // Try to break at better positions (space, >, <, -) + String remaining = segment; + + while (remaining.length() > 0) { + String tempLine = ""; + int lastGoodBreak = -1; + bool lineComplete = false; + + for (int i = 0; i < remaining.length(); i++) { + char ch = remaining.charAt(i); + String testLine = tempLine + ch; + + if (display->getStringWidth(testLine) > maxWidth) { + if (lastGoodBreak >= 0) { + // Break at the last good position + lines.push_back(remaining.substring(0, lastGoodBreak + 1)); + remaining = remaining.substring(lastGoodBreak + 1); + lineComplete = true; + break; + } else if (tempLine.length() > 0) { + lines.push_back(tempLine); + remaining = remaining.substring(i); + lineComplete = true; + break; + } else { + // Single character exceeds width + lines.push_back(String(ch)); + remaining = remaining.substring(i + 1); + lineComplete = true; + break; + } + } else { + tempLine = testLine; + // Mark good break positions + if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')') { + lastGoodBreak = i; + } + } + } + + if (!lineComplete) { + // Reached end of remaining text + if (tempLine.length() > 0) { + lines.push_back(tempLine); + } + break; + } + } + } + } + + int lineHeight = FONT_HEIGHT_SMALL + 1; // Use proper font height with 1px spacing + for (size_t i = 0; i < lines.size(); i++) { + int lineY = contentStartY + (i * lineHeight); + if (lineY + FONT_HEIGHT_SMALL <= display->getHeight()) { + display->drawString(x + 2, lineY, lines[i]); + } + } + } + + } else if (runState == TRACEROUTE_STATE_COOLDOWN) { + display->setFont(FONT_MEDIUM); + int centerY = y + (display->getHeight() / 2) - (FONT_HEIGHT_MEDIUM / 2); + display->drawString(display->getWidth() / 2 + x, centerY, bannerText); + } +} +#endif // HAS_SCREEN +int32_t TraceRouteModule::runOnce() +{ + unsigned long now = millis(); + + if (runState == TRACEROUTE_STATE_IDLE) { + return INT32_MAX; + } + + // Check for tracking timeout + if (runState == TRACEROUTE_STATE_TRACKING && now - lastTraceRouteTime > trackingTimeoutMs) { + LOG_INFO("TraceRoute timeout, no response received"); + runState = TRACEROUTE_STATE_RESULT; + resultText = "No response received"; + resultShowTime = now; + tracingNode = 0; + + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + + setIntervalFromNow(resultDisplayMs); + return resultDisplayMs; + } + + // Update cooldown display every second + if (runState == TRACEROUTE_STATE_COOLDOWN) { + unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000; + if (wait > 0) { + String newBannerText = String("Wait for ") + String(wait) + String("s"); + bannerText = newBannerText; + LOG_INFO("TraceRoute cooldown: updating banner to %s", bannerText.c_str()); + + // Force flash UI + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + + if (screen) { + screen->forceDisplay(); + } + + return 1000; + } else { + // Cooldown finished + LOG_INFO("TraceRoute cooldown finished, returning to IDLE"); + runState = TRACEROUTE_STATE_IDLE; + bannerText = ""; + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + return INT32_MAX; + } + } + + if (runState == TRACEROUTE_STATE_RESULT) { + if (now - resultShowTime >= resultDisplayMs) { + LOG_INFO("TraceRoute result display timeout, returning to IDLE"); + runState = TRACEROUTE_STATE_IDLE; + resultText = ""; + bannerText = ""; + tracingNode = 0; + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + return INT32_MAX; + } else { + return 1000; + } + } + + if (runState == TRACEROUTE_STATE_TRACKING) { + return 1000; + } + + return INT32_MAX; } \ No newline at end of file diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index afe2b3871..51d98826e 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -1,16 +1,40 @@ #pragma once #include "ProtobufModule.h" +#include "concurrency/OSThread.h" +#include "graphics/Screen.h" +#include "graphics/SharedUIDisplay.h" +#include "input/InputBroker.h" +#if HAS_SCREEN +#include "OLEDDisplayUi.h" +#endif #define ROUTE_SIZE sizeof(((meshtastic_RouteDiscovery *)0)->route) / sizeof(((meshtastic_RouteDiscovery *)0)->route[0]) /** * A module that traces the route to a certain destination node */ -class TraceRouteModule : public ProtobufModule +enum TraceRouteRunState { TRACEROUTE_STATE_IDLE, TRACEROUTE_STATE_TRACKING, TRACEROUTE_STATE_RESULT, TRACEROUTE_STATE_COOLDOWN }; + +class TraceRouteModule : public ProtobufModule, + public Observable, + private concurrency::OSThread { public: TraceRouteModule(); + bool startTraceRoute(NodeNum node); + void launch(NodeNum node); + void handleTraceRouteResult(const String &result); + bool shouldDraw(); +#if HAS_SCREEN + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; +#endif + + const char *getNodeName(NodeNum node); + + virtual bool wantUIFrame() override { return shouldDraw(); } + virtual Observable *getUIFrameObservable() override { return this; } + protected: bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r) override; @@ -20,6 +44,8 @@ class TraceRouteModule : public ProtobufModule the route array containing the IDs of nodes this packet went through */ void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) override; + virtual int32_t runOnce() override; + private: // Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination); @@ -31,6 +57,17 @@ class TraceRouteModule : public ProtobufModule Set origin to where the request came from. Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */ void printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination); + + TraceRouteRunState runState = TRACEROUTE_STATE_IDLE; + unsigned long lastTraceRouteTime = 0; + unsigned long resultShowTime = 0; + unsigned long cooldownMs = 30000; + unsigned long resultDisplayMs = 10000; + unsigned long trackingTimeoutMs = 10000; + String bannerText; + String resultText; + NodeNum tracingNode = 0; + bool initialized = false; }; extern TraceRouteModule *traceRouteModule; \ No newline at end of file diff --git a/src/modules/WaypointModule.cpp b/src/modules/WaypointModule.cpp index cab668406..4b05d5fa1 100644 --- a/src/modules/WaypointModule.cpp +++ b/src/modules/WaypointModule.cpp @@ -16,7 +16,7 @@ WaypointModule *waypointModule; ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) auto &p = mp.decoded; LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes); #endif @@ -137,7 +137,7 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, if (ourNode && (nodeDB->hasValidPosition(ourNode) || screen->hasHeading())) { const meshtastic_PositionLite &op = ourNode->position; float myHeading; - if (screen->ignoreCompass) { + if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) { myHeading = 0; } else { if (screen->hasHeading()) @@ -152,7 +152,7 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i)); // If the top of the compass is a static north then bearingToOther can be drawn on the compass directly // If the top of the compass is not a static north we need adjust bearingToOther based on heading - if (!screen->ignoreCompass) + if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) bearingToOther -= myHeading; graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 137c92056..21d4a8fa0 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -39,6 +39,7 @@ #include #define ntohl __ntohl #endif +#include MQTT *mqtt; @@ -559,10 +560,8 @@ void MQTT::sendSubscriptions() int32_t MQTT::runOnce() { -#if HAS_NETWORKING if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) return disable(); - bool wantConnection = wantsLink(); perhapsReportToMap(); @@ -572,7 +571,7 @@ int32_t MQTT::runOnce() publishQueuedMessages(); return 200; } - +#if HAS_NETWORKING else if (!pubSub.loop()) { if (!wantConnection) return 5000; // If we don't want connection now, check again in 5 secs @@ -596,8 +595,10 @@ int32_t MQTT::runOnce() powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) return 20; } -#endif +#else + // No networking available, return default interval return 30000; +#endif } bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTClient *client) @@ -624,18 +625,32 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC return connectPubSub(parsed, *pubSub, (client != nullptr) ? *client : *clientConnection); } #else - LOG_ERROR("Invalid MQTT config: proxy_to_client_enabled must be enabled on nodes that do not have a network"); + const char *warning = "Invalid MQTT config: proxy_to_client_enabled must be enabled on nodes that do not have a network"; + LOG_ERROR(warning); +#if !IS_RUNNING_TESTS + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->level = meshtastic_LogRecord_Level_ERROR; + cn->time = getValidTime(RTCQualityFromNet); + strncpy(cn->message, warning, sizeof(cn->message) - 1); + cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination + service->sendClientNotification(cn); +#endif return false; #endif } const bool defaultServer = isDefaultServer(parsed.serverAddr); - if (defaultServer && config.tls_enabled) { - LOG_ERROR("Invalid MQTT config: TLS was enabled, but the default server does not support TLS"); - return false; - } if (defaultServer && parsed.serverPort != PubSubConfig::defaultPort) { - LOG_ERROR("Invalid MQTT config: Unsupported port '%d' for the default MQTT server", parsed.serverPort); + const char *warning = "Invalid MQTT config: default server address must not have a port specified"; + LOG_ERROR(warning); +#if !IS_RUNNING_TESTS + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->level = meshtastic_LogRecord_Level_ERROR; + cn->time = getValidTime(RTCQualityFromNet); + strncpy(cn->message, warning, sizeof(cn->message) - 1); + cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination + service->sendClientNotification(cn); +#endif return false; } return true; diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 3ab06695b..834184292 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -9,6 +9,7 @@ #include "mesh/mesh-pb-constants.h" #include "sleep.h" #include +#include NimBLECharacteristic *fromNumCharacteristic; NimBLECharacteristic *BatteryCharacteristic; @@ -17,8 +18,37 @@ NimBLEServer *bleServer; static bool passkeyShowing; -class BluetoothPhoneAPI : public PhoneAPI +class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread { + public: + BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") { nimble_queue.resize(3); } + std::vector nimble_queue; + std::mutex nimble_mutex; + uint8_t queue_size = 0; + bool has_fromRadio = false; + uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; + size_t numBytes = 0; + bool hasChecked = false; + bool phoneWants = false; + + protected: + virtual int32_t runOnce() override + { + std::lock_guard guard(nimble_mutex); + if (queue_size > 0) { + for (uint8_t i = 0; i < queue_size; i++) { + handleToRadio(nimble_queue.at(i).data(), nimble_queue.at(i).length()); + } + LOG_DEBUG("Queue_size %u", queue_size); + queue_size = 0; + } + if (hasChecked == false && phoneWants == true) { + numBytes = getFromRadio(fromRadioBytes); + hasChecked = true; + } + + return 100; + } /** * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) */ @@ -51,15 +81,16 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks { virtual void onWrite(NimBLECharacteristic *pCharacteristic) { - LOG_DEBUG("To Radio onwrite"); auto val = pCharacteristic->getValue(); if (memcmp(lastToRadio, val.data(), val.length()) != 0) { - LOG_DEBUG("New ToRadio packet"); - memcpy(lastToRadio, val.data(), val.length()); - bluetoothPhoneAPI->handleToRadio(val.data(), val.length()); - } else { - LOG_DEBUG("Drop dup ToRadio packet we just saw"); + if (bluetoothPhoneAPI->queue_size < 3) { + memcpy(lastToRadio, val.data(), val.length()); + std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); + bluetoothPhoneAPI->nimble_queue.at(bluetoothPhoneAPI->queue_size) = val; + bluetoothPhoneAPI->queue_size++; + bluetoothPhoneAPI->setIntervalFromNow(0); + } } } }; @@ -68,12 +99,23 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks { virtual void onRead(NimBLECharacteristic *pCharacteristic) { - uint8_t fromRadioBytes[meshtastic_FromRadio_size]; - size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); - - std::string fromRadioByteString(fromRadioBytes, fromRadioBytes + numBytes); - + int tries = 0; + bluetoothPhoneAPI->phoneWants = true; + while (!bluetoothPhoneAPI->hasChecked && tries < 100) { + bluetoothPhoneAPI->setIntervalFromNow(0); + delay(20); + tries++; + } + std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); + std::string fromRadioByteString(bluetoothPhoneAPI->fromRadioBytes, + bluetoothPhoneAPI->fromRadioBytes + bluetoothPhoneAPI->numBytes); pCharacteristic->setValue(fromRadioByteString); + + if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload + bluetoothPhoneAPI->setIntervalFromNow(0); + bluetoothPhoneAPI->numBytes = 0; + bluetoothPhoneAPI->hasChecked = false; + bluetoothPhoneAPI->phoneWants = false; } }; @@ -149,7 +191,12 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED)); if (bluetoothPhoneAPI) { + std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); bluetoothPhoneAPI->close(); + bluetoothPhoneAPI->hasChecked = false; + bluetoothPhoneAPI->phoneWants = false; + bluetoothPhoneAPI->numBytes = 0; + bluetoothPhoneAPI->queue_size = 0; } } }; diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 3763bce1e..3168d9121 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -184,8 +184,12 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_SENSOR_HUB #elif defined(ELECROW_PANEL) #define HW_VENDOR meshtastic_HardwareModel_CROWPANEL +#elif defined(RAK3312) +#define HW_VENDOR meshtastic_HardwareModel_RAK3312 #elif defined(LINK_32) #define HW_VENDOR meshtastic_HardwareModel_LINK_32 +#elif defined(T_DECK_PRO) +#define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO #endif // ----------------------------------------------------------------------------- diff --git a/src/platform/extra_variants/t_deck_pro/variant.cpp b/src/platform/extra_variants/t_deck_pro/variant.cpp new file mode 100644 index 000000000..eae9335ce --- /dev/null +++ b/src/platform/extra_variants/t_deck_pro/variant.cpp @@ -0,0 +1,28 @@ +#include "configuration.h" + +#ifdef T_DECK_PRO + +#include "input/TouchScreenImpl1.h" +#include +#include + +CSE_CST328 tsPanel = CSE_CST328(EINK_WIDTH, EINK_HEIGHT, &Wire, CST328_PIN_RST, CST328_PIN_INT); + +bool readTouch(int16_t *x, int16_t *y) +{ + if (tsPanel.getTouches()) { + *x = tsPanel.getPoint(0).x; + *y = tsPanel.getPoint(0).y; + return true; + } + return false; +} + +// T-Deck Pro specific init +void lateInitVariant() +{ + tsPanel.begin(); + touchScreenImpl1 = new TouchScreenImpl1(EINK_WIDTH, EINK_HEIGHT, readTouch); + touchScreenImpl1->init(); +} +#endif \ No newline at end of file diff --git a/src/platform/nrf52/AsyncUDP.cpp b/src/platform/nrf52/AsyncUDP.cpp new file mode 100644 index 000000000..836fb1307 --- /dev/null +++ b/src/platform/nrf52/AsyncUDP.cpp @@ -0,0 +1,73 @@ +#include "AsyncUDP.h" + +#if HAS_ETHERNET + +AsyncUDP::AsyncUDP() : OSThread("AsyncUDP"), localPort(0) {} + +bool AsyncUDP::listenMulticast(IPAddress multicastIP, uint16_t port, uint8_t ttl) +{ + if (!isMulticast(multicastIP)) + return false; + localPort = port; + udp.beginMulticast(multicastIP, port); + return true; +} + +size_t AsyncUDP::write(uint8_t b) +{ + return udp.write(&b, 1); +} + +size_t AsyncUDP::write(const uint8_t *data, size_t len) +{ + return udp.write(data, len); +} + +void AsyncUDP::onPacket(const std::function &callback) +{ + _onPacket = callback; +} + +bool AsyncUDP::writeTo(const uint8_t *data, size_t len, IPAddress ip, uint16_t port) +{ + if (!udp.beginPacket(ip, port)) + return false; + udp.write(data, len); + return udp.endPacket(); +} + +// AsyncUDPPacket +AsyncUDPPacket::AsyncUDPPacket(EthernetUDP &source) : _udp(source), _remoteIP(source.remoteIP()), _remotePort(source.remotePort()) +{ + if (_udp.available() > 0) { + _readLength = _udp.read(_buffer, sizeof(_buffer)); + } else { + _readLength = 0; + } +} + +IPAddress AsyncUDPPacket::remoteIP() +{ + return _remoteIP; +} + +uint16_t AsyncUDPPacket::length() +{ + return _readLength; +} + +const uint8_t *AsyncUDPPacket::data() +{ + return _buffer; +} + +int32_t AsyncUDP::runOnce() +{ + if (_onPacket && udp.parsePacket() > 0) { + AsyncUDPPacket packet(udp); + _onPacket(packet); + } + return 5; // check every 5ms +} + +#endif // HAS_ETHERNET \ No newline at end of file diff --git a/src/platform/nrf52/AsyncUDP.h b/src/platform/nrf52/AsyncUDP.h new file mode 100644 index 000000000..e2b406ba9 --- /dev/null +++ b/src/platform/nrf52/AsyncUDP.h @@ -0,0 +1,63 @@ +#ifndef ASYNC_UDP_H +#define ASYNC_UDP_H + +#include "configuration.h" + +#if HAS_ETHERNET + +#include "concurrency/OSThread.h" +#include +#include +#include +#include +#include + +class AsyncUDPPacket; + +class AsyncUDP : public Print, private concurrency::OSThread +{ + public: + AsyncUDP(); + explicit operator bool() const { return localPort != 0; } + + bool listenMulticast(IPAddress multicastIP, uint16_t port, uint8_t ttl = 64); + bool writeTo(const uint8_t *data, size_t len, IPAddress ip, uint16_t port); + + size_t write(uint8_t b) override; + size_t write(const uint8_t *data, size_t len) override; + void onPacket(const std::function &callback); + + private: + EthernetUDP udp; + uint16_t localPort; + std::function _onPacket; + virtual int32_t runOnce() override; +}; + +class AsyncUDPPacket +{ + public: + AsyncUDPPacket(EthernetUDP &source); + + IPAddress remoteIP(); + uint16_t length(); + const uint8_t *data(); + + private: + EthernetUDP &_udp; + IPAddress _remoteIP; + uint16_t _remotePort; + size_t _readLength = 0; + + static constexpr size_t BUF_SIZE = 512; + uint8_t _buffer[BUF_SIZE]; +}; + +inline bool isMulticast(const IPAddress &ip) +{ + return (ip[0] & 0xF0) == 0xE0; +} + +#endif // HAS_ETHERNET + +#endif // ASYNC_UDP_H diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 684d20e84..1bbdd77e0 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -49,6 +49,8 @@ #define HW_VENDOR meshtastic_HardwareModel_RAK2560 #elif defined(WISMESH_TAP) #define HW_VENDOR meshtastic_HardwareModel_WISMESH_TAP +#elif defined(WISMESH_TAG) +#define HW_VENDOR meshtastic_HardwareModel_WISMESH_TAG #elif defined(GAT562_MESH_TRIAL_TRACKER) #define HW_VENDOR meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER #elif defined(RAK4630) @@ -89,6 +91,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_POCKET #elif defined(NOMADSTAR_METEOR_PRO) #define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO +#elif defined(SEEED_WIO_TRACKER_L1_EINK) +#define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK #elif defined(SEEED_WIO_TRACKER_L1) #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 #else diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index f582a116d..5f99ec2c3 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -34,6 +34,9 @@ Ch341Hal *ch341Hal = nullptr; char *configPath = nullptr; char *optionMac = nullptr; bool forceSimulated = false; +bool verboseEnabled = false; + +const char *argp_program_version = optstr(APP_VERSION); // FIXME - move setBluetoothEnable into a HALPlatform class void setBluetoothEnable(bool enable) @@ -68,7 +71,9 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) case 'h': optionMac = arg; break; - + case 'v': + verboseEnabled = true; + break; case ARGP_KEY_ARG: return 0; default: @@ -83,6 +88,7 @@ void portduinoCustomInit() {"config", 'c', "CONFIG_PATH", 0, "Full path of the .yaml config file to use."}, {"hwid", 'h', "HWID", 0, "The mac address to assign to this virtual machine"}, {"sim", 's', 0, 0, "Run in Simulated radio mode"}, + {"verbose", 'v', 0, 0, "Set log level to full debug"}, {0}}; static void *childArguments; static char doc[] = "Meshtastic native build."; @@ -415,6 +421,9 @@ void portduinoSetup() exit(EXIT_FAILURE); } } + if (verboseEnabled && settingsMap[logoutputlevel] != level_trace) { + settingsMap[logoutputlevel] = level_debug; + } return; } @@ -642,6 +651,11 @@ bool loadConfig(const char *configPath) settingsMap[tbLeftPin] = yamlConfig["Input"]["TrackballLeft"].as(RADIOLIB_NC); settingsMap[tbRightPin] = yamlConfig["Input"]["TrackballRight"].as(RADIOLIB_NC); settingsMap[tbPressPin] = yamlConfig["Input"]["TrackballPress"].as(RADIOLIB_NC); + if (yamlConfig["Input"]["TrackballDirection"].as("RISING") == "RISING") { + settingsMap[tbDirection] = 4; + } else if (yamlConfig["Input"]["TrackballDirection"].as("RISING") == "FALLING") { + settingsMap[tbDirection] = 3; + } } if (yamlConfig["Webserver"]) { @@ -660,6 +674,21 @@ bool loadConfig(const char *configPath) settingsStrings[hostMetrics_user_command] = (yamlConfig["HostMetrics"]["UserStringCommand"]).as(""); } + if (yamlConfig["Config"]) { + if (yamlConfig["Config"]["DisplayMode"]) { + settingsMap[has_configDisplayMode] = true; + if ((yamlConfig["Config"]["DisplayMode"]).as("") == "TWOCOLOR") { + settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR; + } else if ((yamlConfig["Config"]["DisplayMode"]).as("") == "INVERTED") { + settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED; + } else if ((yamlConfig["Config"]["DisplayMode"]).as("") == "COLOR") { + settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_COLOR; + } else { + settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; + } + } + } + if (yamlConfig["General"]) { settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as(200); settingsMap[maxtophone] = (yamlConfig["General"]["MaxMessageQueue"]).as(100); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 5795f0d8d..288870eef 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -63,6 +63,7 @@ enum configNames { tbLeftPin, tbRightPin, tbPressPin, + tbDirection, spidev, spiSpeed, i2cdev, @@ -108,7 +109,9 @@ enum configNames { mac_address, hostMetrics_interval, hostMetrics_channel, - hostMetrics_user_command + hostMetrics_user_command, + configDisplayMode, + has_configDisplayMode }; enum { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d }; enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 }; diff --git a/src/platform/stm32wl/architecture.h b/src/platform/stm32wl/architecture.h index 325a192a4..e131a0a32 100644 --- a/src/platform/stm32wl/architecture.h +++ b/src/platform/stm32wl/architecture.h @@ -12,6 +12,9 @@ #ifndef HAS_TELEMETRY #define HAS_TELEMETRY 1 #endif +#ifndef HAS_WIRE +#define HAS_WIRE 1 +#endif // // set HW_VENDOR @@ -28,4 +31,9 @@ #define SX126X_CS 1000 #define SX126X_DIO1 1001 #define SX126X_RESET 1003 -#define SX126X_BUSY 1004 \ No newline at end of file +#define SX126X_BUSY 1004 + +#if !defined(DEBUG_MUTE) && !defined(PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF) +#error \ + "You MUST enable PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF if debug prints are enabled. printf will print uninitialized garbage instead of floats." +#endif \ No newline at end of file diff --git a/src/power.h b/src/power.h index 70f075b7c..1c078c06d 100644 --- a/src/power.h +++ b/src/power.h @@ -81,7 +81,7 @@ extern NullSensor ina3221Sensor; #endif -#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_STM32WL) +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #if __has_include() #include "modules/Telemetry/Sensor/MAX17048Sensor.h" extern MAX17048Sensor max17048Sensor; @@ -110,7 +110,7 @@ class Power : private concurrency::OSThread Power(); - void shutdown(); + void powerCommandsCheck(); void readPowerStatus(); virtual bool setup(); virtual int32_t runOnce() override; @@ -126,8 +126,12 @@ class Power : private concurrency::OSThread bool analogInit(); /// Setup a Lipo battery level sensor bool lipoInit(); + /// Setup a Lipo charger + bool lipoChargerInit(); private: + void shutdown(); + void reboot(); // open circuit voltage lookup table uint8_t low_voltage_counter; #ifdef DEBUG_HEAP diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index 29a9b6840..5a1f8ed7e 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -100,6 +100,9 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.environment_metrics.has_iaq) { msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq); } + if (decoded->variant.environment_metrics.has_distance) { + msgPayload["distance"] = new JSONValue(decoded->variant.environment_metrics.distance); + } if (decoded->variant.environment_metrics.has_wind_speed) { msgPayload["wind_speed"] = new JSONValue(decoded->variant.environment_metrics.wind_speed); } @@ -115,6 +118,27 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.environment_metrics.has_radiation) { msgPayload["radiation"] = new JSONValue(decoded->variant.environment_metrics.radiation); } + if (decoded->variant.environment_metrics.has_ir_lux) { + msgPayload["ir_lux"] = new JSONValue(decoded->variant.environment_metrics.ir_lux); + } + if (decoded->variant.environment_metrics.has_uv_lux) { + msgPayload["uv_lux"] = new JSONValue(decoded->variant.environment_metrics.uv_lux); + } + if (decoded->variant.environment_metrics.has_weight) { + msgPayload["weight"] = new JSONValue(decoded->variant.environment_metrics.weight); + } + if (decoded->variant.environment_metrics.has_rainfall_1h) { + msgPayload["rainfall_1h"] = new JSONValue(decoded->variant.environment_metrics.rainfall_1h); + } + if (decoded->variant.environment_metrics.has_rainfall_24h) { + msgPayload["rainfall_24h"] = new JSONValue(decoded->variant.environment_metrics.rainfall_24h); + } + if (decoded->variant.environment_metrics.has_soil_moisture) { + msgPayload["soil_moisture"] = new JSONValue((uint)decoded->variant.environment_metrics.soil_moisture); + } + if (decoded->variant.environment_metrics.has_soil_temperature) { + msgPayload["soil_temperature"] = new JSONValue(decoded->variant.environment_metrics.soil_temperature); + } } else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) { if (decoded->variant.air_quality_metrics.has_pm10_standard) { msgPayload["pm10"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_standard); diff --git a/src/shutdown.h b/src/shutdown.h deleted file mode 100644 index 998944677..000000000 --- a/src/shutdown.h +++ /dev/null @@ -1,60 +0,0 @@ -#include "buzz.h" -#include "configuration.h" -#include "graphics/Screen.h" -#include "main.h" -#include "power.h" -#include "sleep.h" -#if defined(ARCH_PORTDUINO) -#include "api/WiFiServerAPI.h" -#include "input/LinuxInputImpl.h" - -#endif - -void powerCommandsCheck() -{ - if (rebootAtMsec && millis() > rebootAtMsec) { - LOG_INFO("Rebooting"); - notifyReboot.notifyObservers(NULL); -#if defined(ARCH_ESP32) - ESP.restart(); -#elif defined(ARCH_NRF52) - NVIC_SystemReset(); -#elif defined(ARCH_RP2040) - rp2040.reboot(); -#elif defined(ARCH_PORTDUINO) - deInitApiServer(); - if (aLinuxInputImpl) - aLinuxInputImpl->deInit(); - SPI.end(); - Wire.end(); - Serial1.end(); - if (screen) - delete screen; - LOG_DEBUG("final reboot!"); - reboot(); -#elif defined(ARCH_STM32WL) - HAL_NVIC_SystemReset(); -#else - rebootAtMsec = -1; - LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied"); -#endif - } - -#if defined(ARCH_ESP32) || defined(ARCH_NRF52) - if (shutdownAtMsec && screen) { - screen->showOverlayBanner("Shutting Down...", 0); // stays on screen - } -#endif - - if (shutdownAtMsec && millis() > shutdownAtMsec) { - LOG_INFO("Shut down from admin command"); -#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040) - playShutdownMelody(); - power->shutdown(); -#elif defined(ARCH_PORTDUINO) - exit(EXIT_SUCCESS); -#else - LOG_WARN("FIXME implement shutdown for this platform"); -#endif - } -} \ No newline at end of file diff --git a/src/sleep.cpp b/src/sleep.cpp index 09484f46e..1a5f246c5 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -131,7 +131,7 @@ void initDeepSleep() support busted boards, assume button one was pressed wakeButtons = ((uint64_t)1) << buttons.gpios[0]; */ -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) // If we booted because our timer ran out or the user pressed reset, send those as fake events RESET_REASON hwReason = rtc_get_reset_reason(0); diff --git a/test/test_meshpacket_serializer/ports/test_encrypted.cpp b/test/test_meshpacket_serializer/ports/test_encrypted.cpp new file mode 100644 index 000000000..557ee7a49 --- /dev/null +++ b/test/test_meshpacket_serializer/ports/test_encrypted.cpp @@ -0,0 +1,50 @@ +#include "../test_helpers.h" + +// Test encrypted packet serialization +void test_encrypted_packet_serialization() +{ + meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero; + packet.from = 0x11223344; + packet.to = 0x55667788; + packet.id = 0x9999; + packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag; + + // Add some dummy encrypted data + const char *encrypted_data = "encrypted_payload_data"; + packet.encrypted.size = strlen(encrypted_data); + memcpy(packet.encrypted.bytes, encrypted_data, packet.encrypted.size); + + std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check basic packet fields + TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end()); + TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber()); + + TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end()); + TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber()); + + TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end()); + TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber()); + + // Check that it has encrypted data fields (not "payload" but "bytes" and "size") + TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["bytes"]->IsString()); + + TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end()); + TEST_ASSERT_EQUAL(22, (int)jsonObj["size"]->AsNumber()); // strlen("encrypted_payload_data") = 22 + + // The encrypted data should be hex-encoded + std::string encrypted_hex = jsonObj["bytes"]->AsString(); + TEST_ASSERT_TRUE(encrypted_hex.length() > 0); + // Should be twice the size of the original data (hex encoding) + TEST_ASSERT_EQUAL(44, encrypted_hex.length()); // 22 * 2 = 44 + + delete root; +} diff --git a/test/test_meshpacket_serializer/ports/test_nodeinfo.cpp b/test/test_meshpacket_serializer/ports/test_nodeinfo.cpp new file mode 100644 index 000000000..febda9950 --- /dev/null +++ b/test/test_meshpacket_serializer/ports/test_nodeinfo.cpp @@ -0,0 +1,51 @@ +#include "../test_helpers.h" + +static size_t encode_user_info(uint8_t *buffer, size_t buffer_size) +{ + meshtastic_User user = meshtastic_User_init_zero; + strcpy(user.short_name, "TEST"); + strcpy(user.long_name, "Test User"); + strcpy(user.id, "!12345678"); + user.hw_model = meshtastic_HardwareModel_HELTEC_V3; + + pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size); + pb_encode(&stream, &meshtastic_User_msg, &user); + return stream.bytes_written; +} + +// Test NODEINFO_APP port +void test_nodeinfo_serialization() +{ + uint8_t buffer[256]; + size_t payload_size = encode_user_info(buffer, sizeof(buffer)); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_NODEINFO_APP, buffer, payload_size); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check message type + TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end()); + TEST_ASSERT_EQUAL_STRING("nodeinfo", jsonObj["type"]->AsString().c_str()); + + // Check payload + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + + // Verify user data + TEST_ASSERT_TRUE(payload.find("shortname") != payload.end()); + TEST_ASSERT_EQUAL_STRING("TEST", payload["shortname"]->AsString().c_str()); + + TEST_ASSERT_TRUE(payload.find("longname") != payload.end()); + TEST_ASSERT_EQUAL_STRING("Test User", payload["longname"]->AsString().c_str()); + + delete root; +} diff --git a/test/test_meshpacket_serializer/ports/test_position.cpp b/test/test_meshpacket_serializer/ports/test_position.cpp new file mode 100644 index 000000000..f0dcc0709 --- /dev/null +++ b/test/test_meshpacket_serializer/ports/test_position.cpp @@ -0,0 +1,57 @@ +#include "../test_helpers.h" + +static size_t encode_position(uint8_t *buffer, size_t buffer_size) +{ + meshtastic_Position position = meshtastic_Position_init_zero; + position.latitude_i = 374208000; // 37.4208 degrees * 1e7 + position.longitude_i = -1221981000; // -122.1981 degrees * 1e7 + position.altitude = 123; + position.time = 1609459200; + position.has_altitude = true; + position.has_latitude_i = true; + position.has_longitude_i = true; + + pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size); + pb_encode(&stream, &meshtastic_Position_msg, &position); + return stream.bytes_written; +} + +// Test POSITION_APP port +void test_position_serialization() +{ + uint8_t buffer[256]; + size_t payload_size = encode_position(buffer, sizeof(buffer)); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_POSITION_APP, buffer, payload_size); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check message type + TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end()); + TEST_ASSERT_EQUAL_STRING("position", jsonObj["type"]->AsString().c_str()); + + // Check payload + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + + // Verify position data + TEST_ASSERT_TRUE(payload.find("latitude_i") != payload.end()); + TEST_ASSERT_EQUAL(374208000, (int)payload["latitude_i"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("longitude_i") != payload.end()); + TEST_ASSERT_EQUAL(-1221981000, (int)payload["longitude_i"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("altitude") != payload.end()); + TEST_ASSERT_EQUAL(123, (int)payload["altitude"]->AsNumber()); + + delete root; +} diff --git a/test/test_meshpacket_serializer/ports/test_telemetry.cpp b/test/test_meshpacket_serializer/ports/test_telemetry.cpp new file mode 100644 index 000000000..a813aaab5 --- /dev/null +++ b/test/test_meshpacket_serializer/ports/test_telemetry.cpp @@ -0,0 +1,528 @@ +#include "../test_helpers.h" + +// Helper function to create and encode device metrics +static size_t encode_telemetry_device_metrics(uint8_t *buffer, size_t buffer_size) +{ + meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero; + telemetry.time = 1609459200; + telemetry.which_variant = meshtastic_Telemetry_device_metrics_tag; + telemetry.variant.device_metrics.battery_level = 85; + telemetry.variant.device_metrics.has_battery_level = true; + telemetry.variant.device_metrics.voltage = 3.72f; + telemetry.variant.device_metrics.has_voltage = true; + telemetry.variant.device_metrics.channel_utilization = 15.56f; + telemetry.variant.device_metrics.has_channel_utilization = true; + telemetry.variant.device_metrics.air_util_tx = 8.23f; + telemetry.variant.device_metrics.has_air_util_tx = true; + telemetry.variant.device_metrics.uptime_seconds = 12345; + telemetry.variant.device_metrics.has_uptime_seconds = true; + + pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size); + pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry); + return stream.bytes_written; +} + +// Helper function to create and encode empty environment metrics (no fields set) +static size_t encode_telemetry_environment_metrics_empty(uint8_t *buffer, size_t buffer_size) +{ + meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero; + telemetry.time = 1609459200; + telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag; + + // NO fields are set - all has_* flags remain false + // This tests that empty environment metrics don't produce any JSON fields + + pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size); + pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry); + return stream.bytes_written; +} + +// Helper function to create environment metrics with ALL possible fields set +// This function should be updated whenever new fields are added to the protobuf +static size_t encode_telemetry_environment_metrics_all_fields(uint8_t *buffer, size_t buffer_size) +{ + meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero; + telemetry.time = 1609459200; + telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag; + + // Basic environment metrics + telemetry.variant.environment_metrics.temperature = 23.56f; + telemetry.variant.environment_metrics.has_temperature = true; + telemetry.variant.environment_metrics.relative_humidity = 65.43f; + telemetry.variant.environment_metrics.has_relative_humidity = true; + telemetry.variant.environment_metrics.barometric_pressure = 1013.27f; + telemetry.variant.environment_metrics.has_barometric_pressure = true; + + // Gas and air quality + telemetry.variant.environment_metrics.gas_resistance = 50.58f; + telemetry.variant.environment_metrics.has_gas_resistance = true; + telemetry.variant.environment_metrics.iaq = 120; + telemetry.variant.environment_metrics.has_iaq = true; + + // Power measurements + telemetry.variant.environment_metrics.voltage = 3.34f; + telemetry.variant.environment_metrics.has_voltage = true; + telemetry.variant.environment_metrics.current = 0.53f; + telemetry.variant.environment_metrics.has_current = true; + + // Light measurements (ALL 4 types) + telemetry.variant.environment_metrics.lux = 450.12f; + telemetry.variant.environment_metrics.has_lux = true; + telemetry.variant.environment_metrics.white_lux = 380.95f; + telemetry.variant.environment_metrics.has_white_lux = true; + telemetry.variant.environment_metrics.ir_lux = 25.37f; + telemetry.variant.environment_metrics.has_ir_lux = true; + telemetry.variant.environment_metrics.uv_lux = 15.68f; + telemetry.variant.environment_metrics.has_uv_lux = true; + + // Distance measurement + telemetry.variant.environment_metrics.distance = 150.29f; + telemetry.variant.environment_metrics.has_distance = true; + + // Wind measurements (ALL 4 types) + telemetry.variant.environment_metrics.wind_direction = 180; + telemetry.variant.environment_metrics.has_wind_direction = true; + telemetry.variant.environment_metrics.wind_speed = 5.52f; + telemetry.variant.environment_metrics.has_wind_speed = true; + telemetry.variant.environment_metrics.wind_gust = 8.24f; + telemetry.variant.environment_metrics.has_wind_gust = true; + telemetry.variant.environment_metrics.wind_lull = 2.13f; + telemetry.variant.environment_metrics.has_wind_lull = true; + + // Weight measurement + telemetry.variant.environment_metrics.weight = 75.56f; + telemetry.variant.environment_metrics.has_weight = true; + + // Radiation measurement + telemetry.variant.environment_metrics.radiation = 0.13f; + telemetry.variant.environment_metrics.has_radiation = true; + + // Rainfall measurements (BOTH types) + telemetry.variant.environment_metrics.rainfall_1h = 2.57f; + telemetry.variant.environment_metrics.has_rainfall_1h = true; + telemetry.variant.environment_metrics.rainfall_24h = 15.89f; + telemetry.variant.environment_metrics.has_rainfall_24h = true; + + // Soil measurements (BOTH types) + telemetry.variant.environment_metrics.soil_moisture = 85; + telemetry.variant.environment_metrics.has_soil_moisture = true; + telemetry.variant.environment_metrics.soil_temperature = 18.54f; + telemetry.variant.environment_metrics.has_soil_temperature = true; + + // IMPORTANT: When new environment fields are added to the protobuf, + // they MUST be added here too, or the coverage test will fail! + + pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size); + pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry); + return stream.bytes_written; +} + +// Helper function to create and encode environment metrics with all current fields +static size_t encode_telemetry_environment_metrics(uint8_t *buffer, size_t buffer_size) +{ + meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero; + telemetry.time = 1609459200; + telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag; + + // Basic environment metrics + telemetry.variant.environment_metrics.temperature = 23.56f; + telemetry.variant.environment_metrics.has_temperature = true; + telemetry.variant.environment_metrics.relative_humidity = 65.43f; + telemetry.variant.environment_metrics.has_relative_humidity = true; + telemetry.variant.environment_metrics.barometric_pressure = 1013.27f; + telemetry.variant.environment_metrics.has_barometric_pressure = true; + + // Gas and air quality + telemetry.variant.environment_metrics.gas_resistance = 50.58f; + telemetry.variant.environment_metrics.has_gas_resistance = true; + telemetry.variant.environment_metrics.iaq = 120; + telemetry.variant.environment_metrics.has_iaq = true; + + // Power measurements + telemetry.variant.environment_metrics.voltage = 3.34f; + telemetry.variant.environment_metrics.has_voltage = true; + telemetry.variant.environment_metrics.current = 0.53f; + telemetry.variant.environment_metrics.has_current = true; + + // Light measurements + telemetry.variant.environment_metrics.lux = 450.12f; + telemetry.variant.environment_metrics.has_lux = true; + telemetry.variant.environment_metrics.white_lux = 380.95f; + telemetry.variant.environment_metrics.has_white_lux = true; + telemetry.variant.environment_metrics.ir_lux = 25.37f; + telemetry.variant.environment_metrics.has_ir_lux = true; + telemetry.variant.environment_metrics.uv_lux = 15.68f; + telemetry.variant.environment_metrics.has_uv_lux = true; + + // Distance measurement + telemetry.variant.environment_metrics.distance = 150.29f; + telemetry.variant.environment_metrics.has_distance = true; + + // Wind measurements + telemetry.variant.environment_metrics.wind_direction = 180; + telemetry.variant.environment_metrics.has_wind_direction = true; + telemetry.variant.environment_metrics.wind_speed = 5.52f; + telemetry.variant.environment_metrics.has_wind_speed = true; + telemetry.variant.environment_metrics.wind_gust = 8.24f; + telemetry.variant.environment_metrics.has_wind_gust = true; + telemetry.variant.environment_metrics.wind_lull = 2.13f; + telemetry.variant.environment_metrics.has_wind_lull = true; + + // Weight measurement + telemetry.variant.environment_metrics.weight = 75.56f; + telemetry.variant.environment_metrics.has_weight = true; + + // Radiation measurement + telemetry.variant.environment_metrics.radiation = 0.13f; + telemetry.variant.environment_metrics.has_radiation = true; + + // Rainfall measurements + telemetry.variant.environment_metrics.rainfall_1h = 2.57f; + telemetry.variant.environment_metrics.has_rainfall_1h = true; + telemetry.variant.environment_metrics.rainfall_24h = 15.89f; + telemetry.variant.environment_metrics.has_rainfall_24h = true; + + // Soil measurements + telemetry.variant.environment_metrics.soil_moisture = 85; + telemetry.variant.environment_metrics.has_soil_moisture = true; + telemetry.variant.environment_metrics.soil_temperature = 18.54f; + telemetry.variant.environment_metrics.has_soil_temperature = true; + + pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size); + pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry); + return stream.bytes_written; +} + +// Test TELEMETRY_APP port with device metrics +void test_telemetry_device_metrics_serialization() +{ + uint8_t buffer[256]; + size_t payload_size = encode_telemetry_device_metrics(buffer, sizeof(buffer)); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check message type + TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end()); + TEST_ASSERT_EQUAL_STRING("telemetry", jsonObj["type"]->AsString().c_str()); + + // Check payload + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + + // Verify telemetry data + TEST_ASSERT_TRUE(payload.find("battery_level") != payload.end()); + TEST_ASSERT_EQUAL(85, (int)payload["battery_level"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("voltage") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.72f, payload["voltage"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("channel_utilization") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.56f, payload["channel_utilization"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("uptime_seconds") != payload.end()); + TEST_ASSERT_EQUAL(12345, (int)payload["uptime_seconds"]->AsNumber()); + + // Note: JSON serialization may not preserve exact 2-decimal formatting due to float precision + // We verify the numeric values are correct within tolerance + + delete root; +} + +// Test that telemetry environment metrics are properly serialized +void test_telemetry_environment_metrics_serialization() +{ + uint8_t buffer[256]; + size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer)); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check payload exists + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + + // Test key fields that should be present in the serializer + TEST_ASSERT_TRUE(payload.find("temperature") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("distance") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"]->AsNumber()); + + // Note: JSON serialization may have float precision limitations + // We focus on verifying numeric accuracy rather than exact string formatting + + delete root; +} + +// Test comprehensive environment metrics coverage +void test_telemetry_environment_metrics_comprehensive() +{ + uint8_t buffer[256]; + size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer)); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check payload exists + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + + // Check all 15 originally supported fields + TEST_ASSERT_TRUE(payload.find("temperature") != payload.end()); + TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end()); + TEST_ASSERT_TRUE(payload.find("barometric_pressure") != payload.end()); + TEST_ASSERT_TRUE(payload.find("gas_resistance") != payload.end()); + TEST_ASSERT_TRUE(payload.find("voltage") != payload.end()); + TEST_ASSERT_TRUE(payload.find("current") != payload.end()); + TEST_ASSERT_TRUE(payload.find("iaq") != payload.end()); + TEST_ASSERT_TRUE(payload.find("distance") != payload.end()); + TEST_ASSERT_TRUE(payload.find("lux") != payload.end()); + TEST_ASSERT_TRUE(payload.find("white_lux") != payload.end()); + TEST_ASSERT_TRUE(payload.find("wind_direction") != payload.end()); + TEST_ASSERT_TRUE(payload.find("wind_speed") != payload.end()); + TEST_ASSERT_TRUE(payload.find("wind_gust") != payload.end()); + TEST_ASSERT_TRUE(payload.find("wind_lull") != payload.end()); + TEST_ASSERT_TRUE(payload.find("radiation") != payload.end()); + + delete root; +} + +// Test for the 7 environment fields that were added to complete coverage +void test_telemetry_environment_metrics_missing_fields() +{ + uint8_t buffer[256]; + size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer)); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check payload exists + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + + // Check the 7 fields that were previously missing + TEST_ASSERT_TRUE(payload.find("ir_lux") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("uv_lux") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("weight") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("rainfall_1h") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("rainfall_24h") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("soil_moisture") != payload.end()); + TEST_ASSERT_EQUAL(85, (int)payload["soil_moisture"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("soil_temperature") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"]->AsNumber()); + + // Note: JSON float serialization may not preserve exact decimal formatting + // We verify the values are numerically correct within tolerance + + delete root; +} + +// Test that ALL environment fields are serialized (canary test for forgotten fields) +// This test will FAIL if a new environment field is added to the protobuf but not to the serializer +void test_telemetry_environment_metrics_complete_coverage() +{ + uint8_t buffer[256]; + size_t payload_size = encode_telemetry_environment_metrics_all_fields(buffer, sizeof(buffer)); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check payload exists + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + + // ✅ ALL 22 environment fields MUST be present and correct + // If this test fails, it means either: + // 1. A new field was added to the protobuf but not to the serializer + // 2. The encode_telemetry_environment_metrics_all_fields() function wasn't updated + + // Basic environment (3 fields) + TEST_ASSERT_TRUE(payload.find("temperature") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("barometric_pressure") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 1013.27f, payload["barometric_pressure"]->AsNumber()); + + // Gas and air quality (2 fields) + TEST_ASSERT_TRUE(payload.find("gas_resistance") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 50.58f, payload["gas_resistance"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("iaq") != payload.end()); + TEST_ASSERT_EQUAL(120, (int)payload["iaq"]->AsNumber()); + + // Power measurements (2 fields) + TEST_ASSERT_TRUE(payload.find("voltage") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.34f, payload["voltage"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("current") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.53f, payload["current"]->AsNumber()); + + // Light measurements (4 fields) + TEST_ASSERT_TRUE(payload.find("lux") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 450.12f, payload["lux"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("white_lux") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 380.95f, payload["white_lux"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("ir_lux") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("uv_lux") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"]->AsNumber()); + + // Distance measurement (1 field) + TEST_ASSERT_TRUE(payload.find("distance") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"]->AsNumber()); + + // Wind measurements (4 fields) + TEST_ASSERT_TRUE(payload.find("wind_direction") != payload.end()); + TEST_ASSERT_EQUAL(180, (int)payload["wind_direction"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("wind_speed") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 5.52f, payload["wind_speed"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("wind_gust") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 8.24f, payload["wind_gust"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("wind_lull") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.13f, payload["wind_lull"]->AsNumber()); + + // Weight measurement (1 field) + TEST_ASSERT_TRUE(payload.find("weight") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"]->AsNumber()); + + // Radiation measurement (1 field) + TEST_ASSERT_TRUE(payload.find("radiation") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.13f, payload["radiation"]->AsNumber()); + + // Rainfall measurements (2 fields) + TEST_ASSERT_TRUE(payload.find("rainfall_1h") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("rainfall_24h") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"]->AsNumber()); + + // Soil measurements (2 fields) + TEST_ASSERT_TRUE(payload.find("soil_moisture") != payload.end()); + TEST_ASSERT_EQUAL(85, (int)payload["soil_moisture"]->AsNumber()); + TEST_ASSERT_TRUE(payload.find("soil_temperature") != payload.end()); + TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"]->AsNumber()); + + // Total: 22 environment fields + // This test ensures 100% coverage of environment metrics + + // Note: JSON float serialization precision may vary due to the underlying library + // The important aspect is that all values are numerically accurate within tolerance + + delete root; +} + +// Test that unset environment fields are not present in JSON +void test_telemetry_environment_metrics_unset_fields() +{ + uint8_t buffer[256]; + size_t payload_size = encode_telemetry_environment_metrics_empty(buffer, sizeof(buffer)); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check payload exists + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + + // With completely empty environment metrics, NO fields should be present + // Only basic telemetry fields like "time" might be present + + // All 22 environment fields should be absent (none were set) + TEST_ASSERT_TRUE(payload.find("temperature") == payload.end()); + TEST_ASSERT_TRUE(payload.find("relative_humidity") == payload.end()); + TEST_ASSERT_TRUE(payload.find("barometric_pressure") == payload.end()); + TEST_ASSERT_TRUE(payload.find("gas_resistance") == payload.end()); + TEST_ASSERT_TRUE(payload.find("iaq") == payload.end()); + TEST_ASSERT_TRUE(payload.find("voltage") == payload.end()); + TEST_ASSERT_TRUE(payload.find("current") == payload.end()); + TEST_ASSERT_TRUE(payload.find("lux") == payload.end()); + TEST_ASSERT_TRUE(payload.find("white_lux") == payload.end()); + TEST_ASSERT_TRUE(payload.find("ir_lux") == payload.end()); + TEST_ASSERT_TRUE(payload.find("uv_lux") == payload.end()); + TEST_ASSERT_TRUE(payload.find("distance") == payload.end()); + TEST_ASSERT_TRUE(payload.find("wind_direction") == payload.end()); + TEST_ASSERT_TRUE(payload.find("wind_speed") == payload.end()); + TEST_ASSERT_TRUE(payload.find("wind_gust") == payload.end()); + TEST_ASSERT_TRUE(payload.find("wind_lull") == payload.end()); + TEST_ASSERT_TRUE(payload.find("weight") == payload.end()); + TEST_ASSERT_TRUE(payload.find("radiation") == payload.end()); + TEST_ASSERT_TRUE(payload.find("rainfall_1h") == payload.end()); + TEST_ASSERT_TRUE(payload.find("rainfall_24h") == payload.end()); + TEST_ASSERT_TRUE(payload.find("soil_moisture") == payload.end()); + TEST_ASSERT_TRUE(payload.find("soil_temperature") == payload.end()); + + delete root; +} diff --git a/test/test_meshpacket_serializer/ports/test_text_message.cpp b/test/test_meshpacket_serializer/ports/test_text_message.cpp new file mode 100644 index 000000000..de3f34541 --- /dev/null +++ b/test/test_meshpacket_serializer/ports/test_text_message.cpp @@ -0,0 +1,42 @@ +#include "../test_helpers.h" + +// Test TEXT_MESSAGE_APP port +void test_text_message_serialization() +{ + const char *test_text = "Hello Meshtastic!"; + meshtastic_MeshPacket packet = + create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, (const uint8_t *)test_text, strlen(test_text)); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check basic packet fields + TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end()); + TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber()); + + TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end()); + TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber()); + + TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end()); + TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber()); + + // Check message type + TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end()); + TEST_ASSERT_EQUAL_STRING("text", jsonObj["type"]->AsString().c_str()); + + // Check payload + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + TEST_ASSERT_TRUE(payload.find("text") != payload.end()); + TEST_ASSERT_EQUAL_STRING("Hello Meshtastic!", payload["text"]->AsString().c_str()); + + delete root; +} diff --git a/test/test_meshpacket_serializer/ports/test_waypoint.cpp b/test/test_meshpacket_serializer/ports/test_waypoint.cpp new file mode 100644 index 000000000..b7e811d70 --- /dev/null +++ b/test/test_meshpacket_serializer/ports/test_waypoint.cpp @@ -0,0 +1,53 @@ +#include "../test_helpers.h" + +static size_t encode_waypoint(uint8_t *buffer, size_t buffer_size) +{ + meshtastic_Waypoint waypoint = meshtastic_Waypoint_init_zero; + waypoint.id = 12345; + waypoint.latitude_i = 374208000; + waypoint.longitude_i = -1221981000; + waypoint.expire = 1609459200 + 3600; // 1 hour from now + strcpy(waypoint.name, "Test Point"); + strcpy(waypoint.description, "Test waypoint description"); + + pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size); + pb_encode(&stream, &meshtastic_Waypoint_msg, &waypoint); + return stream.bytes_written; +} + +// Test WAYPOINT_APP port +void test_waypoint_serialization() +{ + uint8_t buffer[256]; + size_t payload_size = encode_waypoint(buffer, sizeof(buffer)); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_WAYPOINT_APP, buffer, payload_size); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check message type + TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end()); + TEST_ASSERT_EQUAL_STRING("waypoint", jsonObj["type"]->AsString().c_str()); + + // Check payload + TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); + + JSONObject payload = jsonObj["payload"]->AsObject(); + + // Verify waypoint data + TEST_ASSERT_TRUE(payload.find("id") != payload.end()); + TEST_ASSERT_EQUAL(12345, (int)payload["id"]->AsNumber()); + + TEST_ASSERT_TRUE(payload.find("name") != payload.end()); + TEST_ASSERT_EQUAL_STRING("Test Point", payload["name"]->AsString().c_str()); + + delete root; +} diff --git a/test/test_meshpacket_serializer/test_helpers.h b/test/test_meshpacket_serializer/test_helpers.h new file mode 100644 index 000000000..630e059bc --- /dev/null +++ b/test/test_meshpacket_serializer/test_helpers.h @@ -0,0 +1,44 @@ +#pragma once + +#include "serialization/JSON.h" +#include "serialization/MeshPacketSerializer.h" +#include +#include +#include +#include +#include +#include +#include + +// Helper function to create a test packet with the given port and payload +static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const uint8_t *payload, size_t payload_size) +{ + meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero; + + packet.id = 0x9999; + packet.from = 0x11223344; + packet.to = 0x55667788; + packet.channel = 0; + packet.hop_limit = 3; + packet.want_ack = false; + packet.priority = meshtastic_MeshPacket_Priority_UNSET; + packet.rx_time = 1609459200; + packet.rx_snr = 10.5f; + packet.hop_start = 3; + packet.rx_rssi = -85; + packet.delayed = meshtastic_MeshPacket_Delayed_NO_DELAY; + + // Set decoded variant + packet.which_payload_variant = meshtastic_MeshPacket_decoded_tag; + packet.decoded.portnum = port; + memcpy(packet.decoded.payload.bytes, payload, payload_size); + packet.decoded.payload.size = payload_size; + packet.decoded.want_response = false; + packet.decoded.dest = 0x55667788; + packet.decoded.source = 0x11223344; + packet.decoded.request_id = 0; + packet.decoded.reply_id = 0; + packet.decoded.emoji = 0; + + return packet; +} diff --git a/test/test_meshpacket_serializer/test_serializer.cpp b/test/test_meshpacket_serializer/test_serializer.cpp new file mode 100644 index 000000000..d74031fa4 --- /dev/null +++ b/test/test_meshpacket_serializer/test_serializer.cpp @@ -0,0 +1,51 @@ +#include "test_helpers.h" +#include +#include + +// Forward declarations for test functions +void test_text_message_serialization(); +void test_position_serialization(); +void test_nodeinfo_serialization(); +void test_waypoint_serialization(); +void test_telemetry_device_metrics_serialization(); +void test_telemetry_environment_metrics_serialization(); +void test_telemetry_environment_metrics_comprehensive(); +void test_telemetry_environment_metrics_missing_fields(); +void test_telemetry_environment_metrics_complete_coverage(); +void test_telemetry_environment_metrics_unset_fields(); +void test_encrypted_packet_serialization(); + +void setup() +{ + UNITY_BEGIN(); + + // Text message tests + RUN_TEST(test_text_message_serialization); + + // Position tests + RUN_TEST(test_position_serialization); + + // Nodeinfo tests + RUN_TEST(test_nodeinfo_serialization); + + // Waypoint tests + RUN_TEST(test_waypoint_serialization); + + // Telemetry tests + RUN_TEST(test_telemetry_device_metrics_serialization); + RUN_TEST(test_telemetry_environment_metrics_serialization); + RUN_TEST(test_telemetry_environment_metrics_comprehensive); + RUN_TEST(test_telemetry_environment_metrics_missing_fields); + RUN_TEST(test_telemetry_environment_metrics_complete_coverage); + RUN_TEST(test_telemetry_environment_metrics_unset_fields); + + // Encrypted packet test + RUN_TEST(test_encrypted_packet_serialization); + + UNITY_END(); +} + +void loop() +{ + delay(1000); +} diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp index 8047079ba..32d81f6b4 100644 --- a/test/test_mqtt/MQTT.cpp +++ b/test/test_mqtt/MQTT.cpp @@ -27,6 +27,12 @@ #include #include +#if defined(UNIT_TEST) +#define IS_RUNNING_TESTS 1 +#else +#define IS_RUNNING_TESTS 0 +#endif + namespace { // Minimal router needed to receive messages from MQTT. @@ -56,7 +62,13 @@ class MockMeshService : public MeshService messages_.emplace_back(*m); releaseMqttClientProxyMessageToPool(m); } - std::list messages_; // Messages received from the MeshService. + void sendClientNotification(meshtastic_ClientNotification *n) override + { + notifications_.emplace_back(*n); + releaseClientNotificationToPool(n); + } + std::list messages_; // Messages received from the MeshService. + std::list notifications_; // Notifications received from the MeshService. }; // Minimal NodeDB needed to return values from getMeshNode. @@ -823,14 +835,6 @@ void test_configWithDefaultServerAndInvalidPort(void) TEST_ASSERT_FALSE(MQTT::isValidConfig(config)); } -// Configuration with the default server and tls_enabled = true is invalid. -void test_configWithDefaultServerAndInvalidTLSEnabled(void) -{ - meshtastic_ModuleConfig_MQTTConfig config = {.tls_enabled = true}; - - TEST_ASSERT_FALSE(MQTT::isValidConfig(config)); -} - // isValidConfig connects to a custom host and port. void test_configCustomHostAndPort(void) { @@ -911,7 +915,6 @@ void setup() RUN_TEST(test_configEnabledEmptyIsValid); RUN_TEST(test_configWithDefaultServer); RUN_TEST(test_configWithDefaultServerAndInvalidPort); - RUN_TEST(test_configWithDefaultServerAndInvalidTLSEnabled); RUN_TEST(test_configCustomHostAndPort); RUN_TEST(test_configWithConnectionFailure); RUN_TEST(test_configWithTLSEnabled); diff --git a/test/test_serial/SerialModule.cpp b/test/test_serial/SerialModule.cpp new file mode 100644 index 000000000..1bccf04a7 --- /dev/null +++ b/test/test_serial/SerialModule.cpp @@ -0,0 +1,156 @@ +#include "DebugConfiguration.h" +#include "TestUtil.h" +#include + +#ifdef ARCH_PORTDUINO +#include "configuration.h" + +#if defined(UNIT_TEST) +#define IS_RUNNING_TESTS 1 +#else +#define IS_RUNNING_TESTS 0 +#endif + +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) +#include "modules/SerialModule.h" +#endif + +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) + +// Test that empty configuration is valid. +void test_serialConfigEmptyIsValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = {}; + + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +// Test that basic enabled configuration is valid. +void test_serialConfigEnabledIsValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = {.enabled = true}; + + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and NMEA mode is valid. +void test_serialConfigWithOverrideConsoleNmeaModeIsValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA}; + + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and CalTopo mode is valid. +void test_serialConfigWithOverrideConsoleCalTopoModeIsValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO}; + + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and DEFAULT mode is invalid. +void test_serialConfigWithOverrideConsoleDefaultModeIsInvalid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT}; + + TEST_ASSERT_FALSE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and SIMPLE mode is invalid. +void test_serialConfigWithOverrideConsoleSimpleModeIsInvalid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_SIMPLE}; + + TEST_ASSERT_FALSE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and TEXTMSG mode is invalid. +void test_serialConfigWithOverrideConsoleTextMsgModeIsInvalid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG}; + + TEST_ASSERT_FALSE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and PROTO mode is invalid. +void test_serialConfigWithOverrideConsoleProtoModeIsInvalid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_PROTO}; + + TEST_ASSERT_FALSE(SerialModule::isValidConfig(config)); +} + +// Test that various modes work without override_console_serial_port. +void test_serialConfigVariousModesWithoutOverrideAreValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = {.enabled = true, .override_console_serial_port = false}; + + // Test DEFAULT mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test SIMPLE mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_SIMPLE; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test TEXTMSG mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test PROTO mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_PROTO; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test NMEA mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test CALTOPO mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +#endif // Architecture check + +void setup() +{ + initializeTestEnvironment(); + +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) + UNITY_BEGIN(); + RUN_TEST(test_serialConfigEmptyIsValid); + RUN_TEST(test_serialConfigEnabledIsValid); + RUN_TEST(test_serialConfigWithOverrideConsoleNmeaModeIsValid); + RUN_TEST(test_serialConfigWithOverrideConsoleCalTopoModeIsValid); + RUN_TEST(test_serialConfigWithOverrideConsoleDefaultModeIsInvalid); + RUN_TEST(test_serialConfigWithOverrideConsoleSimpleModeIsInvalid); + RUN_TEST(test_serialConfigWithOverrideConsoleTextMsgModeIsInvalid); + RUN_TEST(test_serialConfigWithOverrideConsoleProtoModeIsInvalid); + RUN_TEST(test_serialConfigVariousModesWithoutOverrideAreValid); + exit(UNITY_END()); +#else + LOG_WARN("This test requires ESP32, NRF52, or RP2040 architecture"); + UNITY_BEGIN(); + UNITY_END(); +#endif +} +#else +void setup() +{ + initializeTestEnvironment(); + LOG_WARN("This test requires the ARCH_PORTDUINO variant"); + UNITY_BEGIN(); + UNITY_END(); +} +#endif +void loop() {} diff --git a/userPrefs.jsonc b/userPrefs.jsonc index fc9e6ed72..f6f3ef995 100644 --- a/userPrefs.jsonc +++ b/userPrefs.jsonc @@ -23,6 +23,7 @@ // "USERPREFS_CONFIG_OWNER_SHORT_NAME": "MLN", // "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, LOST AND FOUND, and REPEATER roles are restricted. // "USERPREFS_EVENT_MODE": "1", + // "USERPREFS_FIRMWARE_EDITION": "meshtastic_FirmwareEdition_BURNING_MAN", // "USERPREFS_FIXED_BLUETOOTH": "121212", // "USERPREFS_FIXED_GPS": "", // "USERPREFS_FIXED_GPS_ALT": "0", @@ -53,5 +54,7 @@ // "USERPREFS_MQTT_ENCRYPTION_ENABLED": "true", // "USERPREFS_MQTT_TLS_ENABLED": "false", // "USERPREFS_MQTT_ROOT_TOPIC": "event/REPLACEME", + // "USERPREFS_RINGTONE_NAG_SECS": "60", + "USERPREFS_RINGTONE_RTTTL": "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p", "USERPREFS_TZ_STRING": "tzplaceholder " } diff --git a/variants/diy/platformio.ini b/variants/diy/platformio.ini deleted file mode 100644 index 153796daf..000000000 --- a/variants/diy/platformio.ini +++ /dev/null @@ -1,129 +0,0 @@ -; Meshtastic DIY v1 by Nano VHF Schematic based on ESP32-WROOM-32 (38 pins) devkit & EBYTE E22 SX1262/SX1268 module -[env:meshtastic-diy-v1] -extends = esp32_base -board = esp32doit-devkit-v1 -board_check = true -build_flags = - ${esp32_base.build_flags} - -D DIY_V1 - -D EBYTE_E22 - -I variants/diy/v1 - -; Meshtastic DIY v1.1 new schematic based on ESP32-WROOM-32 & SX1262/SX1268 modules -[env:meshtastic-diy-v1_1] -extends = esp32_base -board = esp32doit-devkit-v1 -board_level = extra -build_flags = - ${esp32_base.build_flags} - -D DIY_V1 - -D EBYTE_E22 - -I variants/diy/v1_1 - -; Port to Disaster Radio's ESP32-v3 Dev Board -[env:meshtastic-dr-dev] -extends = esp32_base -board = esp32doit-devkit-v1 -board_upload.maximum_size = 4194304 -board_upload.maximum_ram_size = 532480 -build_flags = - ${esp32_base.build_flags} - -D DR_DEV - -D EBYTE_E22 - -I variants/diy/dr-dev - -; Hydra - Meshtastic DIY v1 hardware with some specific changes -[env:hydra] -extends = esp32_base -board = esp32doit-devkit-v1 -build_flags = - ${esp32_base.build_flags} - -D DIY_V1 - -I variants/diy/hydra - - -; Promicro + E22(0)-xxxMM / RA-01SH modules board variant - DIY - without TCXO -[env:nrf52_promicro_diy_xtal] -extends = nrf52840_base -board = promicro-nrf52840 -board_level = extra -build_flags = ${nrf52840_base.build_flags} - -I variants/diy/nrf52_promicro_diy_xtal - -D NRF52_PROMICRO_DIY -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_xtal> -lib_deps = - ${nrf52840_base.lib_deps} -debug_tool = jlink - - -; Promicro + E22(0)-xxxM / HT-RA62 modules board variant - DIY - with TCXO -[env:nrf52_promicro_diy_tcxo] -extends = nrf52840_base -board = promicro-nrf52840 -build_flags = ${nrf52840_base.build_flags} - -I variants/diy/nrf52_promicro_diy_tcxo - -D NRF52_PROMICRO_DIY -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_tcxo> -lib_deps = - ${nrf52840_base.lib_deps} -debug_tool = jlink - -; NRF52 ProMicro w/ E-Ink display -[env:nrf52_promicro_diy-inkhud] -board_level = extra -extends = nrf52840_base, inkhud -board = promicro-nrf52840 -build_flags = - ${nrf52840_base.build_flags} - ${inkhud.build_flags} - -I variants/diy/nrf52_promicro_diy_tcxo - -D NRF52_PROMICRO_DIY -build_src_filter = - ${nrf52_base.build_src_filter} - ${inkhud.build_src_filter} - +<../variants/diy/nrf52_promicro_diy_tcxo> -lib_deps = - ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX - ${nrf52840_base.lib_deps} -extra_scripts = - ${env.extra_scripts} - variants/diy/nrf52_promicro_diy_tcxo/custom_build_tasks.py ; Add to PIO's Project Tasks pane: preset builds for common displays - -; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893 -[env:xiao_ble] -extends = env:seeed_xiao_nrf52840_kit -board_level = extra -build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DXIAO_BLE_LEGACY_PINOUT -DEBYTE_E22 -DEBYTE_E22_900M30S -build_unflags = -DGPS_L76K - -; Seeed XIAO nRF52840 + XIAO Wio SX1262 DIY -[env:seeed-xiao-nrf52840-wio-sx1262] -board = xiao_ble_sense -extends = nrf52840_base -board_level = extra -build_flags = ${nrf52840_base.build_flags} -Ivariants/diy/seeed-xiao-nrf52840-wio-sx1262 -D PRIVATE_HW - -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/seeed-xiao-nrf52840-wio-sx1262> -lib_deps = - ${nrf52840_base.lib_deps} -debug_tool = jlink - -; NanoVHF T-Energy-S3 + E22(0)-xxxM - DIY -[env:t-energy-s3_e22] -extends = esp32s3_base -board = esp32-s3-devkitc-1 -board_build.partitions = default_16MB.csv -board_level = extra -board_upload.flash_size = 16MB ;Specify the FLASH capacity as 16MB -board_build.arduino.memory_type = qio_opi ;Enable internal PSRAM -build_unflags = - ${esp32s3_base.build_unflags} - -D ARDUINO_USB_MODE=1 -build_flags = - ${esp32s3_base.build_flags} - -D EBYTE_ESP32_S3 - -D BOARD_HAS_PSRAM - -D ARDUINO_USB_MODE=0 - -D ARDUINO_USB_CDC_ON_BOOT=1 - -I variants/diy/t-energy-s3_e22 diff --git a/variants/diy/xiao_ble/README.md b/variants/diy/xiao_ble/README.md deleted file mode 100644 index 2a08138ba..000000000 --- a/variants/diy/xiao_ble/README.md +++ /dev/null @@ -1,264 +0,0 @@ -# - -

- Xiao BLE/BLE Sense + Ebyte E22-900M30S -

- -

- A step-by-step guide for macOS and Linux -

- -## Introduction - -This guide will walk you through everything needed to get the Xiao BLE (or BLE Sense) running Meshtastic using an Ebyte E22-900M30S LoRa module. The combination of the E22 with an nRF52840 MCU is desirable because it allows for both very low idle (Rx) power draw and high transmit power. The Xiao BLE is a small but surprisingly well-appointed nRF52840 board, with enough GPIO for most Meshtastic applications and a built-in LiPo charger. The E22, on the other hand, is a famously inscrutable and mysterious beast. It is one of the more readily available LoRa modules capable of transmitting at 30 dBm, and includes an LNA to boost its Rx sensitivity a few dB beyond that of the SX1262. However, its documentation is relatively sparse overall, and seems to merely hint at (or completely omit) several key details regarding its functionality. Thus, much of what follows is a synthesis of my observations and inferences over the course of many hours of trial and error. - -

Acknowledgement and friendly disclaimer

- -Huge thanks to those in the community who have forged the way with the E22, without whose hard work none of this would have been possible! (thebentern, riddick, rainer_vie, beegee-tokyo, geeksville, caveman99, Der_Bear, PlumRugOfDoom, BigCorvus, and many others.) - -
- -Please take the conclusions here as a tentative work in progress, representing my current (and fairly limited) understanding of the E22 when paired with this particular MCU. It is my hope that this guide will be helpful to others who are interested in trying a DIY Meshtastic build, and also be subject to revision by folks with more experience and better test equipment. - -### Obligatory liability disclaimer - -This guide and all associated content is for informational purposes only. The information presented is intended for consumption only by persons having appropriate technical skill and judgement, to be used entirely at their own discretion and risk. The authors of this guide in no way provide any warranty, express or implied, toward the content herein, nor its correctness, safety, or suitability to any particular purpose. By following the instructions in this guide in part or in full, you assume all responsibility for all potential risks, including but not limited to fire, property damage, bodily injury, and death. - -### Note - -These instructions assume you are running macOS or Linux, but it should be relatively easy to translate each command for Windows. (In this case, in step 2 below, each line of `xiao_ble.sh` would also need to be converted to the equivalent Windows CLI command and run individually.) - -## 1. Update Bootloader - -The first thing you will need to do is update the Xiao BLE's bootloader. The stock bootloader is functionally very similar to the Adafruit nRF52 UF2 bootloader, but apparently not quite enough so to work with Meshtastic out of the box. - -1. Connect the Xiao BLE to your computer via USB-C. - -2. Install `adafruit-nrfutil` by following the instructions here. - -3. Open a terminal window and navigate to `firmware/variants/xiao_ble` (where `firmware` is the directory into which you have cloned the Meshtastic firmware repo). - -4. Run the following command, replacing `/dev/cu.usbmodem2101` with the serial port your Xiao BLE is connected to: - - ```bash - adafruit-nrfutil --verbose dfu serial --package xiao_nrf52840_ble_bootloader-0.7.0-22-g277a0c8_s140_7.3.0.zip --port /dev/cu.usbmodem2101 -b 115200 --singlebank --touch 1200 - ``` - -5. If all goes well, the Xiao BLE's red LED should start to pulse slowly, and you should see a new USB storage device called `XIAO-BOOT` appear under `Locations` in Finder. - -  - -## 2. PlatformIO Environment Preparation - -Before building Meshtastic for the Xiao BLE + E22, it is necessary to pull in SoftDevice 7.3.0 and its associated linker script (nrf52840_s140_v7.ld) from Seeed Studio's Arduino core. The `xiao_ble.sh` script does this. - -1. In your terminal window, run the following command: - - ```bash - sudo ./xiao_ble.sh - ``` - -  - -## 3. Build Meshtastic - -At this point, you should be able to build the firmware successfully. - -1. In VS Code, press `Command Shift P` to bring up the command palette. - -2. Search for and run the `Developer: Reload Window` command. - -3. Bring up the command palette again with `Command Shift P`. Search for and run the `PlatformIO: Pick Project Environment` command. - -4. In the list of environments, select `env:xiao_ble`. PlatformIO may update itself for a minute or two, and should let you know once done. - -5. Return to the command palette once again (`Command Shift P`). Search for and run the `PlatformIO: Build` command. - -6. PlatformIO will build the project. After a few minutes you should see a green `SUCCESS` message. - -  - -## 4. Wire the board - -Connecting the E22 to the Xiao BLE is straightforward, but there are a few gotchas to be mindful of. - -- On the Xiao BLE: - - - Pins D4 and D5 are currently mapped to `PIN_WIRE_SDA` and `PIN_WIRE_SCL`, respectively. If you are not using I²C and would like to free up pins D4 and D5 for use as GPIO, `PIN_WIRE_SDA` and `PIN_WIRE_SCL` can be reassigned to any two other unused pin numbers. - - - Pins D6 and D7 were originally mapped to the TX and RX pins for serial interface 1 (`PIN_SERIAL1_RX` and `PIN_SERIAL1_TX`) but are currently set to -1 in `variant.h`. If you need to expose a serial interface, you can restore these pins and move e.g. `SX126X_RXEN` to pin 4 or 5 (the opposite should work too). - -- On the E22: - - - There are two options for the E22's `TXEN` pin: - - 1. It can be connected to the MCU on the pin defined as `SX126X_TXEN` in `variant.h`. In this configuration, the MCU will control Tx/Rx switching "manually". As long as `SX126X_TXEN` and `SX126X_RXEN` are both defined in `variant.h` (and neither is set to `RADIOLIB_NC`), `SX126xInterface.cpp` will initialize the E22 correctly for this mode. - - 2. Alternately, it can be connected to the E22's `DIO2` pin only, with neither `TXEN` nor `DIO2` being connected to the MCU. In this configuration, the E22 will control Tx/Rx switching automatically. In `variant.h`, as long as `SX126X_TXEN` is defined as `RADIOLIB_NC`, and `SX126X_RXEN` is defined and connected to the E22's `RXEN` pin, and `E22_TXEN_CONNECTED_TO_DIO2` is defined, `SX126xInterface.cpp` will initialize the E22 correctly for this mode. This configuration frees up a GPIO, and presents no drawbacks that I have found. - - - Note that any combination other than the two described above will likely result in unexpected behavior. In my testing, some of these other configurations appeared to "work" at first glance, but every one I tried had at least one of the following flaws: weak Tx power, extremely poor Rx sensitivity, or the E22 overheating because TXEN was never pulled low, causing its PA to stay on indefinitely. - - - Along the same lines, it is a good idea to check the E22's temperature frequently by lightly touching the shield. If you feel the shield getting hot (i.e. approaching uncomfortable to touch) near pins 1, 2, and 3, something is probably misconfigured; disconnect both the Xiao BLE and E22 from power and double check wiring and pin mapping. - - - Whether you opt to let the E22 control Rx and Tx or handle this manually, the E22's `RXEN` pin must always be connected to the MCU on the pin defined as `SX126X_RXEN` in `variant.h`. - -

Note

- -The default pin mapping in `variant.h` uses 'automatic Tx/Rx switching' mode. If you wire your board for manual Rx/Tx switching, make sure to update `variant.h` accordingly by commenting/uncommenting the necessary lines in the 'E22 Tx/Rx control options' section. - -  - ---- - -  - -

Example wiring for "E22 automatic Tx/Rx switching" mode:

-  - -MCU -> E22 connections - -| Xiao BLE pin | variant.h definition | E22 pin | Notes | -| :----------- | :------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------- | -| D0 | SX126X_CS | 19 (NSS) | | -| D1 | SX126X_DIO1 | 13 (DIO1) | | -| D2 | SX126X_BUSY | 14 (BUSY) | | -| D3 | SX126X_RESET | 15 (NRST) | | -| D7 | SX126X_RXEN | 6 (RXEN) | These pins must still be connected, and `SX126X_RXEN` defined in `variant.h`, otherwise Rx sensitivity will be poor. | -| D8 | PIN_SPI_SCK | 18 (SCK) | | -| D9 | PIN_SPI_MISO | 16 (MISO) | | -| D10 | PIN_SPI_MOSI | 17 (MOSI) | | - -  -  - -E22 -> E22 connections: - -| E22 pin | E22 pin | Notes | -| :------ | :------ | :------------------------------------------------------------------------ | -| TXEN | DIO2 | These must be physically connected for automatic Tx/Rx switching to work. | - -

Note

- -The schematic (`xiao-ble-e22-schematic.png`) in the `eagle-project` directory uses this wiring. - -  - ---- - -  - -

Example wiring for "Manual Tx/Rx switching" mode:

- -MCU -> E22 connections - -| Xiao BLE pin | variant.h definition | E22 pin | Notes | -| :----------- | :------------------- | :-------- | :---- | -| D0 | SX126X_CS | 19 (NSS) | | -| D1 | SX126X_DIO1 | 13 (DIO1) | | -| D2 | SX126X_BUSY | 14 (BUSY) | | -| D3 | SX126X_RESET | 15 (NRST) | | -| D6 | SX126X_TXEN | 7 (TXEN) | | -| D7 | SX126X_RXEN | 6 (RXEN) | | -| D8 | PIN_SPI_SCK | 18 (SCK) | | -| D9 | PIN_SPI_MISO | 16 (MISO) | | -| D10 | PIN_SPI_MOSI | 17 (MOSI) | | - -E22 -> E22 connections: (none) - -  - -## 5. Flash the firmware to the Xiao BLE - -1. Double press the Xiao's `reset` button to put it in bootloader mode. -2. In a terminal window, navigate to the Meshtastic firmware repo's root directory, and from there to `.pio/build/xiao_ble`. -3. Convert the generated `.hex` file into a `.uf2` file: - - ```bash - ../../../bin/uf2conv.py firmware.hex -c -o firmware.uf2 -f 0xADA52840 - ``` - -4. Copy the new `.uf2` file to the Xiao's mass storage volume: - - ```bash - cp firmware.uf2 /Volumes/XIAO-BOOT - ``` - -5. The Xiao's red LED will flash for several seconds as the firmware is copied. -6. Once the firmware is copied, to verify it is running, run the following command: - - ```bash - meshtastic --noproto - ``` - -7. Then, press the Xiao's `reset` button again. You should see a lot of debug output logged in the terminal window. - -  - -## 6. Troubleshooting - -- If after flashing Meshtastic, the Xiao is bootlooped, look at the serial output (you can see this by running `meshtastic --noproto` with the device connected to your computer via USB). - - - If you see that the SX1262 init result was -2, this likely indicates a wiring problem; double check your wiring and pin mapping in `variant.h`. - - - If you see an error mentioning tinyFS, this may mean you need to reformat the Xiao's storage: - - 1. Double press the `reset` button to put the Xiao in bootloader mode. - - 2. In a terminal window, navigate to the Meshtastic firmware repo's root directory, and from there to `variants/xiao_ble`. - - 3. Run the following command:  `cp xiao-ble-internal-format.uf2 /Volumes/XIAO-BOOT` - - 4. The Xiao's red LED will flash briefly as the filesystem format firmware is copied. - - 5. Run the following command:  `meshtastic --noproto` - - 6. In the output of the above command, you should see a message saying "Formatting...done". - - 7. To flash Meshtastic again, repeat the steps in section 5 above. - - - If you don't see any specific error message, but the boot process is stuck or not proceeding as expected, this might also mean there is a conflict in `variant.h`. If you have made any changes to the pin mapping, ensure they do not result in a conflict. If all else fails, try reverting your changes and using the known-good configuration included here. - - - The above might also mean something is wired incorrectly. Try reverting to one of the known-good example wirings in section 4. - -- If the E22 gets hot to the touch: - - The power amplifier is likely running continually. Disconnect it and the Xiao from power immediately, and double check wiring and pin mapping. In my experimentation this occurred in cases where TXEN was inadvertenly high (usually due to a pin mapping conflict). - -  - -## 7. Notes - -- There are several anecdotal recommendations regarding the Tx power the E22's internal SX1262 should be set to in order to achieve the advertised output of 30 dBm, ranging from 4 (per this article in the RadioLib github repo) to 22 (per this conversation from the Meshtastic Discord). When paired with the Xiao BLE in the configurations described above, I observed that the output is at its maximum when Tx power is set to 22. - -- To achieve its full output, the E22 should have a bypass capacitor from its 5V supply to ground. 100 µF works well. - -- The E22 will happily run on voltages lower than 5V, but the full output power will not be realized. For example, with a fully charged LiPo at 4.2V, Tx power appears to max out around 26-27 dBm. - -  - -## 8. Testing Methodology - -During what became a fairly long trial-and-error process, I did a lot of careful testing of Tx power and Rx sensitivity. My methodology in these tests was as follows: - -- All tests were conducted between two nodes: - - 1. The Xiao BLE + E22 coupled with an Abracon ARRKP4065-S915A ceramic patch antenna - - 2. A RAK 5005/4631 coupled with a Laird MA9-5N antenna via a 4" U.FL to Type N pigtail. - - - No other nodes were powered up onsite or nearby. - -
- -- Each node and its antenna was kept in exactly the same position and orientation throughout testing. - -- Other environmental factors (e.g. the location and resting position of my body in the room while testing) were controlled as carefully as possible. - -- Each test comprised at least five (and often ten) runs, after which the results were averaged. - -- All testing was done by sending single-character messages between nodes and observing the received RSSI reported in the message acknowledgement. Messages were sent one by one, waiting for each to be acknowledged or time out before sending the next. - -- The E22's Tx power was observed by sending messages from the RAK to the Xiao BLE + E22 and recording the received RSSI. - -- The opposite was done to observe the E22's Rx sensitivity: messages were sent from the Xiao BLE + E22 to the RAK, and the received RSSI was recorded. - -While this cannot match the level of accuracy achievable with actual test equipment in a lab setting, it was nonetheless sufficient to demonstrate the (sometimes very large) differences in Tx power and Rx sensitivity between various configurations. diff --git a/variants/betafpv_2400_tx_micro/platformio.ini b/variants/esp32/betafpv_2400_tx_micro/platformio.ini similarity index 81% rename from variants/betafpv_2400_tx_micro/platformio.ini rename to variants/esp32/betafpv_2400_tx_micro/platformio.ini index 531e8532d..4d163d834 100644 --- a/variants/betafpv_2400_tx_micro/platformio.ini +++ b/variants/esp32/betafpv_2400_tx_micro/platformio.ini @@ -8,11 +8,11 @@ build_flags = -D VTABLES_IN_FLASH=1 -D CONFIG_DISABLE_HAL_LOCKS=1 -O2 - -I variants/betafpv_2400_tx_micro + -I variants/esp32/betafpv_2400_tx_micro board_build.f_cpu = 240000000L upload_protocol = esptool ;upload_port = /dev/ttyUSB0 upload_speed = 460800 lib_deps = ${esp32_base.lib_deps} - adafruit/Adafruit NeoPixel @ ^1.12.0 \ No newline at end of file + adafruit/Adafruit NeoPixel @ ^1.12.0 diff --git a/variants/betafpv_2400_tx_micro/variant.h b/variants/esp32/betafpv_2400_tx_micro/variant.h similarity index 100% rename from variants/betafpv_2400_tx_micro/variant.h rename to variants/esp32/betafpv_2400_tx_micro/variant.h diff --git a/variants/betafpv_900_tx_nano/platformio.ini b/variants/esp32/betafpv_900_tx_nano/platformio.ini similarity index 84% rename from variants/betafpv_900_tx_nano/platformio.ini rename to variants/esp32/betafpv_900_tx_nano/platformio.ini index 3bea16f6b..7e01fd2fa 100644 --- a/variants/betafpv_900_tx_nano/platformio.ini +++ b/variants/esp32/betafpv_900_tx_nano/platformio.ini @@ -8,10 +8,10 @@ build_flags = -D VTABLES_IN_FLASH=1 -D CONFIG_DISABLE_HAL_LOCKS=1 -O2 - -I variants/betafpv_900_tx_nano + -I variants/esp32/betafpv_900_tx_nano board_build.f_cpu = 240000000L upload_protocol = esptool ;upload_port = /dev/ttyUSB0 upload_speed = 460800 lib_deps = - ${esp32_base.lib_deps} \ No newline at end of file + ${esp32_base.lib_deps} diff --git a/variants/betafpv_900_tx_nano/variant.h b/variants/esp32/betafpv_900_tx_nano/variant.h similarity index 100% rename from variants/betafpv_900_tx_nano/variant.h rename to variants/esp32/betafpv_900_tx_nano/variant.h diff --git a/variants/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini similarity index 91% rename from variants/chatter2/platformio.ini rename to variants/esp32/chatter2/platformio.ini index 83e00d0c4..bf496bf26 100644 --- a/variants/chatter2/platformio.ini +++ b/variants/esp32/chatter2/platformio.ini @@ -5,7 +5,7 @@ board = esp32doit-devkit-v1 build_flags = ${esp32_base.build_flags} -D CHATTER_2 - -I variants/chatter2 + -I variants/esp32/chatter2 lib_deps = ${esp32_base.lib_deps} diff --git a/variants/chatter2/variant.h b/variants/esp32/chatter2/variant.h similarity index 100% rename from variants/chatter2/variant.h rename to variants/esp32/chatter2/variant.h diff --git a/variants/esp32/diy/dr-dev/platformio.ini b/variants/esp32/diy/dr-dev/platformio.ini new file mode 100644 index 000000000..5461d27b3 --- /dev/null +++ b/variants/esp32/diy/dr-dev/platformio.ini @@ -0,0 +1,11 @@ +; Port to Disaster Radio's ESP32-v3 Dev Board +[env:meshtastic-dr-dev] +extends = esp32_base +board = esp32doit-devkit-v1 +board_upload.maximum_size = 4194304 +board_upload.maximum_ram_size = 532480 +build_flags = + ${esp32_base.build_flags} + -D DR_DEV + -D EBYTE_E22 + -I variants/esp32/diy/dr-dev diff --git a/variants/diy/dr-dev/variant.h b/variants/esp32/diy/dr-dev/variant.h similarity index 100% rename from variants/diy/dr-dev/variant.h rename to variants/esp32/diy/dr-dev/variant.h diff --git a/variants/esp32/diy/hydra/platformio.ini b/variants/esp32/diy/hydra/platformio.ini new file mode 100644 index 000000000..a922ed874 --- /dev/null +++ b/variants/esp32/diy/hydra/platformio.ini @@ -0,0 +1,8 @@ +; Hydra - Meshtastic DIY v1 hardware with some specific changes +[env:hydra] +extends = esp32_base +board = esp32doit-devkit-v1 +build_flags = + ${esp32_base.build_flags} + -D DIY_V1 + -I variants/esp32/diy/hydra diff --git a/variants/diy/hydra/variant.h b/variants/esp32/diy/hydra/variant.h similarity index 100% rename from variants/diy/hydra/variant.h rename to variants/esp32/diy/hydra/variant.h diff --git a/variants/esp32/diy/v1/platformio.ini b/variants/esp32/diy/v1/platformio.ini new file mode 100644 index 000000000..bcbd57cfa --- /dev/null +++ b/variants/esp32/diy/v1/platformio.ini @@ -0,0 +1,10 @@ +; Meshtastic DIY v1 by Nano VHF Schematic based on ESP32-WROOM-32 (38 pins) devkit & EBYTE E22 SX1262/SX1268 module +[env:meshtastic-diy-v1] +extends = esp32_base +board = esp32doit-devkit-v1 +board_check = true +build_flags = + ${esp32_base.build_flags} + -D DIY_V1 + -D EBYTE_E22 + -I variants/esp32/diy/v1 diff --git a/variants/diy/v1/variant.h b/variants/esp32/diy/v1/variant.h similarity index 100% rename from variants/diy/v1/variant.h rename to variants/esp32/diy/v1/variant.h diff --git a/variants/esp32/diy/v1_1/platformio.ini b/variants/esp32/diy/v1_1/platformio.ini new file mode 100644 index 000000000..1431bd4c8 --- /dev/null +++ b/variants/esp32/diy/v1_1/platformio.ini @@ -0,0 +1,10 @@ +; Meshtastic DIY v1.1 new schematic based on ESP32-WROOM-32 & SX1262/SX1268 modules +[env:meshtastic-diy-v1_1] +extends = esp32_base +board = esp32doit-devkit-v1 +board_level = extra +build_flags = + ${esp32_base.build_flags} + -D DIY_V1 + -D EBYTE_E22 + -I variants/esp32/diy/v1_1 diff --git a/variants/diy/v1_1/variant.h b/variants/esp32/diy/v1_1/variant.h similarity index 100% rename from variants/diy/v1_1/variant.h rename to variants/esp32/diy/v1_1/variant.h diff --git a/variants/hackerboxes_esp32_io/platformio.ini b/variants/esp32/hackerboxes_esp32_io/platformio.ini similarity index 85% rename from variants/hackerboxes_esp32_io/platformio.ini rename to variants/esp32/hackerboxes_esp32_io/platformio.ini index f024dac3e..fc5f6701c 100644 --- a/variants/hackerboxes_esp32_io/platformio.ini +++ b/variants/esp32/hackerboxes_esp32_io/platformio.ini @@ -5,7 +5,7 @@ board_level = extra build_flags = ${esp32_base.build_flags} -D PRIVATE_HW - -I variants/hackerboxes_esp32_io + -I variants/esp32/hackerboxes_esp32_io monitor_speed = 115200 upload_protocol = esptool ;upload_port = /dev/ttyUSB0 diff --git a/variants/hackerboxes_esp32_io/variant.h b/variants/esp32/hackerboxes_esp32_io/variant.h similarity index 100% rename from variants/hackerboxes_esp32_io/variant.h rename to variants/esp32/hackerboxes_esp32_io/variant.h diff --git a/variants/heltec_v1/platformio.ini b/variants/esp32/heltec_v1/platformio.ini similarity index 70% rename from variants/heltec_v1/platformio.ini rename to variants/esp32/heltec_v1/platformio.ini index ee10ef0f6..4be3ba655 100644 --- a/variants/heltec_v1/platformio.ini +++ b/variants/esp32/heltec_v1/platformio.ini @@ -4,4 +4,6 @@ extends = esp32_base board_level = extra board = heltec_wifi_lora_32 build_flags = - ${esp32_base.build_flags} -D HELTEC_V1 -I variants/heltec_v1 \ No newline at end of file + ${esp32_base.build_flags} + -D HELTEC_V1 + -I variants/esp32/heltec_v1 diff --git a/variants/heltec_v1/variant.h b/variants/esp32/heltec_v1/variant.h similarity index 100% rename from variants/heltec_v1/variant.h rename to variants/esp32/heltec_v1/variant.h diff --git a/variants/heltec_v2.1/platformio.ini b/variants/esp32/heltec_v2.1/platformio.ini similarity index 65% rename from variants/heltec_v2.1/platformio.ini rename to variants/esp32/heltec_v2.1/platformio.ini index ea2281911..763f9764c 100644 --- a/variants/heltec_v2.1/platformio.ini +++ b/variants/esp32/heltec_v2.1/platformio.ini @@ -4,5 +4,7 @@ board_level = extra extends = esp32_base board = heltec_wifi_lora_32_V2 build_flags = - ${esp32_base.build_flags} -D HELTEC_V2_1 -I variants/heltec_v2.1 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. \ No newline at end of file + ${esp32_base.build_flags} + -D HELTEC_V2_1 + -I variants/esp32/heltec_v2.1 + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/heltec_v2.1/variant.h b/variants/esp32/heltec_v2.1/variant.h similarity index 100% rename from variants/heltec_v2.1/variant.h rename to variants/esp32/heltec_v2.1/variant.h diff --git a/variants/heltec_v2/platformio.ini b/variants/esp32/heltec_v2/platformio.ini similarity index 70% rename from variants/heltec_v2/platformio.ini rename to variants/esp32/heltec_v2/platformio.ini index c81bca8ba..ed455616d 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/esp32/heltec_v2/platformio.ini @@ -4,4 +4,6 @@ board_level = extra extends = esp32_base board = heltec_wifi_lora_32_V2 build_flags = - ${esp32_base.build_flags} -D HELTEC_V2_0 -I variants/heltec_v2 \ No newline at end of file + ${esp32_base.build_flags} + -D HELTEC_V2_0 + -I variants/esp32/heltec_v2 diff --git a/variants/heltec_v2/variant.h b/variants/esp32/heltec_v2/variant.h similarity index 100% rename from variants/heltec_v2/variant.h rename to variants/esp32/heltec_v2/variant.h diff --git a/variants/heltec_wireless_bridge/platformio.ini b/variants/esp32/heltec_wireless_bridge/platformio.ini similarity index 94% rename from variants/heltec_wireless_bridge/platformio.ini rename to variants/esp32/heltec_wireless_bridge/platformio.ini index ab30eb744..60e686f9e 100644 --- a/variants/heltec_wireless_bridge/platformio.ini +++ b/variants/esp32/heltec_wireless_bridge/platformio.ini @@ -4,7 +4,7 @@ extends = esp32_base board = heltec_wifi_lora_32 build_flags = ${esp32_base.build_flags} - -I variants/heltec_wireless_bridge + -I variants/esp32/heltec_wireless_bridge -D HELTEC_WIRELESS_BRIDGE -D BOARD_HAS_PSRAM -D RADIOLIB_EXCLUDE_LR11X0=1 diff --git a/variants/heltec_wireless_bridge/variant.h b/variants/esp32/heltec_wireless_bridge/variant.h similarity index 100% rename from variants/heltec_wireless_bridge/variant.h rename to variants/esp32/heltec_wireless_bridge/variant.h diff --git a/variants/heltec_wsl_v2.1/platformio.ini b/variants/esp32/heltec_wsl_v2.1/platformio.ini similarity index 56% rename from variants/heltec_wsl_v2.1/platformio.ini rename to variants/esp32/heltec_wsl_v2.1/platformio.ini index f4fff9698..eb44c88d2 100644 --- a/variants/heltec_wsl_v2.1/platformio.ini +++ b/variants/esp32/heltec_wsl_v2.1/platformio.ini @@ -3,5 +3,7 @@ extends = esp32_base board = heltec_wireless_stick_lite board_level = extra build_flags = - ${esp32_base.build_flags} -D PRIVATE_HW -I variants/heltec_wsl_v2.1 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. \ No newline at end of file + ${esp32_base.build_flags} + -D PRIVATE_HW + -I variants/esp32/heltec_wsl_v2.1 + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/heltec_wsl_v2.1/variant.h b/variants/esp32/heltec_wsl_v2.1/variant.h similarity index 100% rename from variants/heltec_wsl_v2.1/variant.h rename to variants/esp32/heltec_wsl_v2.1/variant.h diff --git a/variants/m5stack_core/pins_arduino.h b/variants/esp32/m5stack_core/pins_arduino.h similarity index 100% rename from variants/m5stack_core/pins_arduino.h rename to variants/esp32/m5stack_core/pins_arduino.h diff --git a/variants/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini similarity index 90% rename from variants/m5stack_core/platformio.ini rename to variants/esp32/m5stack_core/platformio.ini index 7418d9e17..469d93f94 100644 --- a/variants/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -5,7 +5,8 @@ monitor_filters = esp32_exception_decoder build_src_filter = ${esp32_base.build_src_filter} build_flags = - ${esp32_base.build_flags} -I variants/m5stack_core + ${esp32_base.build_flags} + -I variants/esp32/m5stack_core -DILI9341_DRIVER -DM5STACK -DUSER_SETUP_LOADED diff --git a/variants/m5stack_core/variant.h b/variants/esp32/m5stack_core/variant.h similarity index 100% rename from variants/m5stack_core/variant.h rename to variants/esp32/m5stack_core/variant.h diff --git a/variants/m5stack_coreink/pins_arduino.h b/variants/esp32/m5stack_coreink/pins_arduino.h similarity index 100% rename from variants/m5stack_coreink/pins_arduino.h rename to variants/esp32/m5stack_coreink/pins_arduino.h diff --git a/variants/m5stack_coreink/platformio.ini b/variants/esp32/m5stack_coreink/platformio.ini similarity index 90% rename from variants/m5stack_coreink/platformio.ini rename to variants/esp32/m5stack_coreink/platformio.ini index 70da53379..1a00788e3 100644 --- a/variants/m5stack_coreink/platformio.ini +++ b/variants/esp32/m5stack_coreink/platformio.ini @@ -5,7 +5,8 @@ board_check = true build_src_filter = ${esp32_base.build_src_filter} build_flags = - ${esp32_base.build_flags} -I variants/m5stack_coreink + ${esp32_base.build_flags} + -I variants/esp32/m5stack_coreink ;-D RADIOLIB_VERBOSE -Ofast -D__MCUXPRESSO diff --git a/variants/m5stack_coreink/variant.h b/variants/esp32/m5stack_coreink/variant.h similarity index 100% rename from variants/m5stack_coreink/variant.h rename to variants/esp32/m5stack_coreink/variant.h diff --git a/variants/nano-g1-explorer/platformio.ini b/variants/esp32/nano-g1-explorer/platformio.ini similarity index 65% rename from variants/nano-g1-explorer/platformio.ini rename to variants/esp32/nano-g1-explorer/platformio.ini index 22037cbc9..2ba1f49e9 100644 --- a/variants/nano-g1-explorer/platformio.ini +++ b/variants/esp32/nano-g1-explorer/platformio.ini @@ -5,4 +5,6 @@ board = ttgo-t-beam lib_deps = ${esp32_base.lib_deps} build_flags = - ${esp32_base.build_flags} -D NANO_G1_EXPLORER -I variants/nano-g1-explorer \ No newline at end of file + ${esp32_base.build_flags} + -D NANO_G1_EXPLORER + -I variants/esp32/nano-g1-explorer diff --git a/variants/nano-g1-explorer/variant.h b/variants/esp32/nano-g1-explorer/variant.h similarity index 100% rename from variants/nano-g1-explorer/variant.h rename to variants/esp32/nano-g1-explorer/variant.h diff --git a/variants/nano-g1/platformio.ini b/variants/esp32/nano-g1/platformio.ini similarity index 67% rename from variants/nano-g1/platformio.ini rename to variants/esp32/nano-g1/platformio.ini index a3107423e..be8227de2 100644 --- a/variants/nano-g1/platformio.ini +++ b/variants/esp32/nano-g1/platformio.ini @@ -5,4 +5,6 @@ board = ttgo-t-beam lib_deps = ${esp32_base.lib_deps} build_flags = - ${esp32_base.build_flags} -D NANO_G1 -I variants/nano-g1 \ No newline at end of file + ${esp32_base.build_flags} + -D NANO_G1 + -I variants/esp32/nano-g1 diff --git a/variants/nano-g1/variant.h b/variants/esp32/nano-g1/variant.h similarity index 100% rename from variants/nano-g1/variant.h rename to variants/esp32/nano-g1/variant.h diff --git a/variants/radiomaster_900_bandit/platformio.ini b/variants/esp32/radiomaster_900_bandit/platformio.ini similarity index 90% rename from variants/radiomaster_900_bandit/platformio.ini rename to variants/esp32/radiomaster_900_bandit/platformio.ini index f87025937..d9eb78a57 100644 --- a/variants/radiomaster_900_bandit/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit/platformio.ini @@ -8,7 +8,7 @@ build_flags = -DCONFIG_DISABLE_HAL_LOCKS=1 -DHAS_STK8XXX=1 -O2 - -Ivariants/radiomaster_900_bandit + -I variants/esp32/radiomaster_900_bandit board_build.f_cpu = 240000000L upload_protocol = esptool lib_deps = diff --git a/variants/radiomaster_900_bandit/variant.h b/variants/esp32/radiomaster_900_bandit/variant.h similarity index 100% rename from variants/radiomaster_900_bandit/variant.h rename to variants/esp32/radiomaster_900_bandit/variant.h diff --git a/variants/radiomaster_900_bandit_micro/platformio.ini b/variants/esp32/radiomaster_900_bandit_micro/platformio.ini similarity index 87% rename from variants/radiomaster_900_bandit_micro/platformio.ini rename to variants/esp32/radiomaster_900_bandit_micro/platformio.ini index 9e54f5859..36a45787b 100644 --- a/variants/radiomaster_900_bandit_micro/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit_micro/platformio.ini @@ -12,8 +12,8 @@ build_flags = -DVTABLES_IN_FLASH=1 -DCONFIG_DISABLE_HAL_LOCKS=1 -O2 - -Ivariants/radiomaster_900_bandit_nano + -I variants/esp32/radiomaster_900_bandit_nano board_build.f_cpu = 240000000L upload_protocol = esptool lib_deps = - ${esp32_base.lib_deps} \ No newline at end of file + ${esp32_base.lib_deps} diff --git a/variants/radiomaster_900_bandit_nano/platformio.ini b/variants/esp32/radiomaster_900_bandit_nano/platformio.ini similarity index 79% rename from variants/radiomaster_900_bandit_nano/platformio.ini rename to variants/esp32/radiomaster_900_bandit_nano/platformio.ini index 0d43b8665..9a7fad83b 100644 --- a/variants/radiomaster_900_bandit_nano/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit_nano/platformio.ini @@ -7,8 +7,8 @@ build_flags = -DVTABLES_IN_FLASH=1 -DCONFIG_DISABLE_HAL_LOCKS=1 -O2 - -Ivariants/radiomaster_900_bandit_nano + -I variants/esp32/radiomaster_900_bandit_nano board_build.f_cpu = 240000000L upload_protocol = esptool lib_deps = - ${esp32_base.lib_deps} \ No newline at end of file + ${esp32_base.lib_deps} diff --git a/variants/radiomaster_900_bandit_nano/variant.h b/variants/esp32/radiomaster_900_bandit_nano/variant.h similarity index 100% rename from variants/radiomaster_900_bandit_nano/variant.h rename to variants/esp32/radiomaster_900_bandit_nano/variant.h diff --git a/variants/rak11200/pins_arduino.h b/variants/esp32/rak11200/pins_arduino.h similarity index 100% rename from variants/rak11200/pins_arduino.h rename to variants/esp32/rak11200/pins_arduino.h diff --git a/variants/esp32/rak11200/platformio.ini b/variants/esp32/rak11200/platformio.ini new file mode 100644 index 000000000..170e80b41 --- /dev/null +++ b/variants/esp32/rak11200/platformio.ini @@ -0,0 +1,10 @@ +[env:rak11200] +extends = esp32_base +board = wiscore_rak11200 +board_level = pr +board_check = true +build_flags = + ${esp32_base.build_flags} + -D RAK_11200 + -I variants/esp32/rak11200 +upload_speed = 115200 diff --git a/variants/rak11200/variant.h b/variants/esp32/rak11200/variant.h similarity index 100% rename from variants/rak11200/variant.h rename to variants/esp32/rak11200/variant.h diff --git a/variants/station-g1/platformio.ini b/variants/esp32/station-g1/platformio.ini similarity index 66% rename from variants/station-g1/platformio.ini rename to variants/esp32/station-g1/platformio.ini index a466414d0..693a41ae8 100644 --- a/variants/station-g1/platformio.ini +++ b/variants/esp32/station-g1/platformio.ini @@ -5,4 +5,6 @@ board = ttgo-t-beam lib_deps = ${esp32_base.lib_deps} build_flags = - ${esp32_base.build_flags} -D STATION_G1 -I variants/station-g1 \ No newline at end of file + ${esp32_base.build_flags} + -D STATION_G1 + -I variants/esp32/station-g1 diff --git a/variants/station-g1/variant.h b/variants/esp32/station-g1/variant.h similarity index 100% rename from variants/station-g1/variant.h rename to variants/esp32/station-g1/variant.h diff --git a/variants/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini similarity index 75% rename from variants/tbeam/platformio.ini rename to variants/esp32/tbeam/platformio.ini index 9049836a3..ea17751c6 100644 --- a/variants/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -2,12 +2,15 @@ [env:tbeam] 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} -D TBEAM_V10 -I variants/tbeam + ${esp32_base.build_flags} + -D TBEAM_V10 + -I variants/esp32/tbeam -DGPS_POWER_TOGGLE ; comment this line to disable double press function on the user button to turn off gps entirely. -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -upload_speed = 921600 \ No newline at end of file +upload_speed = 921600 diff --git a/variants/tbeam/variant.h b/variants/esp32/tbeam/variant.h similarity index 100% rename from variants/tbeam/variant.h rename to variants/esp32/tbeam/variant.h diff --git a/variants/tbeam_v07/platformio.ini b/variants/esp32/tbeam_v07/platformio.ini similarity index 69% rename from variants/tbeam_v07/platformio.ini rename to variants/esp32/tbeam_v07/platformio.ini index 0cba92400..1647d9fd7 100644 --- a/variants/tbeam_v07/platformio.ini +++ b/variants/esp32/tbeam_v07/platformio.ini @@ -4,4 +4,6 @@ board_level = extra extends = esp32_base board = ttgo-t-beam build_flags = - ${esp32_base.build_flags} -D TBEAM_V07 -I variants/tbeam_v07 \ No newline at end of file + ${esp32_base.build_flags} + -D TBEAM_V07 + -I variants/esp32/tbeam_v07 diff --git a/variants/tbeam_v07/variant.h b/variants/esp32/tbeam_v07/variant.h similarity index 100% rename from variants/tbeam_v07/variant.h rename to variants/esp32/tbeam_v07/variant.h diff --git a/variants/tlora_v1/platformio.ini b/variants/esp32/tlora_v1/platformio.ini similarity index 50% rename from variants/tlora_v1/platformio.ini rename to variants/esp32/tlora_v1/platformio.ini index 17fc71d72..1d879b6b0 100644 --- a/variants/tlora_v1/platformio.ini +++ b/variants/esp32/tlora_v1/platformio.ini @@ -3,5 +3,7 @@ board_level = extra extends = esp32_base board = ttgo-lora32-v1 build_flags = - ${esp32_base.build_flags} -D TLORA_V1 -I variants/tlora_v1 -upload_speed = 115200 \ No newline at end of file + ${esp32_base.build_flags} + -D TLORA_V1 + -I variants/esp32/tlora_v1 +upload_speed = 115200 diff --git a/variants/tlora_v1/variant.h b/variants/esp32/tlora_v1/variant.h similarity index 100% rename from variants/tlora_v1/variant.h rename to variants/esp32/tlora_v1/variant.h diff --git a/variants/tlora_v1_3/platformio.ini b/variants/esp32/tlora_v1_3/platformio.ini similarity index 50% rename from variants/tlora_v1_3/platformio.ini rename to variants/esp32/tlora_v1_3/platformio.ini index c5eca589f..523c38a80 100644 --- a/variants/tlora_v1_3/platformio.ini +++ b/variants/esp32/tlora_v1_3/platformio.ini @@ -3,5 +3,5 @@ board_level = extra extends = esp32_base board = ttgo-lora32-v1 build_flags = - ${esp32_base.build_flags} -D TLORA_V1_3 -I variants/tlora_v1_3 -upload_speed = 115200 \ No newline at end of file + ${esp32_base.build_flags} -D TLORA_V1_3 -I variants/esp32/tlora_v1_3 +upload_speed = 115200 diff --git a/variants/tlora_v1_3/variant.h b/variants/esp32/tlora_v1_3/variant.h similarity index 100% rename from variants/tlora_v1_3/variant.h rename to variants/esp32/tlora_v1_3/variant.h diff --git a/variants/tlora_v2/platformio.ini b/variants/esp32/tlora_v2/platformio.ini similarity index 56% rename from variants/tlora_v2/platformio.ini rename to variants/esp32/tlora_v2/platformio.ini index 8087a30e3..4a710ee34 100644 --- a/variants/tlora_v2/platformio.ini +++ b/variants/esp32/tlora_v2/platformio.ini @@ -3,4 +3,6 @@ board_level = extra extends = esp32_base board = ttgo-lora32-v1 build_flags = - ${esp32_base.build_flags} -D TLORA_V2 -I variants/tlora_v2 \ No newline at end of file + ${esp32_base.build_flags} + -D TLORA_V2 + -I variants/esp32/tlora_v2 diff --git a/variants/tlora_v2/variant.h b/variants/esp32/tlora_v2/variant.h similarity index 100% rename from variants/tlora_v2/variant.h rename to variants/esp32/tlora_v2/variant.h diff --git a/variants/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini similarity index 68% rename from variants/tlora_v2_1_16/platformio.ini rename to variants/esp32/tlora_v2_1_16/platformio.ini index 4253cc6af..bd85aa847 100644 --- a/variants/tlora_v2_1_16/platformio.ini +++ b/variants/esp32/tlora_v2_1_16/platformio.ini @@ -3,6 +3,6 @@ extends = esp32_base board = ttgo-lora32-v21 board_check = true build_flags = - ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/tlora_v2_1_16 + ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -upload_speed = 115200 \ No newline at end of file +upload_speed = 115200 diff --git a/variants/tlora_v2_1_16/variant.h b/variants/esp32/tlora_v2_1_16/variant.h similarity index 100% rename from variants/tlora_v2_1_16/variant.h rename to variants/esp32/tlora_v2_1_16/variant.h diff --git a/variants/tlora_v2_1_16_tcxo/platformio.ini b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini similarity index 90% rename from variants/tlora_v2_1_16_tcxo/platformio.ini rename to variants/esp32/tlora_v2_1_16_tcxo/platformio.ini index 5c7cb7eb3..9404faa02 100644 --- a/variants/tlora_v2_1_16_tcxo/platformio.ini +++ b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini @@ -5,7 +5,7 @@ board = ttgo-lora32-v21 build_flags = ${esp32_base.build_flags} -D TLORA_V2_1_16 - -I variants/tlora_v2_1_16 + -I variants/esp32/tlora_v2_1_16 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D LORA_TCXO_GPIO=33 upload_speed = 115200 \ No newline at end of file diff --git a/variants/tlora_v2_1_18/platformio.ini b/variants/esp32/tlora_v2_1_18/platformio.ini similarity index 55% rename from variants/tlora_v2_1_18/platformio.ini rename to variants/esp32/tlora_v2_1_18/platformio.ini index 48a001ced..432117485 100644 --- a/variants/tlora_v2_1_18/platformio.ini +++ b/variants/esp32/tlora_v2_1_18/platformio.ini @@ -4,4 +4,6 @@ board_level = extra board = ttgo-lora32-v21 build_flags = - ${esp32_base.build_flags} -D TLORA_V2_1_18 -I variants/tlora_v2_1_18 \ No newline at end of file + ${esp32_base.build_flags} + -D TLORA_V2_1_18 + -I variants/esp32/tlora_v2_1_18 diff --git a/variants/tlora_v2_1_18/variant.h b/variants/esp32/tlora_v2_1_18/variant.h similarity index 100% rename from variants/tlora_v2_1_18/variant.h rename to variants/esp32/tlora_v2_1_18/variant.h diff --git a/variants/tlora_v3_3_0_tcxo/platformio.ini b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini similarity index 89% rename from variants/tlora_v3_3_0_tcxo/platformio.ini rename to variants/esp32/tlora_v3_3_0_tcxo/platformio.ini index 8d060a087..f1110386e 100644 --- a/variants/tlora_v3_3_0_tcxo/platformio.ini +++ b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini @@ -4,7 +4,7 @@ board = ttgo-lora32-v21 build_flags = ${esp32_base.build_flags} -D TLORA_V2_1_16 - -I variants/tlora_v2_1_16 + -I variants/esp32/tlora_v2_1_16 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D LORA_TCXO_GPIO=12 -D BUTTON_PIN=0 \ No newline at end of file diff --git a/variants/trackerd/platformio.ini b/variants/esp32/trackerd/platformio.ini similarity index 56% rename from variants/trackerd/platformio.ini rename to variants/esp32/trackerd/platformio.ini index 654534a15..3c2726a3c 100644 --- a/variants/trackerd/platformio.ini +++ b/variants/esp32/trackerd/platformio.ini @@ -4,5 +4,5 @@ board = pico32 board_build.f_flash = 80000000L build_flags = - ${esp32_base.build_flags} -D PRIVATE_HW -I variants/trackerd -D BSFILE=\"boards/dragino_lbt2.h\" + ${esp32_base.build_flags} -D PRIVATE_HW -I variants/esp32/trackerd -D BSFILE=\"boards/dragino_lbt2.h\" ;board_build.partitions = no_ota.csv \ No newline at end of file diff --git a/variants/trackerd/variant.h b/variants/esp32/trackerd/variant.h similarity index 100% rename from variants/trackerd/variant.h rename to variants/esp32/trackerd/variant.h diff --git a/variants/wiphone/pins_arduino.h b/variants/esp32/wiphone/pins_arduino.h similarity index 100% rename from variants/wiphone/pins_arduino.h rename to variants/esp32/wiphone/pins_arduino.h diff --git a/variants/wiphone/platformio.ini b/variants/esp32/wiphone/platformio.ini similarity index 81% rename from variants/wiphone/platformio.ini rename to variants/esp32/wiphone/platformio.ini index 362102731..5cce94b13 100644 --- a/variants/wiphone/platformio.ini +++ b/variants/esp32/wiphone/platformio.ini @@ -5,7 +5,9 @@ board_level = extra monitor_filters = esp32_exception_decoder board_build.partitions = default_16MB.csv build_flags = - ${esp32_base.build_flags} -D WIPHONE -I variants/wiphone + ${esp32_base.build_flags} + -D WIPHONE + -I variants/esp32/wiphone lib_deps = ${esp32_base.lib_deps} lovyan03/LovyanGFX@^1.2.0 diff --git a/variants/wiphone/variant.h b/variants/esp32/wiphone/variant.h similarity index 100% rename from variants/wiphone/variant.h rename to variants/esp32/wiphone/variant.h diff --git a/variants/ai-c3/platformio.ini b/variants/esp32c3/ai-c3/platformio.ini similarity index 57% rename from variants/ai-c3/platformio.ini rename to variants/esp32c3/ai-c3/platformio.ini index 2869ca580..a25c0cb19 100644 --- a/variants/ai-c3/platformio.ini +++ b/variants/esp32c3/ai-c3/platformio.ini @@ -2,7 +2,7 @@ extends = esp32c3_base board = esp32-c3-devkitm-1 board_level = extra -build_flags = ${esp32c3_base.build_flags} +build_flags = + ${esp32c3_base.build_flags} -D PRIVATE_HW - -I variants/ai-c3 - + -I variants/esp32c3/ai-c3 diff --git a/variants/ai-c3/variant.h b/variants/esp32c3/ai-c3/variant.h similarity index 100% rename from variants/ai-c3/variant.h rename to variants/esp32c3/ai-c3/variant.h diff --git a/variants/esp32c3/diy/esp32c3_super_mini/pins_arduino.h b/variants/esp32c3/diy/esp32c3_super_mini/pins_arduino.h new file mode 100644 index 000000000..a325b81eb --- /dev/null +++ b/variants/esp32c3/diy/esp32c3_super_mini/pins_arduino.h @@ -0,0 +1,24 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t TX = 21; +static const uint8_t RX = 20; + +static const uint8_t SDA = 1; +static const uint8_t SCL = 0; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 7; +static const uint8_t MISO = 6; +static const uint8_t SCK = 10; + +static const uint8_t A0 = 0; +static const uint8_t A1 = 1; +static const uint8_t A2 = 2; +static const uint8_t A3 = 3; +static const uint8_t A4 = 4; +static const uint8_t A5 = 5; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32c3/diy/esp32c3_super_mini/platformio.ini b/variants/esp32c3/diy/esp32c3_super_mini/platformio.ini new file mode 100644 index 000000000..c87baa7bf --- /dev/null +++ b/variants/esp32c3/diy/esp32c3_super_mini/platformio.ini @@ -0,0 +1,12 @@ +; ESP32 C3 Super Mini Development Board +; https://www.espboards.dev/esp32/esp32-c3-super-mini/ +[env:esp32c3_super_mini] +extends = esp32c3_base +board = esp32-c3-devkitm-1 +build_flags = + ${esp32_base.build_flags} + -D PRIVATE_HW + -I variants/esp32c3/diy/esp32c3_super_mini + -D ARDUINO_USB_MODE=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 +board_level = extra diff --git a/variants/esp32c3/diy/esp32c3_super_mini/variant.h b/variants/esp32c3/diy/esp32c3_super_mini/variant.h new file mode 100644 index 000000000..48c275912 --- /dev/null +++ b/variants/esp32c3/diy/esp32c3_super_mini/variant.h @@ -0,0 +1,61 @@ +#ifndef _VARIANT_ESP32C3_SUPER_MINI_ +#define _VARIANT_ESP32C3_SUPER_MINI_ + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// I2C (Wire) & OLED +#define WIRE_INTERFACES_COUNT (1) +#define I2C_SDA (1) +#define I2C_SCL (0) + +#define USE_SSD1306 + +// GPS +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN (20) +#define GPS_TX_PIN (21) + +// Button +#define BUTTON_PIN (9) // BOOT button + +// LoRa +#define USE_LLCC68 +#define USE_SX1262 +// #define USE_RF95 +#define USE_SX1268 + +#define LORA_DIO0 RADIOLIB_NC +#define LORA_RESET (5) +#define LORA_DIO1 (3) +#define LORA_RXEN (2) +#define LORA_BUSY (4) +#define LORA_SCK (10) +#define LORA_MISO (6) +#define LORA_MOSI (7) +#define LORA_CS (8) + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_BUSY +#define SX126X_RESET LORA_RESET +#define SX126X_RXEN LORA_RXEN + +#define SX126X_DIO3_TCXO_VOLTAGE (1.8) +#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif diff --git a/variants/hackerboxes_esp32c3_oled/platformio.ini b/variants/esp32c3/hackerboxes_esp32c3_oled/platformio.ini similarity index 80% rename from variants/hackerboxes_esp32c3_oled/platformio.ini rename to variants/esp32c3/hackerboxes_esp32c3_oled/platformio.ini index 4fcbf2ade..5a72b9d74 100644 --- a/variants/hackerboxes_esp32c3_oled/platformio.ini +++ b/variants/esp32c3/hackerboxes_esp32c3_oled/platformio.ini @@ -7,8 +7,8 @@ build_flags = -D PRIVATE_HW -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1 - -I variants/hackerboxes_esp32c3_oled + -I variants/esp32c3/hackerboxes_esp32c3_oled monitor_speed = 115200 upload_protocol = esptool ;upload_port = /dev/ttyUSB0 -upload_speed = 921600 \ No newline at end of file +upload_speed = 921600 diff --git a/variants/hackerboxes_esp32c3_oled/variant.h b/variants/esp32c3/hackerboxes_esp32c3_oled/variant.h similarity index 100% rename from variants/hackerboxes_esp32c3_oled/variant.h rename to variants/esp32c3/hackerboxes_esp32c3_oled/variant.h diff --git a/variants/heltec_esp32c3/pins_arduino.h b/variants/esp32c3/heltec_esp32c3/pins_arduino.h similarity index 100% rename from variants/heltec_esp32c3/pins_arduino.h rename to variants/esp32c3/heltec_esp32c3/pins_arduino.h diff --git a/variants/heltec_esp32c3/platformio.ini b/variants/esp32c3/heltec_esp32c3/platformio.ini similarity index 74% rename from variants/heltec_esp32c3/platformio.ini rename to variants/esp32c3/heltec_esp32c3/platformio.ini index 6fe5c3c69..705e2e996 100644 --- a/variants/heltec_esp32c3/platformio.ini +++ b/variants/esp32c3/heltec_esp32c3/platformio.ini @@ -1,11 +1,12 @@ [env:heltec-ht62-esp32c3-sx1262] extends = esp32c3_base board = esp32-c3-devkitm-1 +board_level = pr build_flags = ${esp32_base.build_flags} -D HELTEC_HT62 - -I variants/heltec_esp32c3 + -I variants/esp32c3/heltec_esp32c3 monitor_speed = 115200 upload_protocol = esptool ;upload_port = /dev/ttyUSB0 -upload_speed = 921600 \ No newline at end of file +upload_speed = 921600 diff --git a/variants/heltec_esp32c3/variant.h b/variants/esp32c3/heltec_esp32c3/variant.h similarity index 100% rename from variants/heltec_esp32c3/variant.h rename to variants/esp32c3/heltec_esp32c3/variant.h diff --git a/variants/heltec_hru_3601/pins_arduino.h b/variants/esp32c3/heltec_hru_3601/pins_arduino.h similarity index 100% rename from variants/heltec_hru_3601/pins_arduino.h rename to variants/esp32c3/heltec_hru_3601/pins_arduino.h diff --git a/variants/heltec_hru_3601/platformio.ini b/variants/esp32c3/heltec_hru_3601/platformio.ini similarity index 84% rename from variants/heltec_hru_3601/platformio.ini rename to variants/esp32c3/heltec_hru_3601/platformio.ini index 3668e72b7..b5ff63eae 100644 --- a/variants/heltec_hru_3601/platformio.ini +++ b/variants/esp32c3/heltec_hru_3601/platformio.ini @@ -4,6 +4,6 @@ board = adafruit_qtpy_esp32c3 build_flags = ${esp32_base.build_flags} -D HELTEC_HRU_3601 - -I variants/heltec_hru_3601 + -I variants/esp32c3/heltec_hru_3601 lib_deps = ${esp32c3_base.lib_deps} adafruit/Adafruit NeoPixel @ ^1.12.0 diff --git a/variants/heltec_hru_3601/variant.h b/variants/esp32c3/heltec_hru_3601/variant.h similarity index 100% rename from variants/heltec_hru_3601/variant.h rename to variants/esp32c3/heltec_hru_3601/variant.h diff --git a/variants/m5stack-stamp-c3/pins_arduino.h b/variants/esp32c3/m5stack-stamp-c3/pins_arduino.h similarity index 100% rename from variants/m5stack-stamp-c3/pins_arduino.h rename to variants/esp32c3/m5stack-stamp-c3/pins_arduino.h diff --git a/variants/m5stack-stamp-c3/platformio.ini b/variants/esp32c3/m5stack-stamp-c3/platformio.ini similarity index 86% rename from variants/m5stack-stamp-c3/platformio.ini rename to variants/esp32c3/m5stack-stamp-c3/platformio.ini index bab65b621..1072df664 100644 --- a/variants/m5stack-stamp-c3/platformio.ini +++ b/variants/esp32c3/m5stack-stamp-c3/platformio.ini @@ -5,7 +5,7 @@ board_level = extra build_flags = ${esp32_base.build_flags} -D PRIVATE_HW - -I variants/m5stack-stamp-c3 + -I variants/esp32c3/m5stack-stamp-c3 monitor_speed = 115200 upload_protocol = esptool ;upload_port = /dev/ttyACM2 diff --git a/variants/m5stack-stamp-c3/variant.h b/variants/esp32c3/m5stack-stamp-c3/variant.h similarity index 100% rename from variants/m5stack-stamp-c3/variant.h rename to variants/esp32c3/m5stack-stamp-c3/variant.h diff --git a/variants/tlora_c6/platformio.ini b/variants/esp32c6/tlora_c6/platformio.ini similarity index 78% rename from variants/tlora_c6/platformio.ini rename to variants/esp32c6/tlora_c6/platformio.ini index 2da10138a..6b402d7c5 100644 --- a/variants/tlora_c6/platformio.ini +++ b/variants/esp32c6/tlora_c6/platformio.ini @@ -1,9 +1,10 @@ [env:tlora-c6] extends = esp32c6_base board = esp32-c6-devkitm-1 +board_level = pr build_flags = ${esp32c6_base.build_flags} -D TLORA_C6 - -I variants/tlora_c6 + -I variants/esp32c6/tlora_c6 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 diff --git a/variants/tlora_c6/variant.h b/variants/esp32c6/tlora_c6/variant.h similarity index 100% rename from variants/tlora_c6/variant.h rename to variants/esp32c6/tlora_c6/variant.h diff --git a/variants/nugget_s2_lora/platformio.ini b/variants/esp32s2/nugget_s2_lora/platformio.ini similarity index 54% rename from variants/nugget_s2_lora/platformio.ini rename to variants/esp32s2/nugget_s2_lora/platformio.ini index 2a7ff1013..a091a705f 100644 --- a/variants/nugget_s2_lora/platformio.ini +++ b/variants/esp32s2/nugget_s2_lora/platformio.ini @@ -3,4 +3,6 @@ extends = esp32s2_base board = lolin_s2_mini board_level = extra build_flags = - ${esp32s2_base.build_flags} -D PRIVATE_HW -I variants/nugget_s2_lora \ No newline at end of file + ${esp32s2_base.build_flags} + -D PRIVATE_HW + -I variants/esp32s2/nugget_s2_lora diff --git a/variants/nugget_s2_lora/variant.h b/variants/esp32s2/nugget_s2_lora/variant.h similarity index 100% rename from variants/nugget_s2_lora/variant.h rename to variants/esp32s2/nugget_s2_lora/variant.h diff --git a/variants/CDEBYTE_EoRa-S3/pins_arduino.h b/variants/esp32s3/CDEBYTE_EoRa-S3/pins_arduino.h similarity index 100% rename from variants/CDEBYTE_EoRa-S3/pins_arduino.h rename to variants/esp32s3/CDEBYTE_EoRa-S3/pins_arduino.h diff --git a/variants/CDEBYTE_EoRa-S3/platformio.ini b/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini similarity index 69% rename from variants/CDEBYTE_EoRa-S3/platformio.ini rename to variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini index a1642ff97..dbd420f04 100644 --- a/variants/CDEBYTE_EoRa-S3/platformio.ini +++ b/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini @@ -4,5 +4,5 @@ board = CDEBYTE_EoRa-S3 build_flags = ${esp32s3_base.build_flags} -D CDEBYTE_EORA_S3 - -I variants/CDEBYTE_EoRa-S3 - -D GPS_POWER_TOGGLE \ No newline at end of file + -I variants/esp32s3/CDEBYTE_EoRa-S3 + -D GPS_POWER_TOGGLE diff --git a/variants/CDEBYTE_EoRa-S3/variant.h b/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h similarity index 100% rename from variants/CDEBYTE_EoRa-S3/variant.h rename to variants/esp32s3/CDEBYTE_EoRa-S3/variant.h diff --git a/variants/EBYTE_ESP32-S3/pins_arduino.h b/variants/esp32s3/EBYTE_ESP32-S3/pins_arduino.h similarity index 100% rename from variants/EBYTE_ESP32-S3/pins_arduino.h rename to variants/esp32s3/EBYTE_ESP32-S3/pins_arduino.h diff --git a/variants/EBYTE_ESP32-S3/platformio.ini b/variants/esp32s3/EBYTE_ESP32-S3/platformio.ini similarity index 86% rename from variants/EBYTE_ESP32-S3/platformio.ini rename to variants/esp32s3/EBYTE_ESP32-S3/platformio.ini index 10de91386..507a19588 100644 --- a/variants/EBYTE_ESP32-S3/platformio.ini +++ b/variants/esp32s3/EBYTE_ESP32-S3/platformio.ini @@ -6,4 +6,4 @@ board_level = extra build_flags = ${esp32s3_base.build_flags} -D EBYTE_ESP32_S3 - -I variants/EBYTE_ESP32-S3 + -I variants/esp32s3/EBYTE_ESP32-S3 diff --git a/variants/EBYTE_ESP32-S3/variant.h b/variants/esp32s3/EBYTE_ESP32-S3/variant.h similarity index 100% rename from variants/EBYTE_ESP32-S3/variant.h rename to variants/esp32s3/EBYTE_ESP32-S3/variant.h diff --git a/variants/ELECROW-ThinkNode-M2/pins_arduino.h b/variants/esp32s3/ELECROW-ThinkNode-M2/pins_arduino.h similarity index 100% rename from variants/ELECROW-ThinkNode-M2/pins_arduino.h rename to variants/esp32s3/ELECROW-ThinkNode-M2/pins_arduino.h diff --git a/variants/ELECROW-ThinkNode-M2/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M2/platformio.ini similarity index 76% rename from variants/ELECROW-ThinkNode-M2/platformio.ini rename to variants/esp32s3/ELECROW-ThinkNode-M2/platformio.ini index c08c94a71..01e82184b 100644 --- a/variants/ELECROW-ThinkNode-M2/platformio.ini +++ b/variants/esp32s3/ELECROW-ThinkNode-M2/platformio.ini @@ -4,4 +4,4 @@ board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32s3_base.build_flags} -D ELECROW_ThinkNode_M2 - -I variants/ELECROW-ThinkNode-M2 + -I variants/esp32s3/ELECROW-ThinkNode-M2 diff --git a/variants/ELECROW-ThinkNode-M2/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h similarity index 100% rename from variants/ELECROW-ThinkNode-M2/variant.h rename to variants/esp32s3/ELECROW-ThinkNode-M2/variant.h diff --git a/variants/bpi_picow_esp32_s3/pins_arduino.h b/variants/esp32s3/bpi_picow_esp32_s3/pins_arduino.h similarity index 100% rename from variants/bpi_picow_esp32_s3/pins_arduino.h rename to variants/esp32s3/bpi_picow_esp32_s3/pins_arduino.h diff --git a/variants/bpi_picow_esp32_s3/platformio.ini b/variants/esp32s3/bpi_picow_esp32_s3/platformio.ini similarity index 77% rename from variants/bpi_picow_esp32_s3/platformio.ini rename to variants/esp32s3/bpi_picow_esp32_s3/platformio.ini index 7e94cc97e..57af0da82 100644 --- a/variants/bpi_picow_esp32_s3/platformio.ini +++ b/variants/esp32s3/bpi_picow_esp32_s3/platformio.ini @@ -11,4 +11,6 @@ lib_deps = ${esp32_base.lib_deps} caveman99/ESP32 Codec2@^1.0.1 build_flags = - ${esp32_base.build_flags} -D PRIVATE_HW -I variants/bpi_picow_esp32_s3 \ No newline at end of file + ${esp32_base.build_flags} + -D PRIVATE_HW + -I variants/esp32s3/bpi_picow_esp32_s3 diff --git a/variants/bpi_picow_esp32_s3/variant.h b/variants/esp32s3/bpi_picow_esp32_s3/variant.h similarity index 100% rename from variants/bpi_picow_esp32_s3/variant.h rename to variants/esp32s3/bpi_picow_esp32_s3/variant.h diff --git a/variants/crowpanel-esp32s3-5-epaper/pins_arduino.h b/variants/esp32s3/crowpanel-esp32s3-5-epaper/pins_arduino.h similarity index 100% rename from variants/crowpanel-esp32s3-5-epaper/pins_arduino.h rename to variants/esp32s3/crowpanel-esp32s3-5-epaper/pins_arduino.h diff --git a/variants/crowpanel-esp32s3-5-epaper/platformio.ini b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini similarity index 89% rename from variants/crowpanel-esp32s3-5-epaper/platformio.ini rename to variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini index ebf013f64..49e84bf4f 100644 --- a/variants/crowpanel-esp32s3-5-epaper/platformio.ini +++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini @@ -11,7 +11,9 @@ board = esp32-s3-devkitc-1 board_level = extra upload_protocol = esptool build_flags = - ${esp32s3_base.build_flags} -D CROWPANEL_ESP32S3_5_EPAPER -I variants/crowpanel-esp32s3-5-epaper + ${esp32s3_base.build_flags} + -D CROWPANEL_ESP32S3_5_EPAPER + -I variants/esp32s3/crowpanel-esp32s3-5-epaper -D PRIVATE_HW -DBOARD_HAS_PSRAM -DGPS_POWER_TOGGLE @@ -39,7 +41,9 @@ board = esp32-s3-devkitc-1 board_level = extra upload_protocol = esptool build_flags = - ${esp32s3_base.build_flags} -D CROWPANEL_ESP32S3_4_EPAPER -I variants/crowpanel-esp32s3-5-epaper + ${esp32s3_base.build_flags} + -D CROWPANEL_ESP32S3_4_EPAPER + -I variants/esp32s3/crowpanel-esp32s3-5-epaper -D PRIVATE_HW -DBOARD_HAS_PSRAM -DGPS_POWER_TOGGLE @@ -67,7 +71,9 @@ board = esp32-s3-devkitc-1 board_level = extra upload_protocol = esptool build_flags = - ${esp32s3_base.build_flags} -D CROWPANEL_ESP32S3_2_EPAPER -I variants/crowpanel-esp32s3-5-epaper + ${esp32s3_base.build_flags} + -D CROWPANEL_ESP32S3_2_EPAPER + -I variants/esp32s3/crowpanel-esp32s3-5-epaper -D PRIVATE_HW -DBOARD_HAS_PSRAM -DGPS_POWER_TOGGLE diff --git a/variants/crowpanel-esp32s3-5-epaper/variant.h b/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h similarity index 100% rename from variants/crowpanel-esp32s3-5-epaper/variant.h rename to variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h diff --git a/variants/my_esp32s3_diy_eink/pins_arduino.h b/variants/esp32s3/diy/my_esp32s3_diy_eink/pins_arduino.h similarity index 100% rename from variants/my_esp32s3_diy_eink/pins_arduino.h rename to variants/esp32s3/diy/my_esp32s3_diy_eink/pins_arduino.h diff --git a/variants/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini similarity index 71% rename from variants/my_esp32s3_diy_eink/platformio.ini rename to variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini index 98613e4fb..267544c40 100644 --- a/variants/my_esp32s3_diy_eink/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini @@ -8,8 +8,6 @@ board_build.f_cpu = 240000000L upload_protocol = esptool ;upload_port = /dev/ttyACM1 upload_speed = 921600 -platform_packages = - platformio/tool-esptoolpy@^1.40801.0 lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2@^1.6.2 @@ -18,12 +16,13 @@ build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 build_flags = - ;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_eink - ${esp32_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_eink + ${esp32_base.build_flags} + -D PRIVATE_HW + -I variants/esp32s3/diy/my_esp32s3_diy_eink -Dmy -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D -DEINK_WIDTH=296 -DEINK_HEIGHT=128 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue - -DARDUINO_USB_MODE=0 \ No newline at end of file + -DARDUINO_USB_MODE=0 diff --git a/variants/my_esp32s3_diy_eink/variant.h b/variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h similarity index 100% rename from variants/my_esp32s3_diy_eink/variant.h rename to variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h diff --git a/variants/my_esp32s3_diy_oled/pins_arduino.h b/variants/esp32s3/diy/my_esp32s3_diy_oled/pins_arduino.h similarity index 100% rename from variants/my_esp32s3_diy_oled/pins_arduino.h rename to variants/esp32s3/diy/my_esp32s3_diy_oled/pins_arduino.h diff --git a/variants/my_esp32s3_diy_oled/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini similarity index 67% rename from variants/my_esp32s3_diy_oled/platformio.ini rename to variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini index 346cc9cac..aa3e6e482 100644 --- a/variants/my_esp32s3_diy_oled/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini @@ -8,8 +8,6 @@ board_build.f_cpu = 240000000L upload_protocol = esptool ;upload_port = /dev/ttyACM0 upload_speed = 921600 -platform_packages = - platformio/tool-esptoolpy@^1.40801.0 lib_deps = ${esp32_base.lib_deps} adafruit/Adafruit NeoPixel @ ^1.12.0 @@ -17,8 +15,9 @@ build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 build_flags = - ;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_oled - ${esp32_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_oled + ${esp32_base.build_flags} + -D PRIVATE_HW + -I variants/esp32s3/diy/my_esp32s3_diy_oled -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue - -DARDUINO_USB_MODE=0 \ No newline at end of file + -DARDUINO_USB_MODE=0 diff --git a/variants/my_esp32s3_diy_oled/variant.h b/variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h similarity index 100% rename from variants/my_esp32s3_diy_oled/variant.h rename to variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h diff --git a/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini b/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini new file mode 100644 index 000000000..681ee6c45 --- /dev/null +++ b/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini @@ -0,0 +1,18 @@ +; NanoVHF T-Energy-S3 + E22(0)-xxxM - DIY +[env:t-energy-s3_e22] +extends = esp32s3_base +board = esp32-s3-devkitc-1 +board_build.partitions = default_16MB.csv +board_level = extra +board_upload.flash_size = 16MB ;Specify the FLASH capacity as 16MB +board_build.arduino.memory_type = qio_opi ;Enable internal PSRAM +build_unflags = + ${esp32s3_base.build_unflags} + -D ARDUINO_USB_MODE=1 +build_flags = + ${esp32s3_base.build_flags} + -D EBYTE_ESP32_S3 + -D BOARD_HAS_PSRAM + -D ARDUINO_USB_MODE=0 + -D ARDUINO_USB_CDC_ON_BOOT=1 + -I variants/esp32s3/diy/t-energy-s3_e22 diff --git a/variants/diy/t-energy-s3_e22/variant.h b/variants/esp32s3/diy/t-energy-s3_e22/variant.h similarity index 100% rename from variants/diy/t-energy-s3_e22/variant.h rename to variants/esp32s3/diy/t-energy-s3_e22/variant.h diff --git a/variants/dreamcatcher/platformio.ini b/variants/esp32s3/dreamcatcher/platformio.ini similarity index 89% rename from variants/dreamcatcher/platformio.ini rename to variants/esp32s3/dreamcatcher/platformio.ini index 6527d89be..d088f2dac 100644 --- a/variants/dreamcatcher/platformio.ini +++ b/variants/esp32s3/dreamcatcher/platformio.ini @@ -8,7 +8,7 @@ build_flags = ${esp32s3_base.build_flags} -D PRIVATE_HW -D OTHERNET_DC_REV=2301 - -I variants/dreamcatcher + -I variants/esp32s3/dreamcatcher -D ARDUINO_USB_CDC_ON_BOOT=1 lib_deps = ${esp32s3_base.lib_deps} @@ -25,5 +25,5 @@ build_flags = ${esp32s3_base.build_flags} -D PRIVATE_HW -D OTHERNET_DC_REV=2206 - -I variants/dreamcatcher + -I variants/esp32s3/dreamcatcher -D ARDUINO_USB_CDC_ON_BOOT=1 diff --git a/variants/dreamcatcher/rfswitch.h b/variants/esp32s3/dreamcatcher/rfswitch.h similarity index 100% rename from variants/dreamcatcher/rfswitch.h rename to variants/esp32s3/dreamcatcher/rfswitch.h diff --git a/variants/dreamcatcher/variant.h b/variants/esp32s3/dreamcatcher/variant.h similarity index 100% rename from variants/dreamcatcher/variant.h rename to variants/esp32s3/dreamcatcher/variant.h diff --git a/variants/elecrow_panel/pins_arduino.h b/variants/esp32s3/elecrow_panel/pins_arduino.h similarity index 100% rename from variants/elecrow_panel/pins_arduino.h rename to variants/esp32s3/elecrow_panel/pins_arduino.h diff --git a/variants/elecrow_panel/platformio.ini b/variants/esp32s3/elecrow_panel/platformio.ini similarity index 95% rename from variants/elecrow_panel/platformio.ini rename to variants/esp32s3/elecrow_panel/platformio.ini index 5bce58208..59bc26000 100644 --- a/variants/elecrow_panel/platformio.ini +++ b/variants/esp32s3/elecrow_panel/platformio.ini @@ -5,7 +5,7 @@ board_check = true upload_protocol = esptool board_build.partitions = default_16MB.csv ; must be here for some reason, board.json is not enough !? build_flags = ${esp32s3_base.build_flags} -Os - -I variants/elecrow_panel + -I variants/esp32s3/elecrow_panel -D ELECROW_PANEL -D CONFIG_ARDUHAL_LOG_COLORS -D RADIOLIB_DEBUG_SPI=0 @@ -79,6 +79,7 @@ build_flags = -D SPI_FREQUENCY=80000000 -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 + -D DISPLAY_SIZE=320x240 ; landscape mode -D LGFX_PANEL=ST7789 -D LGFX_ROTATION=1 -D LGFX_CFG_HOST=SPI2_HOST @@ -97,12 +98,14 @@ build_flags = [env:elecrow-adv-35-tft] extends = crowpanel_small_esp32s3_base +board_level = pr build_flags = ${crowpanel_small_esp32s3_base.build_flags} -D LV_CACHE_DEF_SIZE=2097152 -D SPI_FREQUENCY=60000000 -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 + -D DISPLAY_SIZE=320x480 ; portrait mode -D LGFX_PANEL=ILI9488 -D LGFX_ROTATION=0 -D LGFX_CFG_HOST=SPI2_HOST @@ -126,3 +129,4 @@ extends = crowpanel_large_esp32s3_base build_flags = ${crowpanel_large_esp32s3_base.build_flags} -D VIEW_320x240 + -D DISPLAY_SIZE=800x480 ; landscape mode diff --git a/variants/elecrow_panel/variant.h b/variants/esp32s3/elecrow_panel/variant.h similarity index 100% rename from variants/elecrow_panel/variant.h rename to variants/esp32s3/elecrow_panel/variant.h diff --git a/variants/esp32-s3-pico/pins_arduino.h b/variants/esp32s3/esp32-s3-pico/pins_arduino.h similarity index 100% rename from variants/esp32-s3-pico/pins_arduino.h rename to variants/esp32s3/esp32-s3-pico/pins_arduino.h diff --git a/variants/esp32-s3-pico/platformio.ini b/variants/esp32s3/esp32-s3-pico/platformio.ini similarity index 94% rename from variants/esp32-s3-pico/platformio.ini rename to variants/esp32s3/esp32-s3-pico/platformio.ini index 69969c601..11bd4f5a3 100644 --- a/variants/esp32-s3-pico/platformio.ini +++ b/variants/esp32s3/esp32-s3-pico/platformio.ini @@ -15,7 +15,7 @@ board_upload.require_upload_port = yes build_flags = ${esp32s3_base.build_flags} -DESP32_S3_PICO ;-DPRIVATE_HW - -Ivariants/esp32-s3-pico + -Ivariants/esp32s3/esp32-s3-pico -DBOARD_HAS_PSRAM -DEINK_DISPLAY_MODEL=GxEPD2_290_T94_V2 -DEINK_WIDTH=296 diff --git a/variants/esp32-s3-pico/variant.h b/variants/esp32s3/esp32-s3-pico/variant.h similarity index 100% rename from variants/esp32-s3-pico/variant.h rename to variants/esp32s3/esp32-s3-pico/variant.h diff --git a/variants/heltec_capsule_sensor_v3/platformio.ini b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini similarity index 82% rename from variants/heltec_capsule_sensor_v3/platformio.ini rename to variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini index 8d1c039c1..d43ffd0df 100644 --- a/variants/heltec_capsule_sensor_v3/platformio.ini +++ b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini @@ -4,7 +4,7 @@ board = heltec_wifi_lora_32_V3 board_check = true board_build.partitions = default_8MB.csv build_flags = - ${esp32s3_base.build_flags} -I variants/heltec_capsule_sensor_v3 + ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_capsule_sensor_v3 -D HELTEC_CAPSULE_SENSOR_V3 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. - ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output \ No newline at end of file + ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output diff --git a/variants/heltec_capsule_sensor_v3/variant.h b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h similarity index 100% rename from variants/heltec_capsule_sensor_v3/variant.h rename to variants/esp32s3/heltec_capsule_sensor_v3/variant.h diff --git a/variants/heltec_sensor_hub/platformio.ini b/variants/esp32s3/heltec_sensor_hub/platformio.ini similarity index 75% rename from variants/heltec_sensor_hub/platformio.ini rename to variants/esp32s3/heltec_sensor_hub/platformio.ini index 53f84fab4..92b90d9b9 100644 --- a/variants/heltec_sensor_hub/platformio.ini +++ b/variants/esp32s3/heltec_sensor_hub/platformio.ini @@ -4,7 +4,8 @@ board = heltec_wifi_lora_32_V3 board_check = true build_flags = - ${esp32s3_base.build_flags} -I variants/heltec_sensor_hub + ${esp32s3_base.build_flags} + -I variants/esp32s3/heltec_sensor_hub -D HELTEC_SENSOR_HUB lib_deps = ${esp32s3_base.lib_deps} diff --git a/variants/heltec_sensor_hub/variant.h b/variants/esp32s3/heltec_sensor_hub/variant.h similarity index 100% rename from variants/heltec_sensor_hub/variant.h rename to variants/esp32s3/heltec_sensor_hub/variant.h diff --git a/variants/heltec_v3/platformio.ini b/variants/esp32s3/heltec_v3/platformio.ini similarity index 73% rename from variants/heltec_v3/platformio.ini rename to variants/esp32s3/heltec_v3/platformio.ini index 4be96b019..b521e11ca 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/esp32s3/heltec_v3/platformio.ini @@ -1,8 +1,11 @@ [env:heltec-v3] extends = esp32s3_base board = heltec_wifi_lora_32_V3 +board_level = pr board_check = true board_build.partitions = default_8MB.csv build_flags = - ${esp32s3_base.build_flags} -D HELTEC_V3 -I variants/heltec_v3 + ${esp32s3_base.build_flags} + -D HELTEC_V3 + -I variants/esp32s3/heltec_v3 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/heltec_v3/variant.h b/variants/esp32s3/heltec_v3/variant.h similarity index 100% rename from variants/heltec_v3/variant.h rename to variants/esp32s3/heltec_v3/variant.h diff --git a/variants/esp32s3/heltec_vision_master_e213/einkDetect.h b/variants/esp32s3/heltec_vision_master_e213/einkDetect.h new file mode 100644 index 000000000..35140db60 --- /dev/null +++ b/variants/esp32s3/heltec_vision_master_e213/einkDetect.h @@ -0,0 +1,35 @@ +#pragma once + +#include "configuration.h" + +enum class EInkDetectionResult : uint8_t { + LCMEN213EFC1 = 0, // Initial version + E0213A367 = 1, // E213 PCB marked V1.1 (Mid 2025) +}; + +EInkDetectionResult detectEInk() +{ + // Test 1: Logic of BUSY pin + + // Determines controller IC manufacturer + // Fitipower: busy when LOW + // Solomon Systech: busy when HIGH + + // Force display BUSY by holding reset pin active + pinMode(PIN_EINK_RES, OUTPUT); + digitalWrite(PIN_EINK_RES, LOW); + + delay(10); + + // Read whether pin is HIGH or LOW while busy + pinMode(PIN_EINK_BUSY, INPUT); + bool busyLogic = digitalRead(PIN_EINK_BUSY); + + // Test complete. Release pin + pinMode(PIN_EINK_RES, INPUT); + + if (busyLogic == LOW) + return EInkDetectionResult::LCMEN213EFC1; + else // busy HIGH + return EInkDetectionResult::E0213A367; +} \ No newline at end of file diff --git a/variants/heltec_vision_master_e213/nicheGraphics.h b/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h similarity index 79% rename from variants/heltec_vision_master_e213/nicheGraphics.h rename to variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h index 26f393f6c..1b1291424 100644 --- a/variants/heltec_vision_master_e213/nicheGraphics.h +++ b/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h @@ -18,16 +18,22 @@ // Shared NicheGraphics components // -------------------------------- +#include "graphics/niche/Drivers/EInk/E0213A367.h" #include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h" #include "graphics/niche/Inputs/TwoButton.h" -// Button feedback -#include "buzz.h" +#include "buzz.h" // Button feedback +#include "einkDetect.h" // Detect display model at runtime void setupNicheGraphics() { using namespace NicheGraphics; + // Detect E-Ink Model + // ------------------- + + EInkDetectionResult displayModel = detectEInk(); + // SPI // ----------------------------- @@ -38,7 +44,13 @@ void setupNicheGraphics() // E-Ink Driver // ----------------------------- - Drivers::EInk *driver = new Drivers::LCMEN213EFC1; + Drivers::EInk *driver; + + if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked) + driver = new Drivers::LCMEN213EFC1; + else if (displayModel == EInkDetectionResult::E0213A367) // V1.1 + driver = new Drivers::E0213A367; + driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES); // InkHUD @@ -51,10 +63,15 @@ void setupNicheGraphics() // Set how many FAST updates per FULL update // Set how unhealthy additional FAST updates beyond this number are - inkhud->setDisplayResilience(10, 1.5); + + if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked) + inkhud->setDisplayResilience(10, 1.5); + else if (displayModel == EInkDetectionResult::E0213A367) // V1.1 + inkhud->setDisplayResilience(15, 3); // Select fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings @@ -90,7 +107,7 @@ void setupNicheGraphics() buttons->setWiring(1, PIN_BUTTON2); buttons->setHandlerShortPress(1, [inkhud]() { inkhud->nextTile(); - playBoop(); + playChirp(); }); // Begin handling button events diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h similarity index 100% rename from variants/heltec_vision_master_e213/pins_arduino.h rename to variants/esp32s3/heltec_vision_master_e213/pins_arduino.h diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/esp32s3/heltec_vision_master_e213/platformio.ini similarity index 79% rename from variants/heltec_vision_master_e213/platformio.ini rename to variants/esp32s3/heltec_vision_master_e213/platformio.ini index 34cebb6e3..43f6199af 100644 --- a/variants/heltec_vision_master_e213/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_e213/platformio.ini @@ -4,10 +4,11 @@ board = heltec_vision_master_e213 board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} - -Ivariants/heltec_vision_master_e213 + -Ivariants/esp32s3/heltec_vision_master_e213 -DHELTEC_VISION_MASTER_E213 -DUSE_EINK - -DEINK_DISPLAY_MODEL=GxEPD2_213_FC1 + -DGXEPD2_DRIVER_0=GxEPD2_213_FC1 + -DGXEPD2_DRIVER_1=GxEPD2_213_E0213A367 -DEINK_WIDTH=250 -DEINK_HEIGHT=122 -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk @@ -16,13 +17,14 @@ build_flags = -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip + https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 [env:heltec-vision-master-e213-inkhud] extends = esp32s3_base, inkhud board = heltec_vision_master_e213 +board_level = pr board_build.partitions = default_8MB.csv build_src_filter = ${esp32_base.build_src_filter} @@ -30,9 +32,9 @@ build_src_filter = build_flags = ${esp32s3_base.build_flags} ${inkhud.build_flags} - -I variants/heltec_vision_master_e213 + -I variants/esp32s3/heltec_vision_master_e213 -D HELTEC_VISION_MASTER_E213 lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${esp32s3_base.lib_deps} -upload_speed = 921600 \ No newline at end of file +upload_speed = 921600 diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/esp32s3/heltec_vision_master_e213/variant.h similarity index 100% rename from variants/heltec_vision_master_e213/variant.h rename to variants/esp32s3/heltec_vision_master_e213/variant.h diff --git a/variants/heltec_vision_master_e290/nicheGraphics.h b/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h similarity index 96% rename from variants/heltec_vision_master_e290/nicheGraphics.h rename to variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h index f3cf6355e..61b08c740 100644 --- a/variants/heltec_vision_master_e290/nicheGraphics.h +++ b/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h @@ -67,7 +67,8 @@ void setupNicheGraphics() inkhud->setDisplayResilience(7, 1.5); // Select fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings @@ -103,7 +104,7 @@ void setupNicheGraphics() buttons->setWiring(1, PIN_BUTTON2); buttons->setHandlerShortPress(1, [inkhud]() { inkhud->nextTile(); - playBoop(); + playChirp(); }); // Begin handling button events diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h similarity index 100% rename from variants/heltec_vision_master_e290/pins_arduino.h rename to variants/esp32s3/heltec_vision_master_e290/pins_arduino.h diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/esp32s3/heltec_vision_master_e290/platformio.ini similarity index 91% rename from variants/heltec_vision_master_e290/platformio.ini rename to variants/esp32s3/heltec_vision_master_e290/platformio.ini index cda3fde00..08056b639 100644 --- a/variants/heltec_vision_master_e290/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_e290/platformio.ini @@ -5,7 +5,7 @@ board = heltec_vision_master_e290 board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} - -I variants/heltec_vision_master_e290 + -I variants/esp32s3/heltec_vision_master_e290 -D DISPLAY_FLIP_SCREEN ; Orient so the LoRa antenna faces up -D HELTEC_VISION_MASTER_E290 -D BUTTON_CLICK_MS=200 @@ -34,9 +34,9 @@ build_src_filter = build_flags = ${esp32s3_base.build_flags} ${inkhud.build_flags} - -I variants/heltec_vision_master_e290 + -I variants/esp32s3/heltec_vision_master_e290 -D HELTEC_VISION_MASTER_E290 lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${esp32s3_base.lib_deps} -upload_speed = 921600 \ No newline at end of file +upload_speed = 921600 diff --git a/variants/heltec_vision_master_e290/variant.h b/variants/esp32s3/heltec_vision_master_e290/variant.h similarity index 100% rename from variants/heltec_vision_master_e290/variant.h rename to variants/esp32s3/heltec_vision_master_e290/variant.h diff --git a/variants/heltec_vision_master_t190/pins_arduino.h b/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h similarity index 100% rename from variants/heltec_vision_master_t190/pins_arduino.h rename to variants/esp32s3/heltec_vision_master_t190/pins_arduino.h diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/esp32s3/heltec_vision_master_t190/platformio.ini similarity index 77% rename from variants/heltec_vision_master_t190/platformio.ini rename to variants/esp32s3/heltec_vision_master_t190/platformio.ini index 7f55a1be7..e7e7ff4e4 100644 --- a/variants/heltec_vision_master_t190/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_t190/platformio.ini @@ -4,10 +4,10 @@ board = heltec_vision_master_t190 board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} - -Ivariants/heltec_vision_master_t190 - -DHELTEC_VISION_MASTER_T190 + -I variants/esp32s3/heltec_vision_master_t190 + -D HELTEC_VISION_MASTER_T190 lib_deps = ${esp32s3_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip -upload_speed = 921600 \ No newline at end of file +upload_speed = 921600 diff --git a/variants/heltec_vision_master_t190/variant.h b/variants/esp32s3/heltec_vision_master_t190/variant.h similarity index 100% rename from variants/heltec_vision_master_t190/variant.h rename to variants/esp32s3/heltec_vision_master_t190/variant.h diff --git a/variants/esp32s3/heltec_wireless_paper/einkDetect.h b/variants/esp32s3/heltec_wireless_paper/einkDetect.h new file mode 100644 index 000000000..93b3f86e3 --- /dev/null +++ b/variants/esp32s3/heltec_wireless_paper/einkDetect.h @@ -0,0 +1,35 @@ +#pragma once + +#include "configuration.h" + +enum class EInkDetectionResult : uint8_t { + LCMEN213EFC1 = 0, // V1.1 + E0213A367 = 1, // V1.1.1, V1.2 +}; + +EInkDetectionResult detectEInk() +{ + // Test 1: Logic of BUSY pin + + // Determines controller IC manufacturer + // Fitipower: busy when LOW + // Solomon Systech: busy when HIGH + + // Force display BUSY by holding reset pin active + pinMode(PIN_EINK_RES, OUTPUT); + digitalWrite(PIN_EINK_RES, LOW); + + delay(10); + + // Read whether pin is HIGH or LOW while busy + pinMode(PIN_EINK_BUSY, INPUT); + bool busyLogic = digitalRead(PIN_EINK_BUSY); + + // Test complete. Release pin + pinMode(PIN_EINK_RES, INPUT); + + if (busyLogic == LOW) + return EInkDetectionResult::LCMEN213EFC1; + else // busy HIGH + return EInkDetectionResult::E0213A367; +} \ No newline at end of file diff --git a/variants/heltec_wireless_paper/nicheGraphics.h b/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h similarity index 79% rename from variants/heltec_wireless_paper/nicheGraphics.h rename to variants/esp32s3/heltec_wireless_paper/nicheGraphics.h index c8994b7f1..445b57714 100644 --- a/variants/heltec_wireless_paper/nicheGraphics.h +++ b/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h @@ -18,13 +18,21 @@ // Shared NicheGraphics components // -------------------------------- +#include "graphics/niche/Drivers/EInk/E0213A367.h" #include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h" #include "graphics/niche/Inputs/TwoButton.h" +#include "einkDetect.h" // Detect display model at runtime + void setupNicheGraphics() { using namespace NicheGraphics; + // Detect E-Ink Model + // ------------------- + + EInkDetectionResult displayModel = detectEInk(); + // SPI // ----------------------------- @@ -35,7 +43,13 @@ void setupNicheGraphics() // E-Ink Driver // ----------------------------- - Drivers::EInk *driver = new Drivers::LCMEN213EFC1; + Drivers::EInk *driver; + + if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1 + driver = new Drivers::LCMEN213EFC1; + else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2 + driver = new Drivers::E0213A367; + driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES); // InkHUD @@ -48,10 +62,15 @@ void setupNicheGraphics() // Set how many FAST updates per FULL update // Set how unhealthy additional FAST updates beyond this number are - inkhud->setDisplayResilience(10, 1.5); + + if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1 (unmarked) + inkhud->setDisplayResilience(10, 1.5); + else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2 + inkhud->setDisplayResilience(15, 3); // Select fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings diff --git a/variants/heltec_wireless_paper/pins_arduino.h b/variants/esp32s3/heltec_wireless_paper/pins_arduino.h similarity index 100% rename from variants/heltec_wireless_paper/pins_arduino.h rename to variants/esp32s3/heltec_wireless_paper/pins_arduino.h diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/esp32s3/heltec_wireless_paper/platformio.ini similarity index 81% rename from variants/heltec_wireless_paper/platformio.ini rename to variants/esp32s3/heltec_wireless_paper/platformio.ini index ce5b5e533..f16dcd257 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/esp32s3/heltec_wireless_paper/platformio.ini @@ -5,9 +5,10 @@ board = heltec_wifi_lora_32_V3 board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} - -I variants/heltec_wireless_paper + -I variants/esp32s3/heltec_wireless_paper -D HELTEC_WIRELESS_PAPER - -D EINK_DISPLAY_MODEL=GxEPD2_213_FC1 + -D GXEPD2_DRIVER_0=GxEPD2_213_FC1 + -D GXEPD2_DRIVER_1=GxEPD2_213_E0213A367 -D EINK_WIDTH=250 -D EINK_HEIGHT=122 -D USE_EINK @@ -17,7 +18,7 @@ build_flags = -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip + https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 @@ -31,9 +32,9 @@ build_src_filter = build_flags = ${esp32s3_base.build_flags} ${inkhud.build_flags} - -I variants/heltec_wireless_paper + -I variants/esp32s3/heltec_wireless_paper -D HELTEC_WIRELESS_PAPER lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${esp32s3_base.lib_deps} -upload_speed = 921600 \ No newline at end of file +upload_speed = 921600 diff --git a/variants/heltec_wireless_paper/variant.h b/variants/esp32s3/heltec_wireless_paper/variant.h similarity index 100% rename from variants/heltec_wireless_paper/variant.h rename to variants/esp32s3/heltec_wireless_paper/variant.h diff --git a/variants/heltec_wireless_paper_v1/pins_arduino.h b/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h similarity index 100% rename from variants/heltec_wireless_paper_v1/pins_arduino.h rename to variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini similarity index 91% rename from variants/heltec_wireless_paper_v1/platformio.ini rename to variants/esp32s3/heltec_wireless_paper_v1/platformio.ini index 44b0606af..99f2eddeb 100644 --- a/variants/heltec_wireless_paper_v1/platformio.ini +++ b/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini @@ -5,7 +5,7 @@ board = heltec_wifi_lora_32_V3 board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} - -I variants/heltec_wireless_paper_v1 + -I variants/esp32s3/heltec_wireless_paper_v1 -D HELTEC_WIRELESS_PAPER_V1_0 -D EINK_DISPLAY_MODEL=GxEPD2_213_BN -D EINK_WIDTH=250 @@ -17,4 +17,4 @@ lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2/archive/55f618961db45a23eff0233546430f1e5a80f63a.zip lewisxhe/PCF8563_Library@^1.0.1 -upload_speed = 115200 \ No newline at end of file +upload_speed = 115200 diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/esp32s3/heltec_wireless_paper_v1/variant.h similarity index 100% rename from variants/heltec_wireless_paper_v1/variant.h rename to variants/esp32s3/heltec_wireless_paper_v1/variant.h diff --git a/variants/heltec_wireless_tracker/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h similarity index 100% rename from variants/heltec_wireless_tracker/pins_arduino.h rename to variants/esp32s3/heltec_wireless_tracker/pins_arduino.h diff --git a/variants/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini similarity index 85% rename from variants/heltec_wireless_tracker/platformio.ini rename to variants/esp32s3/heltec_wireless_tracker/platformio.ini index 5c19c37e6..2faba45a8 100644 --- a/variants/heltec_wireless_tracker/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker/platformio.ini @@ -5,7 +5,8 @@ board_build.partitions = default_8MB.csv upload_protocol = esptool build_flags = - ${esp32s3_base.build_flags} -I variants/heltec_wireless_tracker + ${esp32s3_base.build_flags} + -I variants/esp32s3/heltec_wireless_tracker -D HELTEC_TRACKER_V1_1 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/esp32s3/heltec_wireless_tracker/variant.h similarity index 100% rename from variants/heltec_wireless_tracker/variant.h rename to variants/esp32s3/heltec_wireless_tracker/variant.h diff --git a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h similarity index 100% rename from variants/heltec_wireless_tracker_V1_0/pins_arduino.h rename to variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h diff --git a/variants/heltec_wireless_tracker_V1_0/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini similarity index 85% rename from variants/heltec_wireless_tracker_V1_0/platformio.ini rename to variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini index 08b0ae95c..89fe4b385 100644 --- a/variants/heltec_wireless_tracker_V1_0/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini @@ -5,7 +5,8 @@ board = heltec_wireless_tracker board_build.partitions = default_8MB.csv upload_protocol = esptool build_flags = - ${esp32s3_base.build_flags} -I variants/heltec_wireless_tracker_V1_0 + ${esp32s3_base.build_flags} + -I variants/esp32s3/heltec_wireless_tracker_V1_0 -D HELTEC_TRACKER_V1_0 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h similarity index 100% rename from variants/heltec_wireless_tracker_V1_0/variant.h rename to variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h diff --git a/variants/heltec_wsl_v3/platformio.ini b/variants/esp32s3/heltec_wsl_v3/platformio.ini similarity index 78% rename from variants/heltec_wsl_v3/platformio.ini rename to variants/esp32s3/heltec_wsl_v3/platformio.ini index bc3e6ada1..06cde2304 100644 --- a/variants/heltec_wsl_v3/platformio.ini +++ b/variants/esp32s3/heltec_wsl_v3/platformio.ini @@ -4,5 +4,7 @@ board = heltec_wifi_lora_32_V3 board_build.partitions = default_8MB.csv # Temporary until espressif creates a release with this new target build_flags = - ${esp32s3_base.build_flags} -D HELTEC_WSL_V3 -I variants/heltec_wsl_v3 + ${esp32s3_base.build_flags} + -D HELTEC_WSL_V3 + -I variants/esp32s3/heltec_wsl_v3 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/heltec_wsl_v3/variant.h b/variants/esp32s3/heltec_wsl_v3/variant.h similarity index 100% rename from variants/heltec_wsl_v3/variant.h rename to variants/esp32s3/heltec_wsl_v3/variant.h diff --git a/variants/icarus/pins_arduino.h b/variants/esp32s3/icarus/pins_arduino.h similarity index 100% rename from variants/icarus/pins_arduino.h rename to variants/esp32s3/icarus/pins_arduino.h diff --git a/variants/icarus/platformio.ini b/variants/esp32s3/icarus/platformio.ini similarity index 84% rename from variants/icarus/platformio.ini rename to variants/esp32s3/icarus/platformio.ini index b4ea125cf..de450da93 100644 --- a/variants/icarus/platformio.ini +++ b/variants/esp32s3/icarus/platformio.ini @@ -14,6 +14,8 @@ build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 build_flags = - ${esp32s3_base.build_flags} -D PRIVATE_HW -I variants/icarus - -DBOARD_HAS_PSRAM + ${esp32s3_base.build_flags} + -D PRIVATE_HW + -I variants/esp32s3/icarus + -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=0 diff --git a/variants/icarus/variant.h b/variants/esp32s3/icarus/variant.h similarity index 100% rename from variants/icarus/variant.h rename to variants/esp32s3/icarus/variant.h diff --git a/variants/link32_s3_v1/pins_arduino.h b/variants/esp32s3/link32_s3_v1/pins_arduino.h similarity index 100% rename from variants/link32_s3_v1/pins_arduino.h rename to variants/esp32s3/link32_s3_v1/pins_arduino.h diff --git a/variants/link32_s3_v1/platformio.ini b/variants/esp32s3/link32_s3_v1/platformio.ini similarity index 81% rename from variants/link32_s3_v1/platformio.ini rename to variants/esp32s3/link32_s3_v1/platformio.ini index 5a614a7af..c1b71b3b5 100644 --- a/variants/link32_s3_v1/platformio.ini +++ b/variants/esp32s3/link32_s3_v1/platformio.ini @@ -2,7 +2,9 @@ extends = esp32s3_base board = esp32-s3-devkitc-1 build_flags = - ${esp32_base.build_flags} -D LINK_32 -I variants/link32_s3_v1 + ${esp32_base.build_flags} + -D LINK_32 + -I variants/esp32s3/link32_s3_v1 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DARDUINO_USB_CDC_ON_BOOT -DARDUINO_USB_MODE=1 diff --git a/variants/link32_s3_v1/variant.h b/variants/esp32s3/link32_s3_v1/variant.h similarity index 100% rename from variants/link32_s3_v1/variant.h rename to variants/esp32s3/link32_s3_v1/variant.h diff --git a/variants/m5stack_cores3/pins_arduino.h b/variants/esp32s3/m5stack_cores3/pins_arduino.h similarity index 100% rename from variants/m5stack_cores3/pins_arduino.h rename to variants/esp32s3/m5stack_cores3/pins_arduino.h diff --git a/variants/m5stack_cores3/platformio.ini b/variants/esp32s3/m5stack_cores3/platformio.ini similarity index 53% rename from variants/m5stack_cores3/platformio.ini rename to variants/esp32s3/m5stack_cores3/platformio.ini index 2253e75e2..9973abfce 100644 --- a/variants/m5stack_cores3/platformio.ini +++ b/variants/esp32s3/m5stack_cores3/platformio.ini @@ -5,11 +5,9 @@ board = m5stack-cores3 board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool - -build_flags = ${esp32_base.build_flags} - -DPRIVATE_HW - -DM5STACK_CORES3 - -Ivariants/m5stack_cores3 - -lib_deps = - ${esp32_base.lib_deps} +build_flags = + ${esp32_base.build_flags} + -D PRIVATE_HW + -D M5STACK_CORES3 + -I variants/esp32s3/m5stack_cores3 +lib_deps = ${esp32_base.lib_deps} diff --git a/variants/m5stack_cores3/variant.h b/variants/esp32s3/m5stack_cores3/variant.h similarity index 100% rename from variants/m5stack_cores3/variant.h rename to variants/esp32s3/m5stack_cores3/variant.h diff --git a/variants/mesh-tab/pins_arduino.h b/variants/esp32s3/mesh-tab/pins_arduino.h similarity index 100% rename from variants/mesh-tab/pins_arduino.h rename to variants/esp32s3/mesh-tab/pins_arduino.h diff --git a/variants/mesh-tab/platformio.ini b/variants/esp32s3/mesh-tab/platformio.ini similarity index 93% rename from variants/mesh-tab/platformio.ini rename to variants/esp32s3/mesh-tab/platformio.ini index beeb58a48..e21bc38e1 100644 --- a/variants/mesh-tab/platformio.ini +++ b/variants/esp32s3/mesh-tab/platformio.ini @@ -45,7 +45,7 @@ build_flags = ${esp32s3_base.build_flags} -D LGFX_TOUCH_INT=41 -D VIEW_320x240 -D USE_PACKET_API - -I variants/mesh-tab + -I variants/esp32s3/mesh-tab build_src_filter = ${esp32_base.build_src_filter} lib_deps = ${esp32_base.lib_deps} @@ -85,6 +85,7 @@ build_flags = ${mesh_tab_xpt2046.build_flags} -D SPI_FREQUENCY=60000000 -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 + -D DISPLAY_SIZE=320x240 ; landscape mode -D LGFX_PANEL=ST7789 -D LGFX_INVERT_COLOR=false -D LGFX_ROTATION=3 @@ -97,6 +98,7 @@ build_flags = ${mesh_tab_xpt2046.build_flags} -D SPI_FREQUENCY=60000000 ; if image is distorted then lower to 40 MHz -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 + -D DISPLAY_SIZE=320x240 ; landscape mode -D LGFX_PANEL=ILI9341 -D LGFX_ROTATION=1 -D LGFX_TOUCH_ROTATION=4 @@ -109,6 +111,7 @@ build_flags = ${mesh_tab_xpt2046.build_flags} -D DISPLAY_SET_RESOLUTION -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 + -D DISPLAY_SIZE=320x480 ; portrait mode -D LGFX_PANEL=ILI9488 -D LGFX_ROTATION=0 -D LGFX_TOUCH_ROTATION=0 @@ -121,6 +124,7 @@ build_flags = ${mesh_tab_xpt2046.build_flags} -D DISPLAY_SET_RESOLUTION -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 + -D DISPLAY_SIZE=320x480 ; portrait mode -D LGFX_PANEL=HX8357B -D LGFX_INVERT_COLOR=false -D LGFX_ROTATION=4 @@ -133,6 +137,7 @@ build_flags = ${mesh_tab_ft5x06.build_flags} -D SPI_FREQUENCY=75000000 ; may go higher upto 60/80 MHz -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 + -D DISPLAY_SIZE=320x240 ; landscape mode -D LGFX_PANEL=ILI9341 -D LGFX_ROTATION=1 -D LGFX_TOUCH_X_MIN=0 @@ -149,6 +154,7 @@ build_flags = ${mesh_tab_ft5x06.build_flags} -D DISPLAY_SET_RESOLUTION -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 + -D DISPLAY_SIZE=320x480 ; portrait mode -D LGFX_PANEL=ILI9488 -D LGFX_ROTATION=2 -D LGFX_TOUCH_X_MIN=0 @@ -165,6 +171,7 @@ build_flags = ${mesh_tab_ft5x06.build_flags} -D DISPLAY_SET_RESOLUTION -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 + -D DISPLAY_SIZE=320x480 ; portrait mode -D LGFX_PANEL=HX8357B -D LGFX_ROTATION=4 -D LGFX_TOUCH_X_MIN=0 diff --git a/variants/mesh-tab/variant.h b/variants/esp32s3/mesh-tab/variant.h similarity index 100% rename from variants/mesh-tab/variant.h rename to variants/esp32s3/mesh-tab/variant.h diff --git a/variants/nibble_esp32/platformio.ini b/variants/esp32s3/nibble_esp32/platformio.ini similarity index 55% rename from variants/nibble_esp32/platformio.ini rename to variants/esp32s3/nibble_esp32/platformio.ini index 24d2ee2a5..2f6960d2e 100644 --- a/variants/nibble_esp32/platformio.ini +++ b/variants/esp32s3/nibble_esp32/platformio.ini @@ -3,4 +3,6 @@ extends = esp32s3_base board = esp32-s3-zero board_level = extra build_flags = - ${esp32_base.build_flags} -D PRIVATE_HW -I variants/nibble_esp32 \ No newline at end of file + ${esp32_base.build_flags} + -D PRIVATE_HW + -I variants/esp32s3/nibble_esp32 diff --git a/variants/nibble_esp32/variant.h b/variants/esp32s3/nibble_esp32/variant.h similarity index 100% rename from variants/nibble_esp32/variant.h rename to variants/esp32s3/nibble_esp32/variant.h diff --git a/variants/nugget_s3_lora/platformio.ini b/variants/esp32s3/nugget_s3_lora/platformio.ini similarity index 78% rename from variants/nugget_s3_lora/platformio.ini rename to variants/esp32s3/nugget_s3_lora/platformio.ini index 1085d633b..a0076a18b 100644 --- a/variants/nugget_s3_lora/platformio.ini +++ b/variants/esp32s3/nugget_s3_lora/platformio.ini @@ -3,4 +3,4 @@ extends = esp32s3_base board = lolin_s3_mini board_level = extra build_flags = - ${esp32s3_base.build_flags} -D ARDUINO_USB_CDC_ON_BOOT=1 -D PRIVATE_HW -I variants/nugget_s3_lora + ${esp32s3_base.build_flags} -D ARDUINO_USB_CDC_ON_BOOT=1 -D PRIVATE_HW -I variants/esp32s3/nugget_s3_lora diff --git a/variants/nugget_s3_lora/variant.h b/variants/esp32s3/nugget_s3_lora/variant.h similarity index 100% rename from variants/nugget_s3_lora/variant.h rename to variants/esp32s3/nugget_s3_lora/variant.h diff --git a/variants/picomputer-s3/pins_arduino.h b/variants/esp32s3/picomputer-s3/pins_arduino.h similarity index 100% rename from variants/picomputer-s3/pins_arduino.h rename to variants/esp32s3/picomputer-s3/pins_arduino.h diff --git a/variants/picomputer-s3/platformio.ini b/variants/esp32s3/picomputer-s3/platformio.ini similarity index 93% rename from variants/picomputer-s3/platformio.ini rename to variants/esp32s3/picomputer-s3/platformio.ini index b7987796f..d5847959b 100644 --- a/variants/picomputer-s3/platformio.ini +++ b/variants/esp32s3/picomputer-s3/platformio.ini @@ -11,7 +11,7 @@ upload_protocol = esptool build_flags = ${esp32s3_base.build_flags} -DPICOMPUTER_S3 - -I variants/picomputer-s3 + -I variants/esp32s3/picomputer-s3 lib_deps = ${esp32s3_base.lib_deps} @@ -44,6 +44,7 @@ build_flags = -D LOG_DEBUG_INC=\"DebugConfiguration.h\" -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 + -D DISPLAY_SIZE=320x240 ; landscape mode -D LGFX_DRIVER=LGFX_PICOMPUTER_S3 -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_PICOMPUTER_S3.h\" -D VIEW_320x240 diff --git a/variants/picomputer-s3/variant.h b/variants/esp32s3/picomputer-s3/variant.h similarity index 97% rename from variants/picomputer-s3/variant.h rename to variants/esp32s3/picomputer-s3/variant.h index ff8faa6f4..8252e841c 100644 --- a/variants/picomputer-s3/variant.h +++ b/variants/esp32s3/picomputer-s3/variant.h @@ -49,7 +49,7 @@ #define SCREEN_TRANSITION_FRAMERATE 5 // Picomputer gets a white on black display -#define TFT_MESH COLOR565(0xFF, 0xFF, 0xFF) +#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255) #define CANNED_MESSAGE_MODULE_ENABLE 1 diff --git a/variants/esp32s3/rak3312/pins_arduino.h b/variants/esp32s3/rak3312/pins_arduino.h new file mode 100644 index 000000000..15a26e991 --- /dev/null +++ b/variants/esp32s3/rak3312/pins_arduino.h @@ -0,0 +1,28 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "variant.h" +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SDA = 9; +static const uint8_t SCL = 40; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 12; +static const uint8_t MOSI = 11; +static const uint8_t MISO = 10; +static const uint8_t SCK = 13; + +#define SPI_MOSI (11) +#define SPI_SCK (13) +#define SPI_MISO (10) +#define SPI_CS (12) + +// LEDs +#define LED_BUILTIN LED_GREEN + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/rak3312/platformio.ini b/variants/esp32s3/rak3312/platformio.ini new file mode 100644 index 000000000..0de36498f --- /dev/null +++ b/variants/esp32s3/rak3312/platformio.ini @@ -0,0 +1,11 @@ +[env:rak3312] +extends = esp32s3_base +board = wiscore_rak3312 +board_level = pr +board_check = true +upload_protocol = esptool + +build_flags = + ${esp32_base.build_flags} + -D RAK3312 + -I variants/esp32s3/rak3312 diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h new file mode 100644 index 000000000..dfdf4de71 --- /dev/null +++ b/variants/esp32s3/rak3312/variant.h @@ -0,0 +1,44 @@ +#define I2C_SDA 9 +#define I2C_SCL 40 + +#define USE_SX1262 + +#define LORA_SCK 5 +#define LORA_MISO 3 +#define LORA_MOSI 6 +#define LORA_CS 7 +#define LORA_RESET 8 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 47 +#define SX126X_BUSY 48 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif + +#define SX126X_POWER_EN (4) + +#define PIN_POWER_EN PIN_3V3_EN +#define PIN_3V3_EN (14) + +#define LED_GREEN 46 +#define LED_BLUE 45 + +#define PIN_LED1 LED_GREEN +#define PIN_LED2 LED_BLUE + +#define LED_CONN LED_BLUE +#define LED_PIN LED_GREEN +#define ledOff(pin) pinMode(pin, INPUT) + +#define LED_STATE_ON 1 // State when LED is litted + +#define HAS_GPS 1 +#define GPS_TX_PIN 43 +#define GPS_RX_PIN 44 + +#define BATTERY_PIN 1 +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_MULTIPLIER 1.667 \ No newline at end of file diff --git a/variants/seeed-sensecap-indicator/pins_arduino.h b/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h similarity index 100% rename from variants/seeed-sensecap-indicator/pins_arduino.h rename to variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h diff --git a/variants/seeed-sensecap-indicator/platformio.ini b/variants/esp32s3/seeed-sensecap-indicator/platformio.ini similarity index 98% rename from variants/seeed-sensecap-indicator/platformio.ini rename to variants/esp32s3/seeed-sensecap-indicator/platformio.ini index aafb55449..cf01c8748 100644 --- a/variants/seeed-sensecap-indicator/platformio.ini +++ b/variants/esp32s3/seeed-sensecap-indicator/platformio.ini @@ -21,7 +21,7 @@ build_unflags = -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192 build_flags = ${esp32_base.build_flags} -D lcd_periph_signals=lcd_periph_rgb_signals -mtext-section-literals -ffat-lto-objects - -Ivariants/seeed-sensecap-indicator + -Ivariants/esp32s3/seeed-sensecap-indicator -DSENSECAP_INDICATOR -DCONFIG_ARDUHAL_LOG_COLORS -DRADIOLIB_DEBUG_SPI=0 @@ -87,7 +87,7 @@ custom_sdkconfig = [env:seeed-sensecap-indicator-tft] extends = env:seeed-sensecap-indicator -board_level = main +board_level = pr upload_speed = 460800 build_flags = @@ -118,6 +118,7 @@ build_flags = -D CUSTOM_TOUCH_DRIVER -D LGFX_SCREEN_WIDTH=480 -D LGFX_SCREEN_HEIGHT=480 + -D DISPLAY_SIZE=480x480 -D LGFX_DRIVER=LGFX_INDICATOR -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_INDICATOR.h\" -D VIEW_320x240 diff --git a/variants/seeed-sensecap-indicator/variant.h b/variants/esp32s3/seeed-sensecap-indicator/variant.h similarity index 100% rename from variants/seeed-sensecap-indicator/variant.h rename to variants/esp32s3/seeed-sensecap-indicator/variant.h diff --git a/variants/seeed_xiao_s3/pins_arduino.h b/variants/esp32s3/seeed_xiao_s3/pins_arduino.h similarity index 100% rename from variants/seeed_xiao_s3/pins_arduino.h rename to variants/esp32s3/seeed_xiao_s3/pins_arduino.h diff --git a/variants/seeed_xiao_s3/platformio.ini b/variants/esp32s3/seeed_xiao_s3/platformio.ini similarity index 71% rename from variants/seeed_xiao_s3/platformio.ini rename to variants/esp32s3/seeed_xiao_s3/platformio.ini index 9d935e2e0..ffc6e9638 100644 --- a/variants/seeed_xiao_s3/platformio.ini +++ b/variants/esp32s3/seeed_xiao_s3/platformio.ini @@ -1,6 +1,7 @@ [env:seeed-xiao-s3] extends = esp32s3_base board = seeed-xiao-s3 +board_level = pr board_check = true board_build.partitions = default_8MB.csv upload_protocol = esptool @@ -11,7 +12,8 @@ build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 build_flags = - ${esp32s3_base.build_flags} -DSEEED_XIAO_S3 -I variants/seeed_xiao_s3 + ${esp32s3_base.build_flags} + -D SEEED_XIAO_S3 + -I variants/esp32s3/seeed_xiao_s3 -DBOARD_HAS_PSRAM - - -DARDUINO_USB_MODE=0 \ No newline at end of file + -DARDUINO_USB_MODE=0 diff --git a/variants/seeed_xiao_s3/variant.h b/variants/esp32s3/seeed_xiao_s3/variant.h similarity index 100% rename from variants/seeed_xiao_s3/variant.h rename to variants/esp32s3/seeed_xiao_s3/variant.h diff --git a/variants/station-g2/pins_arduino.h b/variants/esp32s3/station-g2/pins_arduino.h similarity index 100% rename from variants/station-g2/pins_arduino.h rename to variants/esp32s3/station-g2/pins_arduino.h diff --git a/variants/station-g2/platformio.ini b/variants/esp32s3/station-g2/platformio.ini similarity index 80% rename from variants/station-g2/platformio.ini rename to variants/esp32s3/station-g2/platformio.ini index 4ddd28f1c..056d543d9 100755 --- a/variants/station-g2/platformio.ini +++ b/variants/esp32s3/station-g2/platformio.ini @@ -1,6 +1,7 @@ [env:station-g2] extends = esp32s3_base board = station-g2 +board_level = pr board_check = true board_build.partitions = default_16MB.csv board_build.mcu = esp32s3 @@ -13,7 +14,9 @@ build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 build_flags = - ${esp32s3_base.build_flags} -D STATION_G2 -I variants/station-g2 + ${esp32s3_base.build_flags} + -D STATION_G2 + -I variants/esp32s3/station-g2 -DBOARD_HAS_PSRAM -DSTATION_G2 -DARDUINO_USB_MODE=0 diff --git a/variants/station-g2/variant.h b/variants/esp32s3/station-g2/variant.h similarity index 100% rename from variants/station-g2/variant.h rename to variants/esp32s3/station-g2/variant.h diff --git a/variants/esp32s3/t-deck-pro/pins_arduino.h b/variants/esp32s3/t-deck-pro/pins_arduino.h new file mode 100644 index 000000000..af0ba80b3 --- /dev/null +++ b/variants/esp32s3/t-deck-pro/pins_arduino.h @@ -0,0 +1,19 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// used for keyboard, touch controller, beam sensor, and gyroscope +static const uint8_t SDA = 13; +static const uint8_t SCL = 14; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 3; +static const uint8_t MOSI = 33; +static const uint8_t MISO = 47; +static const uint8_t SCK = 36; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini new file mode 100644 index 000000000..45c3ae4ea --- /dev/null +++ b/variants/esp32s3/t-deck-pro/platformio.ini @@ -0,0 +1,24 @@ +[env:t-deck-pro] +extends = esp32s3_base +board = t-deck-pro +board_check = true +upload_protocol = esptool + +build_flags = + ${esp32_base.build_flags} -I variants/esp32s3/t-deck-pro + -D T_DECK_PRO + -D GPS_POWER_TOGGLE + -D USE_EINK + -D EINK_DISPLAY_MODEL=GxEPD2_310_GDEQ031T10 + -D EINK_WIDTH=240 + -D EINK_HEIGHT=320 + ;-D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + +lib_deps = + ${esp32s3_base.lib_deps} + https://github.com/ZinggJM/GxEPD2/archive/refs/tags/1.6.4.zip + https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip + https://github.com/CIRCUITSTATE/CSE_CST328/archive/refs/tags/v0.0.4.zip + https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h new file mode 100644 index 000000000..b08d3f65f --- /dev/null +++ b/variants/esp32s3/t-deck-pro/variant.h @@ -0,0 +1,94 @@ +// Display (E-Ink) +#define PIN_EINK_CS 34 +#define PIN_EINK_BUSY 37 +#define PIN_EINK_DC 35 +#define PIN_EINK_RES -1 +#define PIN_EINK_SCLK 36 +#define PIN_EINK_MOSI 47 + +#define I2C_SDA SDA +#define I2C_SCL SCL + +// CST328 touch screen (implementation in src/platform/extra_variants/t_deck_pro/variant.cpp) +#define HAS_TOUCHSCREEN 1 +#define CST328_PIN_INT 12 +#define CST328_PIN_RST 45 + +#define USE_POWERSAVE +#define SLEEP_TIME 120 + +// GNNS +#define HAS_GPS 1 +#define GPS_BAUDRATE 38400 +#define PIN_GPS_EN 15 +#define GPS_EN_ACTIVE 1 +#define GPS_RX_PIN 44 +#define GPS_TX_PIN 43 +#define PIN_GPS_PPS 1 + +#define BUTTON_PIN 0 + +// vibration motor +#define PIN_VIBRATION 2 + +// Have SPI interface SD card slot +#define HAS_SDCARD +#define SDCARD_USE_SPI1 +#define SPI_MOSI (33) +#define SPI_SCK (36) +#define SPI_MISO (47) +#define SPI_CS (48) +#define SDCARD_CS SPI_CS +#define SD_SPI_FREQUENCY 75000000U + +// TCA8418 keyboard +#define KB_BL_PIN 42 +#define CANNED_MESSAGE_MODULE_ENABLE 1 + +// microphone PCM5102A +#define PCM5102A_SCK 47 +#define PCM5102A_DIN 17 +#define PCM5102A_LRCK 18 + +// LTR_553ALS light sensor +#define HAS_LTR553ALS + +// gyroscope BHI260AP +#define BOARD_1V8_EN 38 +#define HAS_BHI260AP + +// battery charger BQ25896 +#define HAS_PPM 1 +#define XPOWERS_CHIP_BQ25896 + +// battery quality management BQ27220 +#define HAS_BQ27220 1 +#define BQ27220_I2C_SDA SDA +#define BQ27220_I2C_SCL SCL +#define BQ27220_DESIGN_CAPACITY 1400 + +// LoRa +#define USE_SX1262 +#define USE_SX1268 + +#define LORA_EN 46 // LoRa enable pin +#define LORA_SCK 36 +#define LORA_MISO 47 +#define LORA_MOSI 33 +#define LORA_CS 3 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 4 +#define LORA_DIO1 5 // SX1262 IRQ +#define LORA_DIO2 6 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +// Not really an E22 but TTGO seems to be trying to clone that +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 2.4 +// Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface +// code) diff --git a/variants/t-deck/pins_arduino.h b/variants/esp32s3/t-deck/pins_arduino.h similarity index 100% rename from variants/t-deck/pins_arduino.h rename to variants/esp32s3/t-deck/pins_arduino.h diff --git a/variants/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini similarity index 92% rename from variants/t-deck/platformio.ini rename to variants/esp32s3/t-deck/platformio.ini index 6ee95b119..7c8070c3e 100644 --- a/variants/t-deck/platformio.ini +++ b/variants/esp32s3/t-deck/platformio.ini @@ -7,19 +7,19 @@ board_build.partitions = default_16MB.csv upload_protocol = esptool build_flags = ${esp32s3_base.build_flags} - -DT_DECK - -DBOARD_HAS_PSRAM - -DGPS_POWER_TOGGLE - -Ivariants/t-deck + -D T_DECK + -D BOARD_HAS_PSRAM + -D GPS_POWER_TOGGLE + -I variants/esp32s3/t-deck lib_deps = ${esp32s3_base.lib_deps} lovyan03/LovyanGFX@^1.2.0 earlephilhower/ESP8266Audio@^1.9.9 earlephilhower/ESP8266SAM@^1.0.1 - [env:t-deck-tft] extends = env:t-deck +board_level = pr build_flags = ${env:t-deck.build_flags} @@ -54,6 +54,7 @@ build_flags = ; -D CALIBRATE_TOUCH=0 -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 + -D DISPLAY_SIZE=320x240 ; landscape mode -D LGFX_DRIVER=LGFX_TDECK -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_T_DECK.h\" ; -D LVGL_DRIVER=LVGL_TDECK diff --git a/variants/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h similarity index 99% rename from variants/t-deck/variant.h rename to variants/esp32s3/t-deck/variant.h index 9fa0018ec..9b0de631a 100644 --- a/variants/t-deck/variant.h +++ b/variants/esp32s3/t-deck/variant.h @@ -68,6 +68,7 @@ #define TB_LEFT 1 #define TB_RIGHT 2 #define TB_PRESS 0 // BUTTON_PIN +#define TB_DIRECTION FALLING // microphone #define ES7210_SCK 47 diff --git a/variants/t-eth-elite/pins_arduino.h b/variants/esp32s3/t-eth-elite/pins_arduino.h similarity index 100% rename from variants/t-eth-elite/pins_arduino.h rename to variants/esp32s3/t-eth-elite/pins_arduino.h diff --git a/variants/t-eth-elite/platformio.ini b/variants/esp32s3/t-eth-elite/platformio.ini similarity index 85% rename from variants/t-eth-elite/platformio.ini rename to variants/esp32s3/t-eth-elite/platformio.ini index d6f415f3d..6107185ce 100644 --- a/variants/t-eth-elite/platformio.ini +++ b/variants/esp32s3/t-eth-elite/platformio.ini @@ -1,12 +1,14 @@ [env:t-eth-elite] extends = esp32s3_base board = esp32s3box +board_level = pr board_check = true board_build.partitions = default_16MB.csv build_flags = ${esp32s3_base.build_flags} -D T_ETH_ELITE - -I variants/t-eth-elite + -D HAS_UDP_MULTICAST=1 + -I variants/esp32s3/t-eth-elite -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. lib_ignore = diff --git a/variants/t-eth-elite/rfswitch.h b/variants/esp32s3/t-eth-elite/rfswitch.h similarity index 100% rename from variants/t-eth-elite/rfswitch.h rename to variants/esp32s3/t-eth-elite/rfswitch.h diff --git a/variants/t-eth-elite/variant.h b/variants/esp32s3/t-eth-elite/variant.h similarity index 100% rename from variants/t-eth-elite/variant.h rename to variants/esp32s3/t-eth-elite/variant.h diff --git a/variants/t-watch-s3/pins_arduino.h b/variants/esp32s3/t-watch-s3/pins_arduino.h similarity index 100% rename from variants/t-watch-s3/pins_arduino.h rename to variants/esp32s3/t-watch-s3/pins_arduino.h diff --git a/variants/t-watch-s3/platformio.ini b/variants/esp32s3/t-watch-s3/platformio.ini similarity index 93% rename from variants/t-watch-s3/platformio.ini rename to variants/esp32s3/t-watch-s3/platformio.ini index f98237943..59ff8891d 100644 --- a/variants/t-watch-s3/platformio.ini +++ b/variants/esp32s3/t-watch-s3/platformio.ini @@ -8,7 +8,7 @@ upload_protocol = esptool build_flags = ${esp32_base.build_flags} -DT_WATCH_S3 - -Ivariants/t-watch-s3 + -Ivariants/esp32s3/t-watch-s3 -DPCF8563_RTC=0x51 -DHAS_BMA423=1 diff --git a/variants/t-watch-s3/variant.h b/variants/esp32s3/t-watch-s3/variant.h similarity index 100% rename from variants/t-watch-s3/variant.h rename to variants/esp32s3/t-watch-s3/variant.h diff --git a/variants/tbeam-s3-core/pins_arduino.h b/variants/esp32s3/tbeam-s3-core/pins_arduino.h similarity index 100% rename from variants/tbeam-s3-core/pins_arduino.h rename to variants/esp32s3/tbeam-s3-core/pins_arduino.h diff --git a/variants/tbeam-s3-core/platformio.ini b/variants/esp32s3/tbeam-s3-core/platformio.ini similarity index 71% rename from variants/tbeam-s3-core/platformio.ini rename to variants/esp32s3/tbeam-s3-core/platformio.ini index a7bdf963f..fba8e4003 100644 --- a/variants/tbeam-s3-core/platformio.ini +++ b/variants/esp32s3/tbeam-s3-core/platformio.ini @@ -11,5 +11,5 @@ lib_deps = build_flags = ${esp32s3_base.build_flags} - -Ivariants/tbeam-s3-core - -DPCF8563_RTC=0x51 ;Putting definitions in variant.h does not compile correctly + -I variants/esp32s3/tbeam-s3-core + -D PCF8563_RTC=0x51 ;Putting definitions in variant.h does not compile correctly diff --git a/variants/tbeam-s3-core/variant.h b/variants/esp32s3/tbeam-s3-core/variant.h similarity index 100% rename from variants/tbeam-s3-core/variant.h rename to variants/esp32s3/tbeam-s3-core/variant.h diff --git a/variants/tlora_t3s3_epaper/nicheGraphics.h b/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h similarity index 96% rename from variants/tlora_t3s3_epaper/nicheGraphics.h rename to variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h index 5184037e8..8f5e63653 100644 --- a/variants/tlora_t3s3_epaper/nicheGraphics.h +++ b/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h @@ -51,7 +51,8 @@ void setupNicheGraphics() inkhud->setDisplayResilience(15, 1.5); // Select fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings diff --git a/variants/tlora_t3s3_epaper/pins_arduino.h b/variants/esp32s3/tlora_t3s3_epaper/pins_arduino.h similarity index 100% rename from variants/tlora_t3s3_epaper/pins_arduino.h rename to variants/esp32s3/tlora_t3s3_epaper/pins_arduino.h diff --git a/variants/tlora_t3s3_epaper/platformio.ini b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini similarity index 85% rename from variants/tlora_t3s3_epaper/platformio.ini rename to variants/esp32s3/tlora_t3s3_epaper/platformio.ini index 0750b5bbb..71644ee77 100644 --- a/variants/tlora_t3s3_epaper/platformio.ini +++ b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini @@ -5,7 +5,9 @@ board_check = true upload_protocol = esptool build_flags = - ${esp32_base.build_flags} -D TLORA_T3S3_EPAPER -I variants/tlora_t3s3_epaper + ${esp32_base.build_flags} + -D TLORA_T3S3_EPAPER + -I variants/esp32s3/tlora_t3s3_epaper -DGPS_POWER_TOGGLE -DUSE_EINK -DEINK_DISPLAY_MODEL=GxEPD2_213_BN @@ -29,8 +31,8 @@ build_src_filter = build_flags = ${esp32s3_base.build_flags} ${inkhud.build_flags} - -I variants/tlora_t3s3_epaper + -I variants/esp32s3/tlora_t3s3_epaper -D TLORA_T3S3_EPAPER lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX - ${esp32s3_base.lib_deps} \ No newline at end of file + ${esp32s3_base.lib_deps} diff --git a/variants/tlora_t3s3_epaper/variant.h b/variants/esp32s3/tlora_t3s3_epaper/variant.h similarity index 100% rename from variants/tlora_t3s3_epaper/variant.h rename to variants/esp32s3/tlora_t3s3_epaper/variant.h diff --git a/variants/tlora_t3s3_v1/pins_arduino.h b/variants/esp32s3/tlora_t3s3_v1/pins_arduino.h similarity index 100% rename from variants/tlora_t3s3_v1/pins_arduino.h rename to variants/esp32s3/tlora_t3s3_v1/pins_arduino.h diff --git a/variants/tlora_t3s3_v1/platformio.ini b/variants/esp32s3/tlora_t3s3_v1/platformio.ini similarity index 58% rename from variants/tlora_t3s3_v1/platformio.ini rename to variants/esp32s3/tlora_t3s3_v1/platformio.ini index 0a5797280..d9624f043 100644 --- a/variants/tlora_t3s3_v1/platformio.ini +++ b/variants/esp32s3/tlora_t3s3_v1/platformio.ini @@ -5,5 +5,5 @@ board_check = true upload_protocol = esptool build_flags = - ${esp32_base.build_flags} -D TLORA_T3S3_V1 -I variants/tlora_t3s3_v1 - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. \ No newline at end of file + ${esp32_base.build_flags} -D TLORA_T3S3_V1 -I variants/esp32s3/tlora_t3s3_v1 + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/tlora_t3s3_v1/rfswitch.h b/variants/esp32s3/tlora_t3s3_v1/rfswitch.h similarity index 100% rename from variants/tlora_t3s3_v1/rfswitch.h rename to variants/esp32s3/tlora_t3s3_v1/rfswitch.h diff --git a/variants/tlora_t3s3_v1/variant.h b/variants/esp32s3/tlora_t3s3_v1/variant.h similarity index 100% rename from variants/tlora_t3s3_v1/variant.h rename to variants/esp32s3/tlora_t3s3_v1/variant.h diff --git a/variants/tracksenger/internal/pins_arduino.h b/variants/esp32s3/tracksenger/internal/pins_arduino.h similarity index 100% rename from variants/tracksenger/internal/pins_arduino.h rename to variants/esp32s3/tracksenger/internal/pins_arduino.h diff --git a/variants/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h similarity index 98% rename from variants/tracksenger/internal/variant.h rename to variants/esp32s3/tracksenger/internal/variant.h index 57ead848d..6f75ad0e2 100644 --- a/variants/tracksenger/internal/variant.h +++ b/variants/esp32s3/tracksenger/internal/variant.h @@ -72,7 +72,7 @@ #define SX126X_DIO3_TCXO_VOLTAGE 1.8 // Picomputer gets a white on black display -#define TFT_MESH COLOR565(0xFF, 0xFF, 0xFF) +#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255) // keyboard changes diff --git a/variants/tracksenger/lcd/pins_arduino.h b/variants/esp32s3/tracksenger/lcd/pins_arduino.h similarity index 100% rename from variants/tracksenger/lcd/pins_arduino.h rename to variants/esp32s3/tracksenger/lcd/pins_arduino.h diff --git a/variants/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h similarity index 98% rename from variants/tracksenger/lcd/variant.h rename to variants/esp32s3/tracksenger/lcd/variant.h index ecf4e854e..843bf3924 100644 --- a/variants/tracksenger/lcd/variant.h +++ b/variants/esp32s3/tracksenger/lcd/variant.h @@ -96,7 +96,7 @@ #define SX126X_DIO3_TCXO_VOLTAGE 1.8 // Picomputer gets a white on black display -#define TFT_MESH COLOR565(0xFF, 0xFF, 0xFF) +#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255) // keyboard changes diff --git a/variants/tracksenger/oled/pins_arduino.h b/variants/esp32s3/tracksenger/oled/pins_arduino.h similarity index 100% rename from variants/tracksenger/oled/pins_arduino.h rename to variants/esp32s3/tracksenger/oled/pins_arduino.h diff --git a/variants/tracksenger/oled/variant.h b/variants/esp32s3/tracksenger/oled/variant.h similarity index 98% rename from variants/tracksenger/oled/variant.h rename to variants/esp32s3/tracksenger/oled/variant.h index 70f0f3209..85cc019c4 100644 --- a/variants/tracksenger/oled/variant.h +++ b/variants/esp32s3/tracksenger/oled/variant.h @@ -74,7 +74,7 @@ #define SX126X_DIO3_TCXO_VOLTAGE 1.8 // Picomputer gets a white on black display -#define TFT_MESH COLOR565(0xFF, 0xFF, 0xFF) +#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255) // keyboard changes diff --git a/variants/tracksenger/platformio.ini b/variants/esp32s3/tracksenger/platformio.ini similarity index 85% rename from variants/tracksenger/platformio.ini rename to variants/esp32s3/tracksenger/platformio.ini index b36b9c45a..0e9f08541 100644 --- a/variants/tracksenger/platformio.ini +++ b/variants/esp32s3/tracksenger/platformio.ini @@ -5,7 +5,8 @@ board_build.partitions = default_8MB.csv upload_protocol = esp-builtin build_flags = - ${esp32s3_base.build_flags} -I variants/tracksenger/internal + ${esp32s3_base.build_flags} + -I variants/esp32s3/tracksenger/internal -D HELTEC_TRACKER_V1_1 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output @@ -21,7 +22,8 @@ board_build.partitions = default_8MB.csv upload_protocol = esp-builtin build_flags = - ${esp32s3_base.build_flags} -I variants/tracksenger/lcd + ${esp32s3_base.build_flags} + -I variants/esp32s3/tracksenger/lcd -D HELTEC_TRACKER_V1_1 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output @@ -37,7 +39,8 @@ board_build.partitions = default_8MB.csv upload_protocol = esp-builtin build_flags = - ${esp32s3_base.build_flags} -I variants/tracksenger/oled + ${esp32s3_base.build_flags} + -I variants/esp32s3/tracksenger/oled -D HELTEC_TRACKER_V1_1 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output diff --git a/variants/unphone/pins_arduino.h b/variants/esp32s3/unphone/pins_arduino.h similarity index 100% rename from variants/unphone/pins_arduino.h rename to variants/esp32s3/unphone/pins_arduino.h diff --git a/variants/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini similarity index 93% rename from variants/unphone/platformio.ini rename to variants/esp32s3/unphone/platformio.ini index f286c3d4c..ecb1cbd67 100644 --- a/variants/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -11,7 +11,7 @@ monitor_filters = esp32_exception_decoder build_flags = ${esp32s3_base.build_flags} -D UNPHONE - -I variants/unphone + -I variants/esp32s3/unphone -D ARDUINO_USB_MODE=0 -D UNPHONE_ACCEL=0 -D UNPHONE_TOUCHS=0 @@ -23,7 +23,7 @@ build_flags = build_src_filter = ${esp32s3_base.build_src_filter} - +<../variants/unphone> + +<../variants/esp32s3/unphone> lib_deps = ${esp32s3_base.lib_deps} lovyan03/LovyanGFX@ 1.2.0 @@ -56,6 +56,7 @@ build_flags = -D LOG_DEBUG_INC=\"DebugConfiguration.h\" -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 + -D DISPLAY_SIZE=320x480 ; portrait mode -D LGFX_DRIVER=LGFX_UNPHONE_V9 -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_UNPHONE.h\" -D VIEW_320x240 diff --git a/variants/unphone/variant.cpp b/variants/esp32s3/unphone/variant.cpp similarity index 100% rename from variants/unphone/variant.cpp rename to variants/esp32s3/unphone/variant.cpp diff --git a/variants/unphone/variant.h b/variants/esp32s3/unphone/variant.h similarity index 100% rename from variants/unphone/variant.h rename to variants/esp32s3/unphone/variant.h diff --git a/variants/portduino-buildroot/platformio.ini b/variants/native/portduino-buildroot/platformio.ini similarity index 75% rename from variants/portduino-buildroot/platformio.ini rename to variants/native/portduino-buildroot/platformio.ini index 3fbd26910..d1bd39e10 100644 --- a/variants/portduino-buildroot/platformio.ini +++ b/variants/native/portduino-buildroot/platformio.ini @@ -2,7 +2,7 @@ extends = portduino_base ; Optional libraries should be appended to `PLATFORMIO_BUILD_FLAGS` ; environment variable in the buildroot environment. -build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino-buildroot +build_flags = ${portduino_base.build_flags} -O0 -I variants/native/portduino-buildroot board = buildroot lib_deps = ${portduino_base.lib_deps} build_src_filter = ${portduino_base.build_src_filter} \ No newline at end of file diff --git a/variants/portduino-buildroot/variant.h b/variants/native/portduino-buildroot/variant.h similarity index 100% rename from variants/portduino-buildroot/variant.h rename to variants/native/portduino-buildroot/variant.h diff --git a/variants/portduino/platformio.ini b/variants/native/portduino/platformio.ini similarity index 97% rename from variants/portduino/platformio.ini rename to variants/native/portduino/platformio.ini index 5293b12b9..732b2a1d4 100644 --- a/variants/portduino/platformio.ini +++ b/variants/native/portduino/platformio.ini @@ -1,6 +1,6 @@ [native_base] extends = portduino_base -build_flags = ${portduino_base.build_flags} -I variants/portduino +build_flags = ${portduino_base.build_flags} -I variants/native/portduino -D ARCH_PORTDUINO -I /usr/include board = cross_platform diff --git a/variants/portduino/variant.h b/variants/native/portduino/variant.h similarity index 100% rename from variants/portduino/variant.h rename to variants/native/portduino/variant.h diff --git a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini similarity index 67% rename from variants/Dongle_nRF52840-pca10059-v1/platformio.ini rename to variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini index ad944779d..83044c206 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -2,11 +2,13 @@ board_level = extra extends = nrf52840_base board = nordic_pca10059 -build_flags = ${nrf52840_base.build_flags} -Ivariants/Dongle_nRF52840-pca10059-v1 -D NORDIC_PCA10059 +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/Dongle_nRF52840-pca10059-v1 + -D NORDIC_PCA10059 -DEINK_DISPLAY_MODEL=GxEPD2_420_M01 -DEINK_WIDTH=300 -DEINK_HEIGHT=400 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/Dongle_nRF52840-pca10059-v1> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/Dongle_nRF52840-pca10059-v1> lib_deps = ${nrf52840_base.lib_deps} zinggjm/GxEPD2@^1.6.2 diff --git a/variants/Dongle_nRF52840-pca10059-v1/variant.cpp b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.cpp similarity index 100% rename from variants/Dongle_nRF52840-pca10059-v1/variant.cpp rename to variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.cpp diff --git a/variants/Dongle_nRF52840-pca10059-v1/variant.h b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h similarity index 100% rename from variants/Dongle_nRF52840-pca10059-v1/variant.h rename to variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h diff --git a/variants/ELECROW-ThinkNode-M1/nicheGraphics.h b/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h similarity index 97% rename from variants/ELECROW-ThinkNode-M1/nicheGraphics.h rename to variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h index f3b709261..f64de9d07 100644 --- a/variants/ELECROW-ThinkNode-M1/nicheGraphics.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h @@ -56,7 +56,8 @@ void setupNicheGraphics() inkhud->setDisplayResilience(10, 1.5); // Select fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings @@ -103,11 +104,11 @@ void setupNicheGraphics() buttons->setHandlerDown(1, [backlight]() { backlight->peek(); }); buttons->setHandlerLongPress(1, [backlight]() { backlight->latch(); - playBeep(); + playBoop(); }); buttons->setHandlerShortPress(1, [backlight]() { backlight->off(); - playBoop(); + playChirp(); }); // Begin handling button events diff --git a/variants/ELECROW-ThinkNode-M1/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini similarity index 88% rename from variants/ELECROW-ThinkNode-M1/platformio.ini rename to variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini index 2e9a20dfe..0578bcfe8 100644 --- a/variants/ELECROW-ThinkNode-M1/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini @@ -6,7 +6,8 @@ board_check = true debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. -build_flags = ${nrf52840_base.build_flags} -Ivariants/ELECROW-ThinkNode-M1 +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/ELECROW-ThinkNode-M1 -DELECROW_ThinkNode_M1 -DGPS_POWER_TOGGLE -DUSE_EINK @@ -20,7 +21,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/ELECROW-ThinkNode-M1 ; -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/ELECROW-ThinkNode-M1> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M1> lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/GxEPD2/archive/33db3fa8ee6fc47d160bdb44f8f127c9a9203a10.zip @@ -36,11 +37,12 @@ debug_tool = jlink build_flags = ${nrf52840_base.build_flags} ${inkhud.build_flags} - -I variants/ELECROW-ThinkNode-M1 + -I variants/nrf52840/ELECROW-ThinkNode-M1 -D ELECROW_ThinkNode_M1 build_src_filter = ${nrf52_base.build_src_filter} ${inkhud.build_src_filter} + +<../variants/nrf52840/ELECROW-ThinkNode-M1> lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${nrf52840_base.lib_deps} diff --git a/variants/ELECROW-ThinkNode-M1/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp similarity index 100% rename from variants/ELECROW-ThinkNode-M1/variant.cpp rename to variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp diff --git a/variants/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h similarity index 100% rename from variants/ELECROW-ThinkNode-M1/variant.h rename to variants/nrf52840/ELECROW-ThinkNode-M1/variant.h diff --git a/variants/ME25LS01-4Y10TD/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini similarity index 76% rename from variants/ME25LS01-4Y10TD/platformio.ini rename to variants/nrf52840/ME25LS01-4Y10TD/platformio.ini index b452f0ad8..89a45694c 100644 --- a/variants/ME25LS01-4Y10TD/platformio.ini +++ b/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini @@ -3,10 +3,14 @@ extends = nrf52840_base board = me25ls01-4y10td board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/ME25LS01-4Y10TD -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/ME25LS01-4Y10TD + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DME25LS01_4Y10TD -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/ME25LS01-4Y10TD> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS01-4Y10TD> lib_deps = ${nrf52840_base.lib_deps} ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) diff --git a/variants/ME25LS01-4Y10TD/rfswitch.h b/variants/nrf52840/ME25LS01-4Y10TD/rfswitch.h similarity index 100% rename from variants/ME25LS01-4Y10TD/rfswitch.h rename to variants/nrf52840/ME25LS01-4Y10TD/rfswitch.h diff --git a/variants/ME25LS01-4Y10TD/variant.cpp b/variants/nrf52840/ME25LS01-4Y10TD/variant.cpp similarity index 100% rename from variants/ME25LS01-4Y10TD/variant.cpp rename to variants/nrf52840/ME25LS01-4Y10TD/variant.cpp diff --git a/variants/ME25LS01-4Y10TD/variant.h b/variants/nrf52840/ME25LS01-4Y10TD/variant.h similarity index 100% rename from variants/ME25LS01-4Y10TD/variant.h rename to variants/nrf52840/ME25LS01-4Y10TD/variant.h diff --git a/variants/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini similarity index 78% rename from variants/ME25LS01-4Y10TD_e-ink/platformio.ini rename to variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini index f9788a521..ad5867bd5 100644 --- a/variants/ME25LS01-4Y10TD_e-ink/platformio.ini +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini @@ -3,13 +3,17 @@ extends = nrf52840_base board = me25ls01-4y10td board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/ME25LS01-4Y10TD_e-ink -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DME25LS01_4Y10TD +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/ME25LS01-4Y10TD_e-ink + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DME25LS01_4Y10TD -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_420_GDEY042T81 -DEINK_WIDTH=400 -DEINK_HEIGHT=300 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/ME25LS01-4Y10TD_e-ink> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS01-4Y10TD_e-ink> lib_deps = ${nrf52840_base.lib_deps} zinggjm/GxEPD2@^1.6.2 diff --git a/variants/ME25LS01-4Y10TD_e-ink/rfswitch.h b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/rfswitch.h similarity index 100% rename from variants/ME25LS01-4Y10TD_e-ink/rfswitch.h rename to variants/nrf52840/ME25LS01-4Y10TD_e-ink/rfswitch.h diff --git a/variants/ME25LS01-4Y10TD_e-ink/variant.cpp b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp similarity index 100% rename from variants/ME25LS01-4Y10TD_e-ink/variant.cpp rename to variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp diff --git a/variants/ME25LS01-4Y10TD_e-ink/variant.h b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h similarity index 100% rename from variants/ME25LS01-4Y10TD_e-ink/variant.h rename to variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h diff --git a/variants/MS24SF1/platformio.ini b/variants/nrf52840/MS24SF1/platformio.ini similarity index 79% rename from variants/MS24SF1/platformio.ini rename to variants/nrf52840/MS24SF1/platformio.ini index 10e8d2c95..f162cbd60 100644 --- a/variants/MS24SF1/platformio.ini +++ b/variants/nrf52840/MS24SF1/platformio.ini @@ -3,10 +3,13 @@ extends = nrf52840_base board = ms24sf1 board_level = extra ; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e -build_flags = ${nrf52840_base.build_flags} -Ivariants/MS24SF1 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/MS24SF1 + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/MS24SF1> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/MS24SF1> lib_deps = ${nrf52840_base.lib_deps} ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) diff --git a/variants/MS24SF1/variant.cpp b/variants/nrf52840/MS24SF1/variant.cpp similarity index 100% rename from variants/MS24SF1/variant.cpp rename to variants/nrf52840/MS24SF1/variant.cpp diff --git a/variants/MS24SF1/variant.h b/variants/nrf52840/MS24SF1/variant.h similarity index 100% rename from variants/MS24SF1/variant.h rename to variants/nrf52840/MS24SF1/variant.h diff --git a/variants/MakePython_nRF52840_eink/platformio.ini b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini similarity index 77% rename from variants/MakePython_nRF52840_eink/platformio.ini rename to variants/nrf52840/MakePython_nRF52840_eink/platformio.ini index ef97172e9..50e5495f0 100644 --- a/variants/MakePython_nRF52840_eink/platformio.ini +++ b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini @@ -2,12 +2,14 @@ board_level = extra extends = nrf52840_base board = nordic_pca10059 -build_flags = ${nrf52840_base.build_flags} -Ivariants/MakePython_nRF52840_eink -D PRIVATE_HW +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/MakePython_nRF52840_eink + -D PRIVATE_HW -D PIN_EINK_EN -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D -DEINK_WIDTH=296 -DEINK_HEIGHT=128 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/MakePython_nRF52840_eink> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/MakePython_nRF52840_eink> lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip diff --git a/variants/MakePython_nRF52840_eink/variant.cpp b/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp similarity index 100% rename from variants/MakePython_nRF52840_eink/variant.cpp rename to variants/nrf52840/MakePython_nRF52840_eink/variant.cpp diff --git a/variants/MakePython_nRF52840_eink/variant.h b/variants/nrf52840/MakePython_nRF52840_eink/variant.h similarity index 100% rename from variants/MakePython_nRF52840_eink/variant.h rename to variants/nrf52840/MakePython_nRF52840_eink/variant.h diff --git a/variants/MakePython_nRF52840_oled/platformio.ini b/variants/nrf52840/MakePython_nRF52840_oled/platformio.ini similarity index 69% rename from variants/MakePython_nRF52840_oled/platformio.ini rename to variants/nrf52840/MakePython_nRF52840_oled/platformio.ini index 57b9ecb79..c7418e53c 100644 --- a/variants/MakePython_nRF52840_oled/platformio.ini +++ b/variants/nrf52840/MakePython_nRF52840_oled/platformio.ini @@ -2,8 +2,10 @@ board_level = extra extends = nrf52840_base board = nordic_pca10059 -build_flags = ${nrf52840_base.build_flags} -Ivariants/MakePython_nRF52840_oled -D PRIVATE_HW -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/MakePython_nRF52840_oled> +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/MakePython_nRF52840_oled + -D PRIVATE_HW +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/MakePython_nRF52840_oled> lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip diff --git a/variants/MakePython_nRF52840_oled/variant.cpp b/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp similarity index 100% rename from variants/MakePython_nRF52840_oled/variant.cpp rename to variants/nrf52840/MakePython_nRF52840_oled/variant.cpp diff --git a/variants/MakePython_nRF52840_oled/variant.h b/variants/nrf52840/MakePython_nRF52840_oled/variant.h similarity index 100% rename from variants/MakePython_nRF52840_oled/variant.h rename to variants/nrf52840/MakePython_nRF52840_oled/variant.h diff --git a/variants/TWC_mesh_v4/platformio.ini b/variants/nrf52840/TWC_mesh_v4/platformio.ini similarity index 59% rename from variants/TWC_mesh_v4/platformio.ini rename to variants/nrf52840/TWC_mesh_v4/platformio.ini index 2eb58bf9f..77aeee26e 100644 --- a/variants/TWC_mesh_v4/platformio.ini +++ b/variants/nrf52840/TWC_mesh_v4/platformio.ini @@ -2,8 +2,10 @@ extends = nrf52840_base board = nordic_pca10059 board_level = extra -build_flags = ${nrf52840_base.build_flags} -I variants/TWC_mesh_v4 -D TWC_mesh_v4 -L".pio\libdeps\TWC_mesh_v4\bsec2\src\cortex-m4\fpv4-sp-d16-hard" -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/TWC_mesh_v4> +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/TWC_mesh_v4 + -D TWC_mesh_v4 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/TWC_mesh_v4> lib_deps = ${nrf52840_base.lib_deps} zinggjm/GxEPD2@^1.6.2 diff --git a/variants/TWC_mesh_v4/variant.cpp b/variants/nrf52840/TWC_mesh_v4/variant.cpp similarity index 100% rename from variants/TWC_mesh_v4/variant.cpp rename to variants/nrf52840/TWC_mesh_v4/variant.cpp diff --git a/variants/TWC_mesh_v4/variant.h b/variants/nrf52840/TWC_mesh_v4/variant.h similarity index 100% rename from variants/TWC_mesh_v4/variant.h rename to variants/nrf52840/TWC_mesh_v4/variant.h diff --git a/variants/canaryone/platformio.ini b/variants/nrf52840/canaryone/platformio.ini similarity index 78% rename from variants/canaryone/platformio.ini rename to variants/nrf52840/canaryone/platformio.ini index ad11305db..251937e9c 100644 --- a/variants/canaryone/platformio.ini +++ b/variants/nrf52840/canaryone/platformio.ini @@ -5,8 +5,10 @@ board = canaryone debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. -build_flags = ${nrf52840_base.build_flags} -Ivariants/canaryone -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/canaryone> +build_flags = + ${nrf52840_base.build_flags} + -I variants/nrf52840/canaryone +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/canaryone> lib_deps = ${nrf52840_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 diff --git a/variants/canaryone/variant.cpp b/variants/nrf52840/canaryone/variant.cpp similarity index 100% rename from variants/canaryone/variant.cpp rename to variants/nrf52840/canaryone/variant.cpp diff --git a/variants/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h similarity index 100% rename from variants/canaryone/variant.h rename to variants/nrf52840/canaryone/variant.h diff --git a/variants/nrf52840/diy/WashTastic/platformio.ini b/variants/nrf52840/diy/WashTastic/platformio.ini new file mode 100644 index 000000000..881b961e1 --- /dev/null +++ b/variants/nrf52840/diy/WashTastic/platformio.ini @@ -0,0 +1,13 @@ +; Promicro + E22900M30S +[env:WashTastic] +extends = nrf52840_base +board = promicro-nrf52840 +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/diy/nrf52_promicro_diy_tcxo + -D PRIVATE_HW + -D EBYTE_E22_900M30S +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/nrf52_promicro_diy_tcxo> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink diff --git a/variants/diy/nrf52_promicro_diy_tcxo/E80_RSSI_per_case.webp b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/E80_RSSI_per_case.webp similarity index 100% rename from variants/diy/nrf52_promicro_diy_tcxo/E80_RSSI_per_case.webp rename to variants/nrf52840/diy/nrf52_promicro_diy_tcxo/E80_RSSI_per_case.webp diff --git a/variants/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf similarity index 100% rename from variants/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf rename to variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf diff --git a/variants/diy/nrf52_promicro_diy_tcxo/custom_build_tasks.py b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/custom_build_tasks.py similarity index 100% rename from variants/diy/nrf52_promicro_diy_tcxo/custom_build_tasks.py rename to variants/nrf52840/diy/nrf52_promicro_diy_tcxo/custom_build_tasks.py diff --git a/variants/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h similarity index 96% rename from variants/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h rename to variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h index bbd530595..8f30a244f 100644 --- a/variants/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h @@ -55,8 +55,9 @@ void setupNicheGraphics() // Set how many FAST updates per FULL update. inkhud->setDisplayResilience(INKHUD_BUILDCONF_DISPLAYRESILIENCE); // Suggest roughly ten - // Prepare fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + // Select fonts + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Init settings, and customize defaults diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/platformio.ini b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/platformio.ini new file mode 100644 index 000000000..61a6eda07 --- /dev/null +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/platformio.ini @@ -0,0 +1,32 @@ +; Promicro + E22(0)-xxxM / HT-RA62 modules board variant - DIY - with TCXO +[env:nrf52_promicro_diy_tcxo] +extends = nrf52840_base +board = promicro-nrf52840 +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/diy/nrf52_promicro_diy_tcxo + -D NRF52_PROMICRO_DIY +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/nrf52_promicro_diy_tcxo> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink + +; NRF52 ProMicro w/ E-Ink display +[env:nrf52_promicro_diy-inkhud] +board_level = extra +extends = nrf52840_base, inkhud +board = promicro-nrf52840 +build_flags = + ${nrf52840_base.build_flags} + ${inkhud.build_flags} + -I variants/nrf52840/diy/nrf52_promicro_diy_tcxo + -D NRF52_PROMICRO_DIY +build_src_filter = + ${nrf52_base.build_src_filter} + ${inkhud.build_src_filter} + +<../variants/nrf52840/diy/nrf52_promicro_diy_tcxo> +lib_deps = + ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX + ${nrf52840_base.lib_deps} +extra_scripts = + ${env.extra_scripts} + variants/nrf52840/diy/nrf52_promicro_diy_tcxo/custom_build_tasks.py ; Add to PIO's Project Tasks pane: preset builds for common displays diff --git a/variants/diy/nrf52_promicro_diy_tcxo/readme.md b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md similarity index 99% rename from variants/diy/nrf52_promicro_diy_tcxo/readme.md rename to variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md index 585ac36de..5a78103ee 100644 --- a/variants/diy/nrf52_promicro_diy_tcxo/readme.md +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md @@ -1,3 +1,5 @@ + + # Notes ## General diff --git a/variants/diy/nrf52_promicro_diy_tcxo/rfswitch.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/rfswitch.h similarity index 100% rename from variants/diy/nrf52_promicro_diy_tcxo/rfswitch.h rename to variants/nrf52840/diy/nrf52_promicro_diy_tcxo/rfswitch.h diff --git a/variants/diy/nrf52_promicro_diy_tcxo/variant.cpp b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp similarity index 100% rename from variants/diy/nrf52_promicro_diy_tcxo/variant.cpp rename to variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp diff --git a/variants/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h similarity index 100% rename from variants/diy/nrf52_promicro_diy_tcxo/variant.h rename to variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini new file mode 100644 index 000000000..278f578c5 --- /dev/null +++ b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini @@ -0,0 +1,12 @@ +; Promicro + E22(0)-xxxMM / RA-01SH modules board variant - DIY - without TCXO +[env:nrf52_promicro_diy_xtal] +extends = nrf52840_base +board = promicro-nrf52840 +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/diy/nrf52_promicro_diy_xtal + -D NRF52_PROMICRO_DIY +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/nrf52_promicro_diy_xtal> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink diff --git a/variants/diy/nrf52_promicro_diy_xtal/variant.cpp b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp similarity index 100% rename from variants/diy/nrf52_promicro_diy_xtal/variant.cpp rename to variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp diff --git a/variants/diy/nrf52_promicro_diy_xtal/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h similarity index 100% rename from variants/diy/nrf52_promicro_diy_xtal/variant.h rename to variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h diff --git a/variants/diy/seeed-xiao-nrf52840-wio-sx1262/README.md b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md similarity index 100% rename from variants/diy/seeed-xiao-nrf52840-wio-sx1262/README.md rename to variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini new file mode 100644 index 000000000..2df31d23c --- /dev/null +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini @@ -0,0 +1,15 @@ +; Seeed XIAO nRF52840 + XIAO Wio SX1262 DIY +[env:seeed-xiao-nrf52840-wio-sx1262] +board = xiao_ble_sense +extends = nrf52840_base +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262 + -D PRIVATE_HW + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink diff --git a/variants/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp similarity index 100% rename from variants/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp rename to variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp diff --git a/variants/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h similarity index 100% rename from variants/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h rename to variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h diff --git a/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini b/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini new file mode 100644 index 000000000..a5d0aaf8f --- /dev/null +++ b/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini @@ -0,0 +1,19 @@ +; Seeed XIAO nRF52840 + EBYTE E22-900M30S - Pinout matching Wio-SX1262 (SKU 113010003) +[env:seeed_xiao_nrf52840_e22_900m30s] +extends = env:seeed_xiao_nrf52840_kit +board_level = extra +build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} + -D PRIVATE_HW + -DEBYTE_E22 + -DEBYTE_E22_900M30S +build_unflags = -DGPS_L76K + +; Seeed XIAO nRF52840 + EBYTE E22-900M33S - Pinout matching Wio-SX1262 (SKU 113010003) +[env:seeed_xiao_nrf52840_e22_900m33s] +extends = env:seeed_xiao_nrf52840_kit +board_level = extra +build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} + -D PRIVATE_HW + -DEBYTE_E22 + -DEBYTE_E22_900M33S +build_unflags = -DGPS_L76K diff --git a/variants/nrf52840/diy/xiao_ble/README.md b/variants/nrf52840/diy/xiao_ble/README.md new file mode 100644 index 000000000..fe6dcba2d --- /dev/null +++ b/variants/nrf52840/diy/xiao_ble/README.md @@ -0,0 +1,168 @@ +# XIAO nrf52840/nrf52840 Sense + Ebyte E22-900M30S + +_A step-by-step guide for macOS and Linux._ + +## Introduction + +This guide will walk you through everything needed to get the XIAO nrf52840 (or XIAO nrf52840 Sense) running Meshtastic using an Ebyte E22-900M30S LoRa module. The combination of the E22 with an nRF52840 MCU is desirable because it allows for both very low idle (Rx) power draw _and_ high transmit power. + +The XIAO nrf52840 is a small but surprisingly well-appointed nRF52840 board, with enough GPIO for most Meshtastic applications and a built-in LiPo charger. + +The E22, on the other hand, is a famously inscrutable and mysterious beast. It is one of the more readily available LoRa modules capable of transmitting at 30 dBm, and includes an LNA to boost its Rx sensitivity a few dB beyond that of the SX1262. + +However, its documentation is relatively sparse overall, and seems to merely hint at (or completely omit) several key details regarding its functionality. Thus, much of what follows is a synthesis of my observations and inferences over the course of many hours of trial and error. + +### Acknowledgement and Friendly Disclaimer + +Huge thanks to those in the community who have forged the way with the E22, without whose hard work none of this would have been possible! (thebentern, riddick, rainer_vie, beegee-tokyo, geeksville, caveman99, Der_Bear, PlumRugOfDoom, BigCorvus, and many others.) + +Please take the conclusions here as a tentative work in progress, representing my current (and fairly limited) understanding of the E22 when paired with this particular MCU. It is my hope that this guide will be helpful to others who are interested in trying a DIY Meshtastic build, and also be subject to revision by folks with more experience and better test equipment. + +### Obligatory Liability Disclaimer + +This guide and all associated content is for informational purposes only. The information presented is intended for consumption only by persons having appropriate technical skill and judgement, to be used entirely at their own discretion and risk. The authors of this guide in no way provide any warranty, express or implied, toward the content herein, nor its correctness, safety, or suitability to any particular purpose. By following the instructions in this guide in part or in full, you assume all responsibility for all potential risks, including but not limited to fire, property damage, bodily injury, and death. + +## 1. Wire the board + +Connecting the E22 to the XIAO nrf52840 is straightforward, but there are a few gotchas to be mindful of. + +### On the XIAO nrf52840 + +- Pins D4 and D5 are currently mapped to `PIN_WIRE_SDA` and `PIN_WIRE_SCL`, respectively. If you are not using I²C and would like to free up pins D4 and D5 for use as GPIO, `PIN_WIRE_SDA` and `PIN_WIRE_SCL` can be reassigned to any two other unused pin numbers. +- Pins D6 and D7 were originally mapped to the TX and RX pins for serial interface 1 (`PIN_SERIAL1_RX` and `PIN_SERIAL1_TX`) but are currently set to -1 in `variant.h`. If you need to expose a serial interface, you can restore these pins and move e.g. `SX126X_RXEN` to pin 4 or 5 (the opposite should work too). + +### On the E22 + +- There are two options for the E22's `TXEN` pin: + 1. It can be connected to the MCU on the pin defined as `SX126X_TXEN` in `variant.h`. In this configuration, the MCU will control Tx/Rx switching "manually". As long as `SX126X_TXEN` and `SX126X_RXEN` are both defined in `variant.h` (and neither is set to `RADIOLIB_NC`), `SX126xInterface.cpp` will initialize the E22 correctly for this mode. + 2. Alternately, it can be connected to the E22's `DIO2` pin only, with neither `TXEN` nor `DIO2` being connected to the MCU. In this configuration, the E22 will control Tx/Rx switching automatically. In `variant.h`, as long as `SX126X_TXEN` is defined as `RADIOLIB_NC`, and `SX126X_RXEN` is defined and connected to the E22's `RXEN` pin, and `E22_TXEN_CONNECTED_TO_DIO2` is defined, `SX126xInterface.cpp` will initialize the E22 correctly for this mode. This configuration frees up a GPIO, and presents no drawbacks that I have found. +- Note that any combination other than the two described above will likely result in unexpected behavior. In my testing, some of these other configurations appeared to "work" at first glance, but every one I tried had at least one of the following flaws: weak Tx power, extremely poor Rx sensitivity, or the E22 overheating because TXEN was never pulled low, causing its PA to stay on indefinitely. +- Along the same lines, it is a good idea to check the E22's temperature frequently by lightly touching the shield. If you feel the shield getting hot (i.e. approaching uncomfortable to touch) near pins 1, 2, and 3, something is probably misconfigured; disconnect both the XIAO nrf52840 and E22 from power and double check wiring and pin mapping. +- Whether you opt to let the E22 control Rx and Tx or handle this manually, **the E22's `RXEN` pin must always be connected to the MCU** on the pin defined as `SX126X_RXEN` in `variant.h`. + +#### Note + +The default pin mapping in `variant.h` uses "Automatic Tx/Rx switching" mode. + +If you wire your board for Manual Tx/Rx Switching Mode, `SX126X_TXEN` must be defined (`#define #define SX126X_TXEN D6`) in `variants/seeed_xiao_nrf52840_kit/variant.h` in the code block following: + +```c +#ifdef XIAO_BLE_LEGACY_PINOUT +// Legacy xiao_ble variant pinout for third-party SX126x modules e.g. EBYTE E22 +``` + +### Example Wiring for Automatic Tx/Rx Switching Mode + +#### MCU -> E22 Connections + +| XIAO nrf52840 pin | variant.h definition | E22 pin | Notes | +| :---------------- | :------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------- | +| D0 | SX126X_CS | 19 (NSS) | | +| D1 | SX126X_DIO1 | 13 (DIO1) | | +| D2 | SX126X_BUSY | 14 (BUSY) | | +| D3 | SX126X_RESET | 15 (NRST) | | +| D7 | SX126X_RXEN | 6 (RXEN) | These pins must still be connected, and `SX126X_RXEN` defined in `variant.h`, otherwise Rx sensitivity will be poor. | +| D8 | PIN_SPI_SCK | 18 (SCK) | | +| D9 | PIN_SPI_MISO | 16 (MISO) | | +| D10 | PIN_SPI_MOSI | 17 (MOSI) | | + +#### E22 -> E22 Connections + +| E22 pin | E22 pin | Notes | +| :------ | :------ | :------------------------------------------------------------------------ | +| TXEN | DIO2 | These must be physically connected for automatic Tx/Rx switching to work. | + +#### Note + +The schematic (`xiao-ble-e22-schematic.png`) in the `eagle-project` directory uses this wiring. + +### Example Wiring for Manual Tx/Rx Switching Mode + +#### MCU -> E22 Connections + +| XIAO nrf52840 pin | variant.h definition | E22 pin | Notes | +| :---------------- | :------------------- | :-------- | :---- | +| D0 | SX126X_CS | 19 (NSS) | | +| D1 | SX126X_DIO1 | 13 (DIO1) | | +| D2 | SX126X_BUSY | 14 (BUSY) | | +| D3 | SX126X_RESET | 15 (NRST) | | +| D6 | SX126X_TXEN | 7 (TXEN) | | +| D7 | SX126X_RXEN | 6 (RXEN) | | +| D8 | PIN_SPI_SCK | 18 (SCK) | | +| D9 | PIN_SPI_MISO | 16 (MISO) | | +| D10 | PIN_SPI_MOSI | 17 (MOSI) | | + +#### E22 -> E22 connections + +_(none)_ + +## 2. Build Meshtastic + +1. Follow the [Building Meshtastic Firmware](https://meshtastic.org/docs/development/firmware/build/) documentation, stop after **Build** → **Step 2** +2. For **Build** → **Step 3**, select `xiao_ble` as your target +3. Adjust source code if you: + - Wired your board for Manual Tx/Rx Switching Mode: see [Wire the Board](#1-wire-the-board) + - Used an E22-900M33S module + (this step is important to avoid **damaging the power amplifier** in the M33S module and **transmitting power above legal limits**!): + 1. Open `variants/diy/platformio.ini` + 2. Search for `[env:xiao_ble]` + 3. In the line starting with `build_flags` within this section, change `-DEBYTE_E22_900M30S` to `-DEBYTE_E22_900M33S` +4. Follow **Build** → **Step 4** to build the firmware +5. Stop here, because the **PlatformIO: Upload** step does not work for factory-fresh XIAO nrf52840 (the automatic reset to bootloader only works if Meshtastic firmware is already running) +6. The built `firmware.uf2` binary can be found in the folder `.pio/build/xiao_ble/firmware.uf2` (relative to where you cloned the Git repository to), we will need it for [flashing the firmware](#3-flash-the-firmware-to-the-xiao-nrf52840) (manually) + +## 3. Flash the Firmware to the XIAO nrf52840 + +1. Double press the XIAO nrf52840's `reset` button to put it in bootloader mode, and a USB volume named `XIAO SENSE` will appear +2. Copy the `firmware.uf2` file to the `XIAO SENSE` volume (refer to the last step of [Build Meshtastic](#2-build-meshtastic)) +3. The XIAO nrf52840's red LED will flash for several seconds as the firmware is copied +4. Once Meshtastic firmware succesfully boots, the: + 1. Green LED will turn on + 2. Red LED will flash several times to indicate flash memory writes during initial settings file creation + 3. Green LED will blink every second once the firmware is running normally +5. If you do not see the above LED patters, proceed to [Troubleshooting](#4-troubleshooting) + +## 4. Troubleshooting + +- If after flashing Meshtastic, the XIAO is bootlooped, look at the serial output (you can see this by running `meshtastic --noproto` with the device connected to your computer via USB). + - If you see that the SX1262 init result was -2, this likely indicates a wiring problem; double check your wiring and pin mapping in `variant.h`. + - If you see an error mentioning tinyFS, this may mean you need to reformat the XIAO's storage: + 1. Open the [Meshtastic web flasher](https://flasher.meshtastic.org/) + 2. Select the **_Seeed XIAO NRF52840 Kit_** + 3. Click the **_trash can icon_** to the right of **_Flash_** + 4. Follow the instructions on the screen + **Do not flash the Seeed XIAO NRF52840 Kit firmware** if you have wired the LoRa module according to this variant, as the Seeed XIAO NRF52840 Kit uses different wiring for the SX1262 LoRa chip + - If you don't see any specific error message, but the boot process is stuck or not proceeding as expected, this might also mean there is a conflict in `variant.h`. If you have made any changes to the pin mapping, ensure they do not result in a conflict. If all else fails, try reverting your changes and using the known-good configuration included here. + - The above might also mean something is wired incorrectly. Try reverting to one of the known-good example wirings in section 4. +- If the E22 gets hot to the touch: + - The power amplifier is likely running continually. Disconnect it and the XIAO from power immediately, and double check wiring and pin mapping. In my experimentation this occurred in cases where TXEN was inadvertenly high (usually due to a pin mapping conflict). + +## 5. Notes + +- **Transmit Power** + - There is a power amplifier after the SX1262's Tx, so the actual Tx power is just over 7 dB greater than the SX1262's set Tx power (the E22-900M30S actually tops out just over 29dB at 5V according to the datasheet) + - Meshtastic firmware is aware of the gain of the E22-900M30S module, so the Meshtastic clients' Tx power setting reflects the actual output power, i.e. setting 30 dBm in the Meshtastic app programs the E22 module to correctly output 30 dBm, setting 24 dBm will output 24 dBm, etc. +- **Adequate 5V Power Supply to the E22 Module** + - Have a bypass capacitor from its 5V supply to ground; 100 µF works well + - Voltage must be between 5V–5.5V, lower supply voltage results in less output power; for example, with a fully charged LiPo at 4.2V, Tx power appears to max out around 26-27 dBm + +### Additional Reading + +- [S5NC/CDEBYTE_Modules](https://github.com/S5NC/CDEBYTE_Modules) has additional information about EBYTE E22 modules' internal workings, including photographs +- [RadioLib High power Radio Modules Guide](https://github.com/jgromes/RadioLib/wiki/High-power-Radio-Modules-Guide) + +## 6. Testing Methodology + +During what became a fairly long trial-and-error process, I did a lot of careful testing of Tx power and Rx sensitivity. My methodology in these tests was as follows: + +- All tests were conducted between two nodes: + 1. The XIAO nrf52840 + E22 coupled with an [Abracon ARRKP4065-S915A](https://www.digikey.com/en/products/detail/abracon-llc/ARRKP4065-S915A/8593263") ceramic patch antenna + 2. A RAK 5005/4631 coupled with a [Laird MA9-5N](https://www.streakwave.com/laird-technologies-ma9-5n-55dbi-900mhz-mobile-omni-select-mount) antenna via a 4" U.FL to Type N pigtail. + - No other nodes were powered up onsite or nearby. +- Each node and its antenna was kept in exactly the same position and orientation throughout testing. +- Other environmental factors (e.g. the location and resting position of my body in the room while testing) were controlled as carefully as possible. +- Each test comprised at least five (and often ten) runs, after which the results were averaged. +- All testing was done by sending single-character messages between nodes and observing the received RSSI reported in the message acknowledgement. Messages were sent one by one, waiting for each to be acknowledged or time out before sending the next. +- The E22's Tx power was observed by sending messages from the RAK to the XIAO nrf52840 + E22 and recording the received RSSI. +- The opposite was done to observe the E22's Rx sensitivity: messages were sent from the XIAO nrf52840 + E22 to the RAK, and the received RSSI was recorded. + While this cannot match the level of accuracy achievable with actual test equipment in a lab setting, it was nonetheless sufficient to demonstrate the (sometimes very large) differences in Tx power and Rx sensitivity between various configurations. diff --git a/variants/nrf52840/diy/xiao_ble/platformio.ini b/variants/nrf52840/diy/xiao_ble/platformio.ini new file mode 100644 index 000000000..6c764ea78 --- /dev/null +++ b/variants/nrf52840/diy/xiao_ble/platformio.ini @@ -0,0 +1,10 @@ +; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893 +[env:xiao_ble] +extends = env:seeed_xiao_nrf52840_kit +board_level = extra +build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} + -D PRIVATE_HW + -DXIAO_BLE_LEGACY_PINOUT + -DEBYTE_E22 + -DEBYTE_E22_900M30S +build_unflags = -DGPS_L76K diff --git a/variants/feather_diy/platformio.ini b/variants/nrf52840/feather_diy/platformio.ini similarity index 79% rename from variants/feather_diy/platformio.ini rename to variants/nrf52840/feather_diy/platformio.ini index 84c582ab0..a17e418a2 100644 --- a/variants/feather_diy/platformio.ini +++ b/variants/nrf52840/feather_diy/platformio.ini @@ -2,8 +2,10 @@ [env:feather_diy] extends = nrf52840_base board = adafruit_feather_nrf52840 -build_flags = ${nrf52840_base.build_flags} -Ivariants/feather_diy -Dfeather_diy -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/feather_diy> +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/feather_diy + -Dfeather_diy +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/feather_diy> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink diff --git a/variants/feather_diy/variant.cpp b/variants/nrf52840/feather_diy/variant.cpp similarity index 100% rename from variants/feather_diy/variant.cpp rename to variants/nrf52840/feather_diy/variant.cpp diff --git a/variants/feather_diy/variant.h b/variants/nrf52840/feather_diy/variant.h similarity index 100% rename from variants/feather_diy/variant.h rename to variants/nrf52840/feather_diy/variant.h diff --git a/variants/gat562_mesh_trial_tracker/platformio.ini b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini similarity index 75% rename from variants/gat562_mesh_trial_tracker/platformio.ini rename to variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini index e67f3ec8d..72ac6320d 100644 --- a/variants/gat562_mesh_trial_tracker/platformio.ini +++ b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini @@ -3,11 +3,13 @@ extends = nrf52840_base board = gat562_mesh_trial_tracker board_check = true -build_flags = ${nrf52840_base.build_flags} -Ivariants/gat562_mesh_trial_tracker -D GAT562_MESH_TRIAL_TRACKER +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/gat562_mesh_trial_tracker + -D GAT562_MESH_TRIAL_TRACKER -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/gat562_mesh_trial_tracker> -lib_deps = +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/gat562_mesh_trial_tracker> +lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/gat562_mesh_trial_tracker/variant.cpp b/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp similarity index 100% rename from variants/gat562_mesh_trial_tracker/variant.cpp rename to variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp diff --git a/variants/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h similarity index 99% rename from variants/gat562_mesh_trial_tracker/variant.h rename to variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 2af0bc76d..6337ac70c 100644 --- a/variants/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -19,8 +19,6 @@ #ifndef _VARIANT_GAT562_MESH_TRIAL_TRACKER_ #define _VARIANT_GAT562_MESH_TRIAL_TRACKER_ -#define GAT562_MESH_TRIAL_TRACKER - // led pin 2 (blue), see https://github.com/meshtastic/firmware/blob/master/src/mesh/NodeDB.cpp#L723 #define RAK4630 diff --git a/variants/heltec_mesh_node_t114-inkhud/custom_build_tasks.py b/variants/nrf52840/heltec_mesh_node_t114-inkhud/custom_build_tasks.py similarity index 100% rename from variants/heltec_mesh_node_t114-inkhud/custom_build_tasks.py rename to variants/nrf52840/heltec_mesh_node_t114-inkhud/custom_build_tasks.py diff --git a/variants/heltec_mesh_node_t114-inkhud/nicheGraphics.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h similarity index 96% rename from variants/heltec_mesh_node_t114-inkhud/nicheGraphics.h rename to variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h index fe1c281bf..b6be70ff4 100644 --- a/variants/heltec_mesh_node_t114-inkhud/nicheGraphics.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h @@ -56,8 +56,9 @@ void setupNicheGraphics() // Set how many FAST updates per FULL update. inkhud->setDisplayResilience(INKHUD_BUILDCONF_DISPLAYRESILIENCE); // Suggest roughly ten - // Prepare fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + // Select fonts + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Init settings, and customize defaults diff --git a/variants/heltec_mesh_node_t114-inkhud/platformio.ini b/variants/nrf52840/heltec_mesh_node_t114-inkhud/platformio.ini similarity index 66% rename from variants/heltec_mesh_node_t114-inkhud/platformio.ini rename to variants/nrf52840/heltec_mesh_node_t114-inkhud/platformio.ini index 9a5673040..2641a507d 100644 --- a/variants/heltec_mesh_node_t114-inkhud/platformio.ini +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/platformio.ini @@ -6,14 +6,15 @@ board_check = true build_flags = ${nrf52840_base.build_flags} ${inkhud.build_flags} - -I variants/heltec_mesh_node_t114-inkhud + -I variants/nrf52840/heltec_mesh_node_t114-inkhud build_src_filter = ${nrf52_base.build_src_filter} ${inkhud.build_src_filter} + +<../variants/nrf52840/heltec_mesh_node_t114-inkhud> lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${nrf52840_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 extra_scripts = ${env.extra_scripts} - variants/diy/nrf52_promicro_diy_tcxo/custom_build_tasks.py ; Add to PIO's Project Tasks pane: preset builds for common displays \ No newline at end of file + variants/nrf52840/diy/nrf52_promicro_diy_tcxo/custom_build_tasks.py ; Add to PIO's Project Tasks pane: preset builds for common displays diff --git a/variants/heltec_mesh_node_t114-inkhud/variant.cpp b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp similarity index 100% rename from variants/heltec_mesh_node_t114-inkhud/variant.cpp rename to variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp diff --git a/variants/heltec_mesh_node_t114-inkhud/variant.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h similarity index 100% rename from variants/heltec_mesh_node_t114-inkhud/variant.h rename to variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h diff --git a/variants/heltec_mesh_node_t114/platformio.ini b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini similarity index 72% rename from variants/heltec_mesh_node_t114/platformio.ini rename to variants/nrf52840/heltec_mesh_node_t114/platformio.ini index 3ba97bd04..c7b30b339 100644 --- a/variants/heltec_mesh_node_t114/platformio.ini +++ b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini @@ -2,14 +2,16 @@ [env:heltec-mesh-node-t114] extends = nrf52840_base board = heltec_mesh_node_t114 +board_level = pr debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. -build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_node_t114 - -DGPS_POWER_TOGGLE - -DHELTEC_T114 +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/heltec_mesh_node_t114 + -DGPS_POWER_TOGGLE + -DHELTEC_T114 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_node_t114> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_node_t114> lib_deps = ${nrf52840_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 diff --git a/variants/heltec_mesh_node_t114/variant.cpp b/variants/nrf52840/heltec_mesh_node_t114/variant.cpp similarity index 100% rename from variants/heltec_mesh_node_t114/variant.cpp rename to variants/nrf52840/heltec_mesh_node_t114/variant.cpp diff --git a/variants/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h similarity index 98% rename from variants/heltec_mesh_node_t114/variant.h rename to variants/nrf52840/heltec_mesh_node_t114/variant.h index 798c3538a..f4f0baf13 100644 --- a/variants/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -57,6 +57,9 @@ extern "C" { #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 +// T114 gets a muted yellow on black display +#define TFT_MESH_OVERRIDE COLOR565(255, 255, 128) + // #define TFT_OFFSET_ROTATION 0 // #define SCREEN_ROTATE // #define SCREEN_TRANSITION_FRAMERATE 5 diff --git a/variants/heltec_mesh_pocket/nicheGraphics.h b/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h similarity index 96% rename from variants/heltec_mesh_pocket/nicheGraphics.h rename to variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h index 271a35d6d..f8202debb 100644 --- a/variants/heltec_mesh_pocket/nicheGraphics.h +++ b/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h @@ -50,7 +50,8 @@ void setupNicheGraphics() inkhud->setDisplayResilience(10, 1.5); // Select fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings diff --git a/variants/heltec_mesh_pocket/platformio.ini b/variants/nrf52840/heltec_mesh_pocket/platformio.ini similarity index 89% rename from variants/heltec_mesh_pocket/platformio.ini rename to variants/nrf52840/heltec_mesh_pocket/platformio.ini index 2f3886887..2fb852226 100644 --- a/variants/heltec_mesh_pocket/platformio.ini +++ b/variants/nrf52840/heltec_mesh_pocket/platformio.ini @@ -5,7 +5,8 @@ board = heltec_mesh_pocket debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. -build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_pocket +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/heltec_mesh_pocket -DHELTEC_MESH_POCKET -DHELTEC_MESH_POCKET_BATTERY_5000 -DUSE_EINK @@ -21,7 +22,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_pocket -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_pocket> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket> lib_deps = ${nrf52840_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 @@ -31,11 +32,11 @@ lib_deps = [env:heltec-mesh-pocket-5000-inkhud] extends = nrf52840_base, inkhud board = heltec_mesh_pocket -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_pocket> ${inkhud.build_src_filter} +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket> ${inkhud.build_src_filter} build_flags = ${inkhud.build_flags} ${nrf52840_base.build_flags} - -I variants/heltec_mesh_pocket + -I variants/nrf52840/heltec_mesh_pocket -D HELTEC_MESH_POCKET -D HELTEC_MESH_POCKET_BATTERY_5000 lib_deps = @@ -50,7 +51,8 @@ board = heltec_mesh_pocket debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. -build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_pocket +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/heltec_mesh_pocket -DHELTEC_MESH_POCKET -DHELTEC_MESH_POCKET_BATTERY_10000 -DUSE_EINK @@ -66,7 +68,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/heltec_mesh_pocket -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_pocket> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket> lib_deps = ${nrf52840_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 @@ -76,11 +78,11 @@ lib_deps = [env:heltec-mesh-pocket-10000-inkhud] extends = nrf52840_base, inkhud board = heltec_mesh_pocket -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/heltec_mesh_pocket> ${inkhud.build_src_filter} +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_pocket> ${inkhud.build_src_filter} build_flags = ${inkhud.build_flags} ${nrf52840_base.build_flags} - -I variants/heltec_mesh_pocket + -I variants/nrf52840/heltec_mesh_pocket -D HELTEC_MESH_POCKET -D HELTEC_MESH_POCKET_BATTERY_10000 lib_deps = diff --git a/variants/heltec_mesh_pocket/variant.cpp b/variants/nrf52840/heltec_mesh_pocket/variant.cpp similarity index 100% rename from variants/heltec_mesh_pocket/variant.cpp rename to variants/nrf52840/heltec_mesh_pocket/variant.cpp diff --git a/variants/heltec_mesh_pocket/variant.h b/variants/nrf52840/heltec_mesh_pocket/variant.h similarity index 100% rename from variants/heltec_mesh_pocket/variant.h rename to variants/nrf52840/heltec_mesh_pocket/variant.h diff --git a/variants/meshlink/platformio.ini b/variants/nrf52840/meshlink/platformio.ini similarity index 94% rename from variants/meshlink/platformio.ini rename to variants/nrf52840/meshlink/platformio.ini index 384858576..8216a704a 100644 --- a/variants/meshlink/platformio.ini +++ b/variants/nrf52840/meshlink/platformio.ini @@ -5,7 +5,9 @@ extends = nrf52840_base board = meshlink ;board_check = true -build_flags = ${nrf52840_base.build_flags} -I variants/meshlink -D MESHLINK +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/meshlink + -D MESHLINK -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D EINK_DISPLAY_MODEL=GxEPD2_213_B74 -D EINK_WIDTH=250 @@ -19,7 +21,7 @@ build_flags = ${nrf52840_base.build_flags} -I variants/meshlink -D MESHLINK -D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/meshlink> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshlink> lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/GxEPD2/archive/55f618961db45a23eff0233546430f1e5a80f63a.zip diff --git a/variants/meshlink/variant.cpp b/variants/nrf52840/meshlink/variant.cpp similarity index 100% rename from variants/meshlink/variant.cpp rename to variants/nrf52840/meshlink/variant.cpp diff --git a/variants/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h similarity index 100% rename from variants/meshlink/variant.h rename to variants/nrf52840/meshlink/variant.h diff --git a/variants/meshlink_eink/platformio.ini b/variants/nrf52840/meshlink_eink/platformio.ini similarity index 93% rename from variants/meshlink_eink/platformio.ini rename to variants/nrf52840/meshlink_eink/platformio.ini index 550b1e2fc..a48a9e695 100644 --- a/variants/meshlink_eink/platformio.ini +++ b/variants/nrf52840/meshlink_eink/platformio.ini @@ -5,7 +5,9 @@ extends = nrf52840_base board = meshlink ;board_check = true -build_flags = ${nrf52840_base.build_flags} -I variants/meshlink_eink -D MESHLINK +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/meshlink_eink + -D MESHLINK -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D EINK_DISPLAY_MODEL=GxEPD2_213_B74 -D EINK_WIDTH=250 @@ -19,7 +21,7 @@ build_flags = ${nrf52840_base.build_flags} -I variants/meshlink_eink -D MESHLINK -D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/meshlink_eink> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshlink_eink> lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/GxEPD2/archive/55f618961db45a23eff0233546430f1e5a80f63a.zip diff --git a/variants/meshlink_eink/variant.cpp b/variants/nrf52840/meshlink_eink/variant.cpp similarity index 100% rename from variants/meshlink_eink/variant.cpp rename to variants/nrf52840/meshlink_eink/variant.cpp diff --git a/variants/meshlink_eink/variant.h b/variants/nrf52840/meshlink_eink/variant.h similarity index 100% rename from variants/meshlink_eink/variant.h rename to variants/nrf52840/meshlink_eink/variant.h diff --git a/variants/monteops_hw1/platformio.ini b/variants/nrf52840/monteops_hw1/platformio.ini similarity index 77% rename from variants/monteops_hw1/platformio.ini rename to variants/nrf52840/monteops_hw1/platformio.ini index 82567f614..5426aee7f 100644 --- a/variants/monteops_hw1/platformio.ini +++ b/variants/nrf52840/monteops_hw1/platformio.ini @@ -3,8 +3,10 @@ board_level = extra extends = nrf52840_base board = wiscore_rak4631 -build_flags = ${nrf52840_base.build_flags} -Ivariants/monteops_hw1 -D MONTEOPS_HW1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/monteops_hw1> + + + +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/monteops_hw1 + -D MONTEOPS_HW1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/monteops_hw1> + + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} diff --git a/variants/monteops_hw1/variant.cpp b/variants/nrf52840/monteops_hw1/variant.cpp similarity index 100% rename from variants/monteops_hw1/variant.cpp rename to variants/nrf52840/monteops_hw1/variant.cpp diff --git a/variants/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h similarity index 100% rename from variants/monteops_hw1/variant.h rename to variants/nrf52840/monteops_hw1/variant.h diff --git a/variants/nano-g2-ultra/platformio.ini b/variants/nrf52840/nano-g2-ultra/platformio.ini similarity index 70% rename from variants/nano-g2-ultra/platformio.ini rename to variants/nrf52840/nano-g2-ultra/platformio.ini index 7da168b47..f697a90dd 100644 --- a/variants/nano-g2-ultra/platformio.ini +++ b/variants/nrf52840/nano-g2-ultra/platformio.ini @@ -4,8 +4,10 @@ extends = nrf52840_base board = nano-g2-ultra debug_tool = jlink -build_flags = ${nrf52840_base.build_flags} -Ivariants/nano-g2-ultra -D NANO_G2_ULTRA -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nano-g2-ultra> +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/nano-g2-ultra + -D NANO_G2_ULTRA +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/nano-g2-ultra> lib_deps = ${nrf52840_base.lib_deps} lewisxhe/PCF8563_Library@^1.0.1 diff --git a/variants/nano-g2-ultra/variant.cpp b/variants/nrf52840/nano-g2-ultra/variant.cpp similarity index 100% rename from variants/nano-g2-ultra/variant.cpp rename to variants/nrf52840/nano-g2-ultra/variant.cpp diff --git a/variants/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h similarity index 100% rename from variants/nano-g2-ultra/variant.h rename to variants/nrf52840/nano-g2-ultra/variant.h diff --git a/variants/rak2560/platformio.ini b/variants/nrf52840/rak2560/platformio.ini similarity index 87% rename from variants/rak2560/platformio.ini rename to variants/nrf52840/rak2560/platformio.ini index 8a720ce5a..2b73aca03 100644 --- a/variants/rak2560/platformio.ini +++ b/variants/nrf52840/rak2560/platformio.ini @@ -3,13 +3,15 @@ extends = nrf52840_base board = wiscore_rak4631 board_check = true -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak2560 -D RAK_4631 +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/rak2560 + -D RAK_4631 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 -DHAS_RAKPROT=1 ; Define if RAk OneWireSerial is used (disables GPS) -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak2560> + + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak2560> + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} diff --git a/variants/rak2560/variant.cpp b/variants/nrf52840/rak2560/variant.cpp similarity index 100% rename from variants/rak2560/variant.cpp rename to variants/nrf52840/rak2560/variant.cpp diff --git a/variants/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h similarity index 99% rename from variants/rak2560/variant.h rename to variants/nrf52840/rak2560/variant.h index a03fc3933..f922e8a61 100644 --- a/variants/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -222,6 +222,7 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // #define PIN_GPS_EN PIN_3V3_EN #define PIN_GPS_PPS (17) // Pulse per second input from the GPS +#define GPS_SERIAL_PORT Serial2 // On RAK2560 the GPS is be on a different UART // #define GPS_RX_PIN PIN_SERIAL2_RX // #define GPS_TX_PIN PIN_SERIAL2_TX diff --git a/variants/rak4631/platformio.ini b/variants/nrf52840/rak4631/platformio.ini similarity index 94% rename from variants/rak4631/platformio.ini rename to variants/nrf52840/rak4631/platformio.ini index ee134e87a..199e17570 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/nrf52840/rak4631/platformio.ini @@ -2,8 +2,11 @@ [env:rak4631] extends = nrf52840_base board = wiscore_rak4631 +board_level = pr board_check = true -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/rak4631 + -D RAK_4631 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 @@ -11,7 +14,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631> + + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} diff --git a/variants/rak4631/variant.cpp b/variants/nrf52840/rak4631/variant.cpp similarity index 100% rename from variants/rak4631/variant.cpp rename to variants/nrf52840/rak4631/variant.cpp diff --git a/variants/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h similarity index 100% rename from variants/rak4631/variant.h rename to variants/nrf52840/rak4631/variant.h diff --git a/variants/rak4631_epaper/platformio.ini b/variants/nrf52840/rak4631_epaper/platformio.ini similarity index 86% rename from variants/rak4631_epaper/platformio.ini rename to variants/nrf52840/rak4631_epaper/platformio.ini index 47e4451c7..704520f8d 100644 --- a/variants/rak4631_epaper/platformio.ini +++ b/variants/nrf52840/rak4631_epaper/platformio.ini @@ -2,14 +2,16 @@ [env:rak4631_eink] extends = nrf52840_base board = wiscore_rak4631 -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/rak4631_epaper + -D RAK_4631 -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631_epaper> lib_deps = ${nrf52840_base.lib_deps} zinggjm/GxEPD2@^1.6.2 diff --git a/variants/rak4631_epaper/variant.cpp b/variants/nrf52840/rak4631_epaper/variant.cpp similarity index 100% rename from variants/rak4631_epaper/variant.cpp rename to variants/nrf52840/rak4631_epaper/variant.cpp diff --git a/variants/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h similarity index 100% rename from variants/rak4631_epaper/variant.h rename to variants/nrf52840/rak4631_epaper/variant.h diff --git a/variants/rak4631_epaper_onrxtx/platformio.ini b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini similarity index 87% rename from variants/rak4631_epaper_onrxtx/platformio.ini rename to variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini index 52a13f2a7..e0156668b 100644 --- a/variants/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini @@ -3,7 +3,9 @@ board_level = extra extends = nrf52840_base board = wiscore_rak4631 -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/rak4631_epaper + -D RAK_4631 -D PIN_EINK_EN=34 -D EINK_DISPLAY_MODEL=GxEPD2_213_BN -D EINK_WIDTH=250 @@ -11,7 +13,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 -D RADIOLIB_EXCLUDE_SX128X=1 -D RADIOLIB_EXCLUDE_SX127X=1 -D RADIOLIB_EXCLUDE_LR11X0=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper_onrxtx> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631_epaper_onrxtx> lib_deps = ${nrf52840_base.lib_deps} zinggjm/GxEPD2@^1.6.2 diff --git a/variants/rak4631_epaper_onrxtx/variant.cpp b/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp similarity index 100% rename from variants/rak4631_epaper_onrxtx/variant.cpp rename to variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp diff --git a/variants/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h similarity index 100% rename from variants/rak4631_epaper_onrxtx/variant.h rename to variants/nrf52840/rak4631_epaper_onrxtx/variant.h diff --git a/variants/rak4631_eth_gw/platformio.ini b/variants/nrf52840/rak4631_eth_gw/platformio.ini similarity index 94% rename from variants/rak4631_eth_gw/platformio.ini rename to variants/nrf52840/rak4631_eth_gw/platformio.ini index 492ca374b..a1c1b4610 100644 --- a/variants/rak4631_eth_gw/platformio.ini +++ b/variants/nrf52840/rak4631_eth_gw/platformio.ini @@ -3,8 +3,11 @@ extends = nrf52840_base board = wiscore_rak4631 board_check = true -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_eth_gw -D RAK_4631 +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/rak4631_eth_gw + -D RAK_4631 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DHAS_UDP_MULTICAST=1 -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 @@ -21,7 +24,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_eth_gw -D RAK_4631 -DMESHTASTIC_EXCLUDE_STOREFORWARD=1 -DMESHTASTIC_EXCLUDE_CANNEDMESSAGES=1 -DMESHTASTIC_EXCLUDE_WAYPOINT=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_eth_gw> + + + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631_eth_gw> + + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} diff --git a/variants/rak4631_eth_gw/variant.cpp b/variants/nrf52840/rak4631_eth_gw/variant.cpp similarity index 100% rename from variants/rak4631_eth_gw/variant.cpp rename to variants/nrf52840/rak4631_eth_gw/variant.cpp diff --git a/variants/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h similarity index 100% rename from variants/rak4631_eth_gw/variant.h rename to variants/nrf52840/rak4631_eth_gw/variant.h diff --git a/variants/rak4631_nomadstar_meteor_pro/platformio.ini b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini similarity index 89% rename from variants/rak4631_nomadstar_meteor_pro/platformio.ini rename to variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini index d5fbe6a16..e94eef1ee 100644 --- a/variants/rak4631_nomadstar_meteor_pro/platformio.ini +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini @@ -3,8 +3,9 @@ extends = nrf52840_base board = wiscore_rak4631 board_check = true -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_nomadstar_meteor_pro -D NOMADSTAR_METEOR_PRO - -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/rak4631_nomadstar_meteor_pro + -D NOMADSTAR_METEOR_PRO ;-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 @@ -12,8 +13,8 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_nomadstar_meteor_p -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_nomadstar_meteor_pro> + + -lib_deps = +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631_nomadstar_meteor_pro> + + +lib_deps = ${nrf52840_base.lib_deps} https://github.com/NomadStar-outdoor/IOBoard-RGB-LP5562-Library.git#9c366c8 diff --git a/variants/rak4631_nomadstar_meteor_pro/variant.cpp b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp similarity index 100% rename from variants/rak4631_nomadstar_meteor_pro/variant.cpp rename to variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp diff --git a/variants/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h similarity index 100% rename from variants/rak4631_nomadstar_meteor_pro/variant.h rename to variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h diff --git a/variants/nrf52840/rak_wismeshtag/platformio.ini b/variants/nrf52840/rak_wismeshtag/platformio.ini new file mode 100644 index 000000000..08e723302 --- /dev/null +++ b/variants/nrf52840/rak_wismeshtag/platformio.ini @@ -0,0 +1,17 @@ +; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +[env:rak_wismeshtag] +extends = nrf52840_base +board = wiscore_rak4631 +board_check = true +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/rak_wismeshtag + -D WISMESH_TAG + -D RAK_4631 + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 + -DMESHTASTIC_EXCLUDE_WIFI=1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak_wismeshtag> +lib_deps = + ${nrf52840_base.lib_deps} \ No newline at end of file diff --git a/variants/nrf52840/rak_wismeshtag/variant.cpp b/variants/nrf52840/rak_wismeshtag/variant.cpp new file mode 100644 index 000000000..e84b60b3b --- /dev/null +++ b/variants/nrf52840/rak_wismeshtag/variant.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/nrf52840/rak_wismeshtag/variant.h b/variants/nrf52840/rak_wismeshtag/variant.h new file mode 100644 index 000000000..eba910dc1 --- /dev/null +++ b/variants/nrf52840/rak_wismeshtag/variant.h @@ -0,0 +1,244 @@ +/* + 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_RAK4630_ +#define _VARIANT_RAK4630_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Buttons + */ + +#define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion +#define BUTTON_NEED_PULLUP +#define PIN_BUTTON2 12 +#define PIN_BUTTON3 24 +#define PIN_BUTTON4 25 + +/* + * Analog pins + */ +#define PIN_A0 (5) +#define PIN_A1 (31) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +#define PIN_SPI1_MISO (29) // (0 + 29) +#define PIN_SPI1_MOSI (30) // (0 + 30) +#define PIN_SPI1_SCK (3) // (0 + 3) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * eink display pins + */ + +#define PIN_EINK_CS (0 + 26) +#define PIN_EINK_BUSY (0 + 4) +#define PIN_EINK_DC (0 + 17) +#define PIN_EINK_RES (-1) +#define PIN_EINK_SCLK (0 + 3) +#define PIN_EINK_MOSI (0 + 30) // also called SDI + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +// RAK WISMESHTAG +#define PIN_WIRE_SDA (25) +#define PIN_WIRE_SCL (24) + +// QSPI Pins +#define PIN_QSPI_SCK 3 +#define PIN_QSPI_CS 26 +#define PIN_QSPI_IO0 30 +#define PIN_QSPI_IO1 29 +#define PIN_QSPI_IO2 28 +#define PIN_QSPI_IO3 2 + +/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports + RAK5005-O <-> nRF52840 + IO1 <-> P0.17 (Arduino GPIO number 17) + IO2 <-> P1.02 (Arduino GPIO number 34) + IO3 <-> P0.21 (Arduino GPIO number 21) + IO4 <-> P0.04 (Arduino GPIO number 4) + IO5 <-> P0.09 (Arduino GPIO number 9) + IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) + SW1 <-> P0.01 (Arduino GPIO number 1) + A0 <-> P0.04/AIN2 (Arduino Analog A2 + A1 <-> P0.31/AIN7 (Arduino Analog A7 + SPI_CS <-> P0.26 (Arduino GPIO number 26) + */ + +// RAK4630 LoRa module + +/* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) + +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) + +Important for successful SX1262 initialization: + +* Setup DIO2 to control the antenna switch +* Setup DIO3 to control the TCXO power supply +* Setup the SX1262 to use it's DCDC regulator and not the LDO +* RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the +control of the antenna switch + +SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG + +*/ + +#define DETECTION_SENSOR_EN 4 + +#define USE_SX1262 +#define SX126X_CS (42) +#define SX126X_DIO1 (47) +#define SX126X_BUSY (46) +#define SX126X_RESET (38) +// #define SX126X_TXEN (39) +// #define SX126X_RXEN (37) +#define SX126X_POWER_EN (37) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Testing USB detection +#define NRF_APM + +// enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings +#define PIN_3V3_EN (34) + +// RAK WISMESHTAG +#define PIN_GPS_EN PIN_3V3_EN +#define PIN_GPS_PPS (17) // Pulse per second input from the GPS + +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +// RAK WISMESHTAG +#define PIN_BUZZER 21 + +// Battery +// The battery sense is hooked to pin A0 (5) +#define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 + +#define RAK_4631 1 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file diff --git a/variants/rak_wismeshtap/platformio.ini b/variants/nrf52840/rak_wismeshtap/platformio.ini similarity index 87% rename from variants/rak_wismeshtap/platformio.ini rename to variants/nrf52840/rak_wismeshtap/platformio.ini index bfb3ea927..f6ee8fd23 100644 --- a/variants/rak_wismeshtap/platformio.ini +++ b/variants/nrf52840/rak_wismeshtap/platformio.ini @@ -2,7 +2,10 @@ [env:rak_wismeshtap] extends = nrf52840_base board = wiscore_rak4631 -build_flags = ${nrf52840_base.build_flags} -Ivariants/rak_wismeshtap -DWISMESH_TAP -DRAK_4631 +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/rak_wismeshtap + -DWISMESH_TAP + -DRAK_4631 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 @@ -12,7 +15,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/rak_wismeshtap -DWISMESH_T -DMESHTASTIC_EXCLUDE_STOREFORWARD=1 -DMESHTASTIC_EXCLUDE_POWER_TELEMETRY=1 -DMESHTASTIC_EXCLUDE_ATAK=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak_wismeshtap> + + + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak_wismeshtap> + + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} diff --git a/variants/rak_wismeshtap/variant.cpp b/variants/nrf52840/rak_wismeshtap/variant.cpp similarity index 100% rename from variants/rak_wismeshtap/variant.cpp rename to variants/nrf52840/rak_wismeshtap/variant.cpp diff --git a/variants/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h similarity index 100% rename from variants/rak_wismeshtap/variant.h rename to variants/nrf52840/rak_wismeshtap/variant.h diff --git a/variants/seeed_solar_node/platformio.ini b/variants/nrf52840/seeed_solar_node/platformio.ini similarity index 70% rename from variants/seeed_solar_node/platformio.ini rename to variants/nrf52840/seeed_solar_node/platformio.ini index eb91a435f..b2a128c57 100644 --- a/variants/seeed_solar_node/platformio.ini +++ b/variants/nrf52840/seeed_solar_node/platformio.ini @@ -3,11 +3,12 @@ board = seeed_solar_node extends = nrf52840_base ;board_level = extra build_flags = ${nrf52840_base.build_flags} - -I $PROJECT_DIR/variants/seeed_solar_node + -I variants/nrf52840/seeed_solar_node -D SEEED_SOLAR_NODE - -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 + -I src/platform/nrf52/softdevice + -I src/platform/nrf52/softdevice/nrf52 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/seeed_solar_node> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_solar_node> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink diff --git a/variants/seeed_solar_node/variant.cpp b/variants/nrf52840/seeed_solar_node/variant.cpp similarity index 100% rename from variants/seeed_solar_node/variant.cpp rename to variants/nrf52840/seeed_solar_node/variant.cpp diff --git a/variants/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h similarity index 100% rename from variants/seeed_solar_node/variant.h rename to variants/nrf52840/seeed_solar_node/variant.h diff --git a/variants/seeed_wio_tracker_L1/platformio.ini b/variants/nrf52840/seeed_wio_tracker_L1/platformio.ini similarity index 61% rename from variants/seeed_wio_tracker_L1/platformio.ini rename to variants/nrf52840/seeed_wio_tracker_L1/platformio.ini index 3c4653d7e..6c137384d 100644 --- a/variants/seeed_wio_tracker_L1/platformio.ini +++ b/variants/nrf52840/seeed_wio_tracker_L1/platformio.ini @@ -1,13 +1,13 @@ [env:seeed_wio_tracker_L1] board = seeed_wio_tracker_L1 extends = nrf52840_base -;board_level = extra build_flags = ${nrf52840_base.build_flags} - -I $PROJECT_DIR/variants/seeed_wio_tracker_L1 - -D SEEED_WIO_TRACKER_L1 - -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 + -I variants/nrf52840/seeed_wio_tracker_L1 + -D SEEED_WIO_TRACKER_L1 + -I src/platform/nrf52/softdevice + -I src/platform/nrf52/softdevice/nrf52 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/seeed_wio_tracker_L1> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_wio_tracker_L1> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink diff --git a/variants/seeed_wio_tracker_L1/variant.cpp b/variants/nrf52840/seeed_wio_tracker_L1/variant.cpp similarity index 100% rename from variants/seeed_wio_tracker_L1/variant.cpp rename to variants/nrf52840/seeed_wio_tracker_L1/variant.cpp diff --git a/variants/seeed_wio_tracker_L1/variant.h b/variants/nrf52840/seeed_wio_tracker_L1/variant.h similarity index 100% rename from variants/seeed_wio_tracker_L1/variant.h rename to variants/nrf52840/seeed_wio_tracker_L1/variant.h diff --git a/variants/seeed_wio_tracker_L1_eink/nicheGraphics.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h similarity index 97% rename from variants/seeed_wio_tracker_L1_eink/nicheGraphics.h rename to variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h index 12ec4479a..a32753343 100644 --- a/variants/seeed_wio_tracker_L1_eink/nicheGraphics.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h @@ -57,7 +57,8 @@ void setupNicheGraphics() inkhud->setDisplayResilience(7, 1.5); // Select fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings diff --git a/variants/seeed_wio_tracker_L1_eink/platformio.ini b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini similarity index 50% rename from variants/seeed_wio_tracker_L1_eink/platformio.ini rename to variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini index b84757b9d..52ff39d49 100644 --- a/variants/seeed_wio_tracker_L1_eink/platformio.ini +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini @@ -2,12 +2,15 @@ board = seeed_wio_tracker_L1 extends = nrf52840_base, inkhud ;board_level = extra -build_flags = ${nrf52840_base.build_flags} ${inkhud.build_flags} - -I $PROJECT_DIR/variants/seeed_wio_tracker_L1_eink - -D SEEED_WIO_TRACKER_L1 - -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 +build_flags = ${nrf52840_base.build_flags} + ${inkhud.build_flags} + -I variants/nrf52840/seeed_wio_tracker_L1_eink + -D SEEED_WIO_TRACKER_L1_EINK + -D SEEED_WIO_TRACKER_L1 + -I src/platform/nrf52/softdevice + -I src/platform/nrf52/softdevice/nrf52 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/seeed_wio_tracker_L1_eink> ${inkhud.build_src_filter} +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_wio_tracker_L1_eink> ${inkhud.build_src_filter} lib_deps = ${inkhud.lib_deps} ${nrf52840_base.lib_deps} diff --git a/variants/seeed_wio_tracker_L1_eink/variant.cpp b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.cpp similarity index 100% rename from variants/seeed_wio_tracker_L1_eink/variant.cpp rename to variants/nrf52840/seeed_wio_tracker_L1_eink/variant.cpp diff --git a/variants/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h similarity index 100% rename from variants/seeed_wio_tracker_L1_eink/variant.h rename to variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h diff --git a/variants/seeed_xiao_nrf52840_kit/platformio.ini b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini similarity index 56% rename from variants/seeed_xiao_nrf52840_kit/platformio.ini rename to variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini index 8c4c5a57b..623eace71 100644 --- a/variants/seeed_xiao_nrf52840_kit/platformio.ini +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini @@ -1,10 +1,16 @@ -; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893 +; Seeed Xiao BLE: https://wiki.seeedstudio.com/XIAO_BLE/ [env:seeed_xiao_nrf52840_kit] extends = nrf52840_base board = xiao_ble_sense -build_flags = ${nrf52840_base.build_flags} -Ivariants/seeed_xiao_nrf52840_kit -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DSEEED_XIAO_NRF52840_KIT -DGPS_L76K +board_level = pr +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/seeed_xiao_nrf52840_kit + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DSEEED_XIAO_NRF52840_KIT + -DGPS_L76K board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/seeed_xiao_nrf52840_kit> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink diff --git a/variants/seeed_xiao_nrf52840_kit/variant.cpp b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.cpp similarity index 100% rename from variants/seeed_xiao_nrf52840_kit/variant.cpp rename to variants/nrf52840/seeed_xiao_nrf52840_kit/variant.cpp diff --git a/variants/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h similarity index 100% rename from variants/seeed_xiao_nrf52840_kit/variant.h rename to variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h diff --git a/variants/t-echo/nicheGraphics.h b/variants/nrf52840/t-echo/nicheGraphics.h similarity index 97% rename from variants/t-echo/nicheGraphics.h rename to variants/nrf52840/t-echo/nicheGraphics.h index 03185cf5b..c89d816b9 100644 --- a/variants/t-echo/nicheGraphics.h +++ b/variants/nrf52840/t-echo/nicheGraphics.h @@ -57,7 +57,8 @@ void setupNicheGraphics() inkhud->setDisplayResilience(20, 1.5); // Select fonts - InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252; + InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; + InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings diff --git a/variants/t-echo/platformio.ini b/variants/nrf52840/t-echo/platformio.ini similarity index 87% rename from variants/t-echo/platformio.ini rename to variants/nrf52840/t-echo/platformio.ini index 85c3b5799..6541c9796 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/nrf52840/t-echo/platformio.ini @@ -2,11 +2,13 @@ [env:t-echo] extends = nrf52840_base board = t-echo +board_level = pr board_check = true debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. -build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/t-echo -DGPS_POWER_TOGGLE -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 -DEINK_WIDTH=200 @@ -16,7 +18,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo> lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/GxEPD2/archive/55f618961db45a23eff0233546430f1e5a80f63a.zip @@ -26,16 +28,17 @@ lib_deps = [env:t-echo-inkhud] extends = nrf52840_base, inkhud board = t-echo +board_level = pr board_check = true debug_tool = jlink build_flags = ${nrf52840_base.build_flags} ${inkhud.build_flags} - -I variants/t-echo + -I variants/nrf52840/t-echo build_src_filter = ${nrf52_base.build_src_filter} ${inkhud.build_src_filter} - +<../variants/t-echo> + +<../variants/nrf52840/t-echo> lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${nrf52840_base.lib_deps} diff --git a/variants/t-echo/variant.cpp b/variants/nrf52840/t-echo/variant.cpp similarity index 100% rename from variants/t-echo/variant.cpp rename to variants/nrf52840/t-echo/variant.cpp diff --git a/variants/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h similarity index 100% rename from variants/t-echo/variant.h rename to variants/nrf52840/t-echo/variant.h diff --git a/variants/tracker-t1000-e/platformio.ini b/variants/nrf52840/tracker-t1000-e/platformio.ini similarity index 76% rename from variants/tracker-t1000-e/platformio.ini rename to variants/nrf52840/tracker-t1000-e/platformio.ini index b1f11d524..c6c3f269c 100644 --- a/variants/tracker-t1000-e/platformio.ini +++ b/variants/nrf52840/tracker-t1000-e/platformio.ini @@ -1,7 +1,12 @@ [env:tracker-t1000-e] extends = nrf52840_base board = tracker-t1000-e -build_flags = ${nrf52840_base.build_flags} -Ivariants/tracker-t1000-e -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DTRACKER_T1000_E +board_level = pr +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/tracker-t1000-e + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DTRACKER_T1000_E -DGPS_POWER_TOGGLE -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL=1 -DMESHTASTIC_EXCLUDE_CANNEDMESSAGES=1 @@ -9,7 +14,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/tracker-t1000-e -Isrc/plat -DMESHTASTIC_EXCLUDE_DETECTIONSENSOR=1 -DMESHTASTIC_EXCLUDE_WIFI=1 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/tracker-t1000-e> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/tracker-t1000-e> lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/QMA6100P_Arduino_Library/archive/14c900b8b2e4feaac5007a7e41e0c1b7f0841136.zip diff --git a/variants/tracker-t1000-e/rfswitch.h b/variants/nrf52840/tracker-t1000-e/rfswitch.h similarity index 100% rename from variants/tracker-t1000-e/rfswitch.h rename to variants/nrf52840/tracker-t1000-e/rfswitch.h diff --git a/variants/tracker-t1000-e/variant.cpp b/variants/nrf52840/tracker-t1000-e/variant.cpp similarity index 100% rename from variants/tracker-t1000-e/variant.cpp rename to variants/nrf52840/tracker-t1000-e/variant.cpp diff --git a/variants/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h similarity index 100% rename from variants/tracker-t1000-e/variant.h rename to variants/nrf52840/tracker-t1000-e/variant.h diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/nrf52840/wio-sdk-wm1110/platformio.ini similarity index 80% rename from variants/wio-sdk-wm1110/platformio.ini rename to variants/nrf52840/wio-sdk-wm1110/platformio.ini index 4e1415678..2c65246b8 100644 --- a/variants/wio-sdk-wm1110/platformio.ini +++ b/variants/nrf52840/wio-sdk-wm1110/platformio.ini @@ -4,16 +4,20 @@ extends = nrf52840_base board = wio-sdk-wm1110 extra_scripts = - bin/platformio-custom.py + ${env.extra_scripts} extra_scripts/disable_adafruit_usb.py # Remove adafruit USB serial from the build (it is incompatible with using the ch340 serial chip on this board) build_unflags = ${nrf52840_base:build_unflags} -DUSBCON -DUSE_TINYUSB -build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/wio-sdk-wm1110 + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DWIO_WM1110 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DCFG_TUD_CDC=0 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/wio-sdk-wm1110> ;debug_tool = jlink debug_tool = stlink diff --git a/variants/wio-sdk-wm1110/rfswitch.h b/variants/nrf52840/wio-sdk-wm1110/rfswitch.h similarity index 100% rename from variants/wio-sdk-wm1110/rfswitch.h rename to variants/nrf52840/wio-sdk-wm1110/rfswitch.h diff --git a/variants/wio-sdk-wm1110/variant.cpp b/variants/nrf52840/wio-sdk-wm1110/variant.cpp similarity index 100% rename from variants/wio-sdk-wm1110/variant.cpp rename to variants/nrf52840/wio-sdk-wm1110/variant.cpp diff --git a/variants/wio-sdk-wm1110/variant.h b/variants/nrf52840/wio-sdk-wm1110/variant.h similarity index 100% rename from variants/wio-sdk-wm1110/variant.h rename to variants/nrf52840/wio-sdk-wm1110/variant.h diff --git a/variants/wio-t1000-s/platformio.ini b/variants/nrf52840/wio-t1000-s/platformio.ini similarity index 77% rename from variants/wio-t1000-s/platformio.ini rename to variants/nrf52840/wio-t1000-s/platformio.ini index 2eab1e1c5..3594bcf07 100644 --- a/variants/wio-t1000-s/platformio.ini +++ b/variants/nrf52840/wio-t1000-s/platformio.ini @@ -3,10 +3,14 @@ extends = nrf52840_base board = wio-t1000-s board_level = extra -build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-t1000-s -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/wio-t1000-s + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DWIO_WM1110 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-t1000-s> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/wio-t1000-s> lib_deps = ${nrf52840_base.lib_deps} debug_tool = jlink diff --git a/variants/wio-t1000-s/rfswitch.h b/variants/nrf52840/wio-t1000-s/rfswitch.h similarity index 100% rename from variants/wio-t1000-s/rfswitch.h rename to variants/nrf52840/wio-t1000-s/rfswitch.h diff --git a/variants/wio-t1000-s/variant.cpp b/variants/nrf52840/wio-t1000-s/variant.cpp similarity index 100% rename from variants/wio-t1000-s/variant.cpp rename to variants/nrf52840/wio-t1000-s/variant.cpp diff --git a/variants/wio-t1000-s/variant.h b/variants/nrf52840/wio-t1000-s/variant.h similarity index 100% rename from variants/wio-t1000-s/variant.h rename to variants/nrf52840/wio-t1000-s/variant.h diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/nrf52840/wio-tracker-wm1110/platformio.ini similarity index 73% rename from variants/wio-tracker-wm1110/platformio.ini rename to variants/nrf52840/wio-tracker-wm1110/platformio.ini index a6960b435..b383043bb 100644 --- a/variants/wio-tracker-wm1110/platformio.ini +++ b/variants/nrf52840/wio-tracker-wm1110/platformio.ini @@ -2,10 +2,14 @@ [env:wio-tracker-wm1110] extends = nrf52840_base board = wio-tracker-wm1110 -build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 -DWIO_WM1110 +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/wio-tracker-wm1110 + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DWIO_WM1110 -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-tracker-wm1110> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/wio-tracker-wm1110> lib_deps = ${nrf52840_base.lib_deps} ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) diff --git a/variants/wio-tracker-wm1110/rfswitch.h b/variants/nrf52840/wio-tracker-wm1110/rfswitch.h similarity index 100% rename from variants/wio-tracker-wm1110/rfswitch.h rename to variants/nrf52840/wio-tracker-wm1110/rfswitch.h diff --git a/variants/wio-tracker-wm1110/variant.cpp b/variants/nrf52840/wio-tracker-wm1110/variant.cpp similarity index 100% rename from variants/wio-tracker-wm1110/variant.cpp rename to variants/nrf52840/wio-tracker-wm1110/variant.cpp diff --git a/variants/wio-tracker-wm1110/variant.h b/variants/nrf52840/wio-tracker-wm1110/variant.h similarity index 100% rename from variants/wio-tracker-wm1110/variant.h rename to variants/nrf52840/wio-tracker-wm1110/variant.h diff --git a/variants/rak11200/platformio.ini b/variants/rak11200/platformio.ini deleted file mode 100644 index eddc3458e..000000000 --- a/variants/rak11200/platformio.ini +++ /dev/null @@ -1,7 +0,0 @@ -[env:rak11200] -extends = esp32_base -board = wiscore_rak11200 -board_check = true -build_flags = - ${esp32_base.build_flags} -D RAK_11200 -I variants/rak11200 -upload_speed = 115200 \ No newline at end of file diff --git a/variants/rak3172/platformio.ini b/variants/rak3172/platformio.ini deleted file mode 100644 index 456697aef..000000000 --- a/variants/rak3172/platformio.ini +++ /dev/null @@ -1,23 +0,0 @@ -[env:rak3172] -extends = stm32_base -board = wiscore_rak3172 -board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem -build_flags = - ${stm32_base.build_flags} - -Ivariants/rak3172 - -DHAL_DAC_MODULE_ONLY - -DHAL_RNG_MODULE_ENABLED - -DRADIOLIB_EXCLUDE_SX128X=1 - -DRADIOLIB_EXCLUDE_SX127X=1 - -DRADIOLIB_EXCLUDE_LR11X0=1 - -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 - -DMESHTASTIC_EXCLUDE_I2C=1 - -DMESHTASTIC_EXCLUDE_WIFI=1 - -DMESHTASTIC_EXCLUDE_BLUETOOTH=1 - -DMESHTASTIC_EXCLUDE_GPS=1 - -DMESHTASTIC_EXCLUDE_SCREEN=1 - -DMESHTASTIC_EXCLUDE_MQTT=1 - -DMESHTASTIC_EXCLUDE_POWERMON=1 - ;-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF - ;-DCFG_DEBUG -upload_port = stlink diff --git a/variants/ec_catsniffer/platformio.ini b/variants/rp2040/ec_catsniffer/platformio.ini similarity index 50% rename from variants/ec_catsniffer/platformio.ini rename to variants/rp2040/ec_catsniffer/platformio.ini index 6db9abe90..acf19d757 100644 --- a/variants/ec_catsniffer/platformio.ini +++ b/variants/rp2040/ec_catsniffer/platformio.ini @@ -2,13 +2,13 @@ extends = rp2040_base board = rpipico upload_protocol = picotool - -build_flags = ${rp2040_base.build_flags} - -DRPI_PICO - -Ivariants/ec_catsniffer - -DDEBUG_RP2040_PORT=Serial - # -DHW_SPI1_DEVICE +build_flags = + ${rp2040_base.build_flags} + -D RPI_PICO + -I variants/rp2040/ec_catsniffer + -D DEBUG_RP2040_PORT=Serial + ; -D HW_SPI1_DEVICE lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags}, -g -debug_tool = cmsis-dap \ No newline at end of file +debug_tool = cmsis-dap diff --git a/variants/ec_catsniffer/variant.cpp b/variants/rp2040/ec_catsniffer/variant.cpp similarity index 100% rename from variants/ec_catsniffer/variant.cpp rename to variants/rp2040/ec_catsniffer/variant.cpp diff --git a/variants/ec_catsniffer/variant.h b/variants/rp2040/ec_catsniffer/variant.h similarity index 100% rename from variants/ec_catsniffer/variant.h rename to variants/rp2040/ec_catsniffer/variant.h diff --git a/variants/feather_rp2040_rfm95/platformio.ini b/variants/rp2040/feather_rp2040_rfm95/platformio.ini similarity index 53% rename from variants/feather_rp2040_rfm95/platformio.ini rename to variants/rp2040/feather_rp2040_rfm95/platformio.ini index db1eb4f02..ef4118cb0 100644 --- a/variants/feather_rp2040_rfm95/platformio.ini +++ b/variants/rp2040/feather_rp2040_rfm95/platformio.ini @@ -2,14 +2,14 @@ extends = rp2040_base board = adafruit_feather upload_protocol = picotool - # add our variants files to the include and src paths -build_flags = ${rp2040_base.build_flags} - -DRP2040_FEATHER_RFM95 - -Ivariants/feather_rp2040_rfm95 - -DDEBUG_RP2040_PORT=Serial - -DHW_SPI1_DEVICE +build_flags = + ${rp2040_base.build_flags} + -D RP2040_FEATHER_RFM95 + -I variants/rp2040/feather_rp2040_rfm95 + -D DEBUG_RP2040_PORT=Serial + -D HW_SPI1_DEVICE lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags} -debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file +debug_tool = cmsis-dap ; for e.g. Picotool diff --git a/variants/feather_rp2040_rfm95/variant.h b/variants/rp2040/feather_rp2040_rfm95/variant.h similarity index 100% rename from variants/feather_rp2040_rfm95/variant.h rename to variants/rp2040/feather_rp2040_rfm95/variant.h diff --git a/variants/nibble_rp2040/platformio.ini b/variants/rp2040/nibble_rp2040/platformio.ini similarity index 56% rename from variants/nibble_rp2040/platformio.ini rename to variants/rp2040/nibble_rp2040/platformio.ini index c3a1923c5..024a72206 100644 --- a/variants/nibble_rp2040/platformio.ini +++ b/variants/rp2040/nibble_rp2040/platformio.ini @@ -3,14 +3,14 @@ extends = rp2040_base board = rpipico board_level = extra upload_protocol = picotool - # add our variants files to the include and src paths -build_flags = ${rp2040_base.build_flags} - -DPRIVATE_HW - -Ivariants/nibble_rp2040 - -DDEBUG_RP2040_PORT=Serial - -DHW_SPI1_DEVICE +build_flags = + ${rp2040_base.build_flags} + -D PRIVATE_HW + -I variants/rp2040/nibble_rp2040 + -D DEBUG_RP2040_PORT=Serial + -D HW_SPI1_DEVICE lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags}, -g -debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file +debug_tool = cmsis-dap ; for e.g. Picotool diff --git a/variants/nibble_rp2040/variant.h b/variants/rp2040/nibble_rp2040/variant.h similarity index 100% rename from variants/nibble_rp2040/variant.h rename to variants/rp2040/nibble_rp2040/variant.h diff --git a/variants/rak11310/pins_arduino.h b/variants/rp2040/rak11310/pins_arduino.h similarity index 100% rename from variants/rak11310/pins_arduino.h rename to variants/rp2040/rak11310/pins_arduino.h diff --git a/variants/rak11310/platformio.ini b/variants/rp2040/rak11310/platformio.ini similarity index 62% rename from variants/rak11310/platformio.ini rename to variants/rp2040/rak11310/platformio.ini index fd7e842cc..f3eaa176e 100644 --- a/variants/rak11310/platformio.ini +++ b/variants/rp2040/rak11310/platformio.ini @@ -1,19 +1,20 @@ [env:rak11310] extends = rp2040_base board = rakwireless_rak11300 +board_level = pr upload_protocol = picotool - # add our variants files to the include and src paths -build_flags = ${rp2040_base.build_flags} - -DRAK11310 - -Ivariants/rak11310 - -DDEBUG_RP2040_PORT=Serial - -DRV3028_RTC=0x52 -build_src_filter = ${rp2040_base.build_src_filter} +<../variants/rak11310> + + + +build_flags = + ${rp2040_base.build_flags} + -D RAK11310 + -I variants/rp2040/rak11310 + -D DEBUG_RP2040_PORT=Serial + -D RV3028_RTC=0x52 +build_src_filter = ${rp2040_base.build_src_filter} +<../variants/rp2040/rak11310> + + + lib_deps = ${rp2040_base.lib_deps} ${networking_base.lib_deps} melopero/Melopero RV3028@^1.1.0 https://github.com/RAKWireless/RAK13800-W5100S/archive/1.0.2.zip debug_build_flags = ${rp2040_base.build_flags}, -g -debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file +debug_tool = cmsis-dap ; for e.g. Picotool diff --git a/variants/rak11310/variant.h b/variants/rp2040/rak11310/variant.h similarity index 100% rename from variants/rak11310/variant.h rename to variants/rp2040/rak11310/variant.h diff --git a/variants/rp2040-lora/platformio.ini b/variants/rp2040/rp2040-lora/platformio.ini similarity index 54% rename from variants/rp2040-lora/platformio.ini rename to variants/rp2040/rp2040-lora/platformio.ini index 7ac5b2cac..d59e74f20 100644 --- a/variants/rp2040-lora/platformio.ini +++ b/variants/rp2040/rp2040-lora/platformio.ini @@ -2,14 +2,14 @@ extends = rp2040_base board = rpipico upload_protocol = picotool - # add our variants files to the include and src paths -build_flags = ${rp2040_base.build_flags} - -DRP2040_LORA - -Ivariants/rp2040-lora - -DDEBUG_RP2040_PORT=Serial - -DHW_SPI1_DEVICE +build_flags = + ${rp2040_base.build_flags} + -D RP2040_LORA + -I variants/rp2040/rp2040-lora + -D DEBUG_RP2040_PORT=Serial + -D HW_SPI1_DEVICE lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags}, -g -debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file +debug_tool = cmsis-dap ; for e.g. Picotool diff --git a/variants/rp2040-lora/variant.h b/variants/rp2040/rp2040-lora/variant.h similarity index 100% rename from variants/rp2040-lora/variant.h rename to variants/rp2040/rp2040-lora/variant.h diff --git a/variants/rpipico-slowclock/platformio.ini b/variants/rp2040/rpipico-slowclock/platformio.ini similarity index 85% rename from variants/rpipico-slowclock/platformio.ini rename to variants/rp2040/rpipico-slowclock/platformio.ini index c56f9e78c..30928aead 100644 --- a/variants/rpipico-slowclock/platformio.ini +++ b/variants/rp2040/rpipico-slowclock/platformio.ini @@ -12,11 +12,11 @@ debug_init_cmds = $LOAD_CMDS monitor init monitor reset halt - # add our variants files to the include and src paths -build_flags = ${rp2040_base.build_flags} +build_flags = + ${rp2040_base.build_flags} -DRPI_PICO - -Ivariants/rpipico-slowclock + -Ivariants/rp2040/rpipico-slowclock -DDEBUG_RP2040_PORT=Serial2 -DHW_SPI1_DEVICE -g @@ -25,4 +25,4 @@ lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags} -g - -DNO_USB \ No newline at end of file + -DNO_USB diff --git a/variants/rpipico-slowclock/variant.h b/variants/rp2040/rpipico-slowclock/variant.h similarity index 100% rename from variants/rpipico-slowclock/variant.h rename to variants/rp2040/rpipico-slowclock/variant.h diff --git a/variants/rpipico/platformio.ini b/variants/rp2040/rpipico/platformio.ini similarity index 52% rename from variants/rpipico/platformio.ini rename to variants/rp2040/rpipico/platformio.ini index e34cfa43b..a6171bbac 100644 --- a/variants/rpipico/platformio.ini +++ b/variants/rp2040/rpipico/platformio.ini @@ -1,15 +1,17 @@ [env:pico] extends = rp2040_base board = rpipico +board_level = pr upload_protocol = picotool # add our variants files to the include and src paths -build_flags = ${rp2040_base.build_flags} - -DRPI_PICO - -Ivariants/rpipico - -DDEBUG_RP2040_PORT=Serial - -DHW_SPI1_DEVICE +build_flags = + ${rp2040_base.build_flags} + -D RPI_PICO + -I variants/rp2040/rpipico + -D DEBUG_RP2040_PORT=Serial + -D HW_SPI1_DEVICE lib_deps = ${rp2040_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags}, -g -debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file +debug_tool = cmsis-dap ; for e.g. Picotool diff --git a/variants/rpipico/variant.h b/variants/rp2040/rpipico/variant.h similarity index 100% rename from variants/rpipico/variant.h rename to variants/rp2040/rpipico/variant.h diff --git a/variants/rpipicow/platformio.ini b/variants/rp2040/rpipicow/platformio.ini similarity index 70% rename from variants/rpipicow/platformio.ini rename to variants/rp2040/rpipicow/platformio.ini index e59944b5d..60845ba39 100644 --- a/variants/rpipicow/platformio.ini +++ b/variants/rp2040/rpipicow/platformio.ini @@ -1,15 +1,17 @@ [env:picow] extends = rp2040_base board = rpipicow +board_level = pr +board_check = true upload_protocol = picotool - # add our variants files to the include and src paths -build_flags = ${rp2040_base.build_flags} - -DRPI_PICO - -Ivariants/rpipicow - -DHW_SPI1_DEVICE +build_flags = + ${rp2040_base.build_flags} + -D RPI_PICO + -I variants/rp2040/rpipicow + -D HW_SPI1_DEVICE + -D HAS_UDP_MULTICAST=1 -fexceptions # for exception handling in MQTT - -DHAS_UDP_MULTICAST=1 build_src_filter = ${rp2040_base.build_src_filter} + lib_deps = ${rp2040_base.lib_deps} diff --git a/variants/rpipicow/variant.h b/variants/rp2040/rpipicow/variant.h similarity index 100% rename from variants/rpipicow/variant.h rename to variants/rp2040/rpipicow/variant.h diff --git a/variants/senselora_rp2040/pins_arduino.h b/variants/rp2040/senselora_rp2040/pins_arduino.h similarity index 100% rename from variants/senselora_rp2040/pins_arduino.h rename to variants/rp2040/senselora_rp2040/pins_arduino.h diff --git a/variants/senselora_rp2040/platformio.ini b/variants/rp2040/senselora_rp2040/platformio.ini similarity index 60% rename from variants/senselora_rp2040/platformio.ini rename to variants/rp2040/senselora_rp2040/platformio.ini index b05fc1f8b..3a574d0f9 100644 --- a/variants/senselora_rp2040/platformio.ini +++ b/variants/rp2040/senselora_rp2040/platformio.ini @@ -5,9 +5,9 @@ board = rpipico upload_protocol = picotool # add our variants files to the include and src paths -build_flags = ${rp2040_base.build_flags} - -DSENSELORA_RP2040 - -Ivariants/senselora_rp2040 - -DDEBUG_RP2040_PORT=Serial +build_flags = ${rp2040_base.build_flags} + -D SENSELORA_RP2040 + -I variants/rp2040/senselora_rp2040 + -D DEBUG_RP2040_PORT=Serial lib_deps = ${rp2040_base.lib_deps} \ No newline at end of file diff --git a/variants/senselora_rp2040/variant.h b/variants/rp2040/senselora_rp2040/variant.h similarity index 92% rename from variants/senselora_rp2040/variant.h rename to variants/rp2040/senselora_rp2040/variant.h index 2f68cf034..cc90284b7 100644 --- a/variants/senselora_rp2040/variant.h +++ b/variants/rp2040/senselora_rp2040/variant.h @@ -6,6 +6,7 @@ #define BUTTON_NEED_PULLUP #define LED_PIN PIN_LED +#define ledOff(pin) pinMode(pin, INPUT) #undef BATTERY_PIN #define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION diff --git a/variants/rpipico2/platformio.ini b/variants/rp2350/rpipico2/platformio.ini similarity index 52% rename from variants/rpipico2/platformio.ini rename to variants/rp2350/rpipico2/platformio.ini index 066809a91..ad7a4ce51 100644 --- a/variants/rpipico2/platformio.ini +++ b/variants/rp2350/rpipico2/platformio.ini @@ -1,15 +1,17 @@ [env:pico2] extends = rp2350_base board = rpipico2 +board_level = pr upload_protocol = picotool # add our variants files to the include and src paths -build_flags = ${rp2350_base.build_flags} - -DRPI_PICO2 - -Ivariants/rpipico2 - -DDEBUG_RP2040_PORT=Serial - -DHW_SPI1_DEVICE +build_flags = + ${rp2350_base.build_flags} + -D RPI_PICO2 + -I variants/rp2350/rpipico2 + -D DEBUG_RP2040_PORT=Serial + -D HW_SPI1_DEVICE lib_deps = ${rp2350_base.lib_deps} debug_build_flags = ${rp2350_base.build_flags}, -g -debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file +debug_tool = cmsis-dap ; for e.g. Picotool diff --git a/variants/rpipico2/variant.h b/variants/rp2350/rpipico2/variant.h similarity index 100% rename from variants/rpipico2/variant.h rename to variants/rp2350/rpipico2/variant.h diff --git a/variants/rpipico2w/platformio.ini b/variants/rp2350/rpipico2w/platformio.ini similarity index 87% rename from variants/rpipico2w/platformio.ini rename to variants/rp2350/rpipico2w/platformio.ini index 0fac1e9ce..5dbce533b 100644 --- a/variants/rpipico2w/platformio.ini +++ b/variants/rp2350/rpipico2w/platformio.ini @@ -1,6 +1,8 @@ [env:pico2w] extends = rp2350_base board = rpipico2w +board_level = pr +board_check = true upload_protocol = jlink # debug settings for external openocd with RP2040 support (custom build) debug_tool = custom @@ -13,9 +15,10 @@ debug_init_cmds = monitor reset halt # add our variants files to the include and src paths -build_flags = ${rp2350_base.build_flags} +build_flags = + ${rp2350_base.build_flags} -DRPI_PICO2 - -Ivariants/rpipico2w + -Ivariants/rp2350/rpipico2w # -DDEBUG_RP2040_PORT=Serial -DHW_SPI1_DEVICE -DARDUINO_RASPBERRY_PI_PICO_2W diff --git a/variants/rpipico2w/variant.h b/variants/rp2350/rpipico2w/variant.h similarity index 100% rename from variants/rpipico2w/variant.h rename to variants/rp2350/rpipico2w/variant.h diff --git a/variants/CDEBYTE_E77-MBL/platformio.ini b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini similarity index 58% rename from variants/CDEBYTE_E77-MBL/platformio.ini rename to variants/stm32/CDEBYTE_E77-MBL/platformio.ini index 8a8002086..c011f62c9 100644 --- a/variants/CDEBYTE_E77-MBL/platformio.ini +++ b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini @@ -5,23 +5,13 @@ board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem board_level = extra build_flags = ${stm32_base.build_flags} - -Ivariants/CDEBYTE_E77-MBL + -Ivariants/stm32/CDEBYTE_E77-MBL -DSERIAL_UART_INSTANCE=1 -DPIN_SERIAL_RX=PA3 -DPIN_SERIAL_TX=PA2 - -DHAL_DAC_MODULE_ONLY - -DHAL_RNG_MODULE_ENABLED - -DRADIOLIB_EXCLUDE_SX128X=1 - -DRADIOLIB_EXCLUDE_SX127X=1 - -DRADIOLIB_EXCLUDE_LR11X0=1 -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 -DMESHTASTIC_EXCLUDE_I2C=1 - -DMESHTASTIC_EXCLUDE_WIFI=1 - -DMESHTASTIC_EXCLUDE_BLUETOOTH=1 -DMESHTASTIC_EXCLUDE_GPS=1 - -DMESHTASTIC_EXCLUDE_SCREEN=1 - -DMESHTASTIC_EXCLUDE_MQTT=1 - -DMESHTASTIC_EXCLUDE_POWERMON=1 ;-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF ;-DCFG_DEBUG diff --git a/variants/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h similarity index 100% rename from variants/CDEBYTE_E77-MBL/variant.h rename to variants/stm32/CDEBYTE_E77-MBL/variant.h diff --git a/variants/stm32/rak3172/platformio.ini b/variants/stm32/rak3172/platformio.ini new file mode 100644 index 000000000..4f9edbb92 --- /dev/null +++ b/variants/stm32/rak3172/platformio.ini @@ -0,0 +1,15 @@ +[env:rak3172] +extends = stm32_base +board = wiscore_rak3172 +board_level = pr +board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem +build_flags = + ${stm32_base.build_flags} + -Ivariants/stm32/rak3172 + -DPIN_WIRE_SDA=PA11 + -DPIN_WIRE_SCL=PA12 + -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 + -DMESHTASTIC_EXCLUDE_I2C=1 + -DMESHTASTIC_EXCLUDE_GPS=1 + ;-DCFG_DEBUG +upload_port = stlink diff --git a/variants/rak3172/variant.h b/variants/stm32/rak3172/variant.h similarity index 100% rename from variants/rak3172/variant.h rename to variants/stm32/rak3172/variant.h diff --git a/variants/stm32/wio-e5/platformio.ini b/variants/stm32/wio-e5/platformio.ini new file mode 100644 index 000000000..a9fcf51d6 --- /dev/null +++ b/variants/stm32/wio-e5/platformio.ini @@ -0,0 +1,25 @@ +[env:wio-e5] +extends = stm32_base +board = lora_e5_dev_board +board_level = pr +board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem +build_flags = + ${stm32_base.build_flags} + -Ivariants/stm32/wio-e5 + -DSERIAL_UART_INSTANCE=1 + -DPIN_SERIAL_RX=PB7 + -DPIN_SERIAL_TX=PB6 + -DPIN_WIRE_SDA=PA15 + -DPIN_WIRE_SCL=PB15 + -DHAS_SENSOR=1 + -DENABLE_HWSERIAL2 + -DPIN_SERIAL2_TX=PA2 + -DPIN_SERIAL2_RX=PA3 + -DHAS_GPS=1 + -DGPS_SERIAL_PORT=Serial2 + +upload_port = stlink + +lib_deps = + ${stm32_base.lib_deps} + # Add your custom sensor here! \ No newline at end of file diff --git a/variants/wio-e5/variant.h b/variants/stm32/wio-e5/variant.h similarity index 96% rename from variants/wio-e5/variant.h rename to variants/stm32/wio-e5/variant.h index 5421eaeb9..6098b4ce6 100644 --- a/variants/wio-e5/variant.h +++ b/variants/stm32/wio-e5/variant.h @@ -17,6 +17,8 @@ Do not expect a working Meshtastic device with this target. #define LED_PIN PB5 #define LED_STATE_ON 1 +#define WIO_E5 + #if (defined(LED_BUILTIN) && LED_BUILTIN == PNUM_NOT_DEFINED) #undef LED_BUILTIN #define LED_BUILTIN (LED_PIN) diff --git a/variants/wio-e5/platformio.ini b/variants/wio-e5/platformio.ini deleted file mode 100644 index e746ae2f0..000000000 --- a/variants/wio-e5/platformio.ini +++ /dev/null @@ -1,27 +0,0 @@ -[env:wio-e5] -extends = stm32_base -board = lora_e5_dev_board -board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem -build_flags = - ${stm32_base.build_flags} - -Ivariants/wio-e5 - -DSERIAL_UART_INSTANCE=1 - -DPIN_SERIAL_RX=PB7 - -DPIN_SERIAL_TX=PB6 - -DHAL_DAC_MODULE_ONLY - -DHAL_RNG_MODULE_ENABLED - -DRADIOLIB_EXCLUDE_SX128X=1 - -DRADIOLIB_EXCLUDE_SX127X=1 - -DRADIOLIB_EXCLUDE_LR11X0=1 - -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 - -DMESHTASTIC_EXCLUDE_I2C=1 - -DMESHTASTIC_EXCLUDE_WIFI=1 - -DMESHTASTIC_EXCLUDE_BLUETOOTH=1 - -DMESHTASTIC_EXCLUDE_GPS=1 - -DMESHTASTIC_EXCLUDE_SCREEN=1 - -DMESHTASTIC_EXCLUDE_MQTT=1 - -DMESHTASTIC_EXCLUDE_POWERMON=1 - ;-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF - ;-DCFG_DEBUG - -upload_port = stlink \ No newline at end of file diff --git a/version.properties b/version.properties index 3fe1aa385..aa959bcac 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 1 +build = 4