diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh index 10a2db0bd..86ab775f9 100644 --- a/.clusterfuzzlite/build.sh +++ b/.clusterfuzzlite/build.sh @@ -51,7 +51,7 @@ for f in .clusterfuzzlite/*_fuzzer.cpp; do fuzzer=$(basename "$f" .cpp) cp -f "$f" src/fuzzer.cpp pio run -vvv --environment "$PIO_ENV" - program="$PLATFORMIO_WORKSPACE_DIR/build/$PIO_ENV/program" + program="$PLATFORMIO_WORKSPACE_DIR/build/$PIO_ENV/meshtasticd" cp "$program" "$OUT/$fuzzer" # Copy shared libraries used by the fuzzer. diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml index a71ddfc4d..a1e8dd852 100644 --- a/.github/actions/build-variant/action.yml +++ b/.github/actions/build-variant/action.yml @@ -102,7 +102,7 @@ runs: - name: Store binaries as an artifact uses: actions/upload-artifact@v5 with: - name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip + name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }} overwrite: true path: | ${{ inputs.artifact-paths }} diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml index 9ac84c23e..c3b70d4c9 100644 --- a/.github/workflows/build_firmware.yml +++ b/.github/workflows/build_firmware.yml @@ -55,15 +55,29 @@ jobs: ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }} ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }} + - name: Echo manifest from release/firmware-*.mt.json to job summary + if: ${{ always() }} + env: + PIO_ENV: ${{ inputs.pio_env }} + run: | + echo "## Manifest: \`$PIO_ENV\`" >> $GITHUB_STEP_SUMMARY + echo '```json' >> $GITHUB_STEP_SUMMARY + cat release/firmware-*.mt.json >> $GITHUB_STEP_SUMMARY + echo '' >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + - name: Store binaries as an artifact uses: actions/upload-artifact@v5 id: upload with: - name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip + name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }} overwrite: true path: | + release/*.mt.json release/*.bin release/*.elf release/*.uf2 release/*.hex - release/*-ota.zip + release/*.zip + release/device-*.sh + release/device-*.bat diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml deleted file mode 100644 index 5673f8cb6..000000000 --- a/.github/workflows/build_one_arch.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: Build One Arch - -on: - workflow_dispatch: - inputs: - # trunk-ignore(checkov/CKV_GHA_7) - arch: - type: choice - options: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 - - native - -permissions: read-all - -env: - INPUT_ARCH: ${{ github.event.inputs.arch }} - -jobs: - setup: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 - with: - python-version: 3.x - cache: pip - - run: pip install -U platformio - - name: Generate matrix - id: jsonStep - run: | - TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra) - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" - echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT - outputs: - selected_arch: ${{ steps.jsonStep.outputs.selected_arch }} - - version: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Get release version string - run: | - echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT - id: version - env: - BUILD_LOCATION: local - outputs: - long: ${{ steps.version.outputs.long }} - deb: ${{ steps.version.outputs.deb }} - - build: - if: ${{ github.event_name != 'workflow_dispatch' }} - needs: [setup, version] - strategy: - fail-fast: false - matrix: - build: ${{ fromJson(needs.setup.outputs.selected_arch) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.build.board }} - platform: ${{ matrix.build.arch }} - - build-debian-src: - if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} - uses: ./.github/workflows/build_debian_src.yml - with: - series: UNRELEASED - build_location: local - secrets: inherit - - package-pio-deps-native-tft: - if: ${{ inputs.arch == 'native' }} - uses: ./.github/workflows/package_pio_deps.yml - with: - pio_env: native-tft - secrets: inherit - - test-native: - if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }} - uses: ./.github/workflows/test_native.yml - - gather-artifacts: - permissions: - contents: write - pull-requests: write - strategy: - fail-fast: false - matrix: - arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 - runs-on: ubuntu-latest - needs: [version, build] - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} - - - uses: actions/download-artifact@v6 - with: - path: ./ - pattern: firmware-${{inputs.arch}}-* - merge-multiple: true - - - name: Display structure of downloaded files - run: ls -R - - - name: Move files up - run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat - - - name: Repackage in single firmware zip - uses: actions/upload-artifact@v5 - with: - name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} - overwrite: true - path: | - ./firmware-*.bin - ./firmware-*.uf2 - ./firmware-*.hex - ./firmware-*-ota.zip - ./device-*.sh - ./device-*.bat - ./littlefs-*.bin - ./bleota*bin - ./Meshtastic_nRF52_factory_erase*.uf2 - retention-days: 30 - - - uses: actions/download-artifact@v6 - with: - name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output - - # For diagnostics - - name: Show artifacts - run: ls -lR - - - name: Device scripts permissions - run: | - chmod +x ./output/device-install.sh - chmod +x ./output/device-update.sh - - - name: Zip firmware - run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output - - - name: Repackage in single elfs zip - uses: actions/upload-artifact@v5 - with: - name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - overwrite: true - path: ./*.elf - retention-days: 30 - - - uses: scruplelesswizard/comment-artifact@main - if: ${{ github.event_name == 'pull_request' }} - with: - name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} - description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml index 343e5be64..02aad5a9c 100644 --- a/.github/workflows/build_one_target.yml +++ b/.github/workflows/build_one_target.yml @@ -15,7 +15,6 @@ on: - rp2040 - rp2350 - stm32 - - native target: type: string required: false @@ -42,7 +41,6 @@ jobs: - rp2040 - rp2350 - stm32 - runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 @@ -60,7 +58,7 @@ jobs: echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY echo "Targets:" >> $GITHUB_STEP_SUMMARY - echo $TARGETS >> $GITHUB_STEP_SUMMARY + echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY version: if: ${{ inputs.target != '' }} @@ -87,25 +85,6 @@ jobs: pio_env: ${{ inputs.target }} platform: ${{ inputs.arch }} - build-debian-src: - if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }} - uses: ./.github/workflows/build_debian_src.yml - with: - series: UNRELEASED - build_location: local - secrets: inherit - - package-pio-deps-native-tft: - if: ${{ inputs.arch == 'native' }} - uses: ./.github/workflows/package_pio_deps.yml - with: - pio_env: native-tft - secrets: inherit - - test-native: - if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }} - uses: ./.github/workflows/test_native.yml - gather-artifacts: permissions: contents: write @@ -140,7 +119,7 @@ jobs: ./firmware-*.bin ./firmware-*.uf2 ./firmware-*.hex - ./firmware-*-ota.zip + ./firmware-*.zip ./device-*.sh ./device-*.bat ./littlefs-*.bin diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 38373a2fc..b4f4c3d11 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -177,19 +177,17 @@ jobs: - name: Display structure of downloaded files run: ls -R - - name: Move files up - run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat - - name: Repackage in single firmware zip uses: actions/upload-artifact@v5 with: name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} overwrite: true path: | + ./firmware-*.mt.json ./firmware-*.bin ./firmware-*.uf2 ./firmware-*.hex - ./firmware-*-ota.zip + ./firmware-*.zip ./device-*.sh ./device-*.bat ./littlefs-*.bin @@ -218,7 +216,7 @@ jobs: - name: Repackage in single elfs zip uses: actions/upload-artifact@v5 with: - name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }} overwrite: true path: ./*.elf retention-days: 30 @@ -236,6 +234,7 @@ jobs: outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} needs: + - setup - version - gather-artifacts - build-debian-src @@ -244,11 +243,6 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - name: Create release uses: softprops/action-gh-release@v2 id: create_release @@ -284,10 +278,25 @@ jobs: - name: Display structure of downloaded files run: ls -lR - - name: Add Linux sources to GtiHub Release + - name: Generate Release manifest + run: | + jq -n --arg ver "${{ needs.version.outputs.long }}" --arg targets "${{ toJson(needs.setup.outputs.all) }}" '{ + "version": $ver, + "targets": ($targets | fromjson) + }' > firmware-${{ needs.version.outputs.long }}.json + + - name: Save Release manifest artifact + uses: actions/upload-artifact@v5 + with: + name: manifest-${{ needs.version.outputs.long }} + overwrite: true + path: firmware-${{ needs.version.outputs.long }}.json + + - name: Add sources to GitHub Release # Only run when targeting master branch with workflow_dispatch if: ${{ github.ref_name == 'master' }} run: | + gh release upload v${{ needs.version.outputs.long }} ./firmware-${{ needs.version.outputs.long }}.json 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: @@ -337,7 +346,7 @@ jobs: - uses: actions/download-artifact@v6 with: - name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true path: ./elfs @@ -373,12 +382,19 @@ jobs: with: python-version: 3.x - - uses: actions/download-artifact@v6 + - name: Get firmware artifacts + uses: actions/download-artifact@v6 with: pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} merge-multiple: true path: ./publish + - name: Get manifest artifact + uses: actions/download-artifact@v6 + with: + pattern: manifest-${{ needs.version.outputs.long }} + path: ./publish + - name: Publish firmware to meshtastic.github.io uses: peaceiris/actions-gh-pages@v4 env: diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index 154b230c7..b9bb3ceed 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -168,7 +168,7 @@ jobs: ./firmware-*.bin ./firmware-*.uf2 ./firmware-*.hex - ./firmware-*-ota.zip + ./firmware-*.zip ./device-*.sh ./device-*.bat ./littlefs-*.bin @@ -197,7 +197,7 @@ jobs: - name: Repackage in single elfs zip uses: actions/upload-artifact@v5 with: - name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }} overwrite: true path: ./*.elf retention-days: 30 @@ -223,11 +223,6 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - name: Create release uses: softprops/action-gh-release@v2 id: create_release @@ -316,7 +311,7 @@ jobs: - uses: actions/download-artifact@v6 with: - name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }} merge-multiple: true path: ./elfs diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index 048186538..a3e0b23cf 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -52,7 +52,7 @@ jobs: if: needs.native-tests.result != 'skipped' uses: actions/download-artifact@v6 with: - name: platformio-test-report-${{ steps.version.outputs.long }}.zip + name: platformio-test-report-${{ steps.version.outputs.long }} merge-multiple: true - name: Parse test results and create detailed summary diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml index f21b13ee1..badbb31d4 100644 --- a/.github/workflows/release_channels.yml +++ b/.github/workflows/release_channels.yml @@ -102,7 +102,7 @@ jobs: PIP_DISABLE_PIP_VERSION_CHECK: 1 - name: Create Bumps pull request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: base: ${{ github.event.repository.default_branch }} branch: create-pull-request/bump-version diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml index decd23954..26ff306a9 100644 --- a/.github/workflows/test_native.yml +++ b/.github/workflows/test_native.yml @@ -40,7 +40,7 @@ jobs: - name: Integration test run: | - .pio/build/coverage/program -s & + .pio/build/coverage/meshtasticd -s & PID=$! timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done" echo "Simulator started, launching python test..." @@ -62,7 +62,7 @@ jobs: uses: actions/upload-artifact@v5 if: always() # run this step even if previous step failed with: - name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}.zip + name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }} overwrite: true path: ./coverage_*.info @@ -96,7 +96,7 @@ jobs: if: always() # run this step even if previous step failed uses: actions/upload-artifact@v5 with: - name: platformio-test-report-${{ steps.version.outputs.long }}.zip + name: platformio-test-report-${{ steps.version.outputs.long }} overwrite: true path: ./testreport.xml @@ -111,7 +111,7 @@ jobs: uses: actions/upload-artifact@v5 if: always() # run this step even if previous step failed with: - name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}.zip + name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }} overwrite: true path: ./coverage_*.info @@ -139,7 +139,7 @@ jobs: - name: Download test artifacts uses: actions/download-artifact@v6 with: - name: platformio-test-report-${{ steps.version.outputs.long }}.zip + name: platformio-test-report-${{ steps.version.outputs.long }} merge-multiple: true - name: Test Report @@ -152,7 +152,7 @@ jobs: - name: Download coverage artifacts uses: actions/download-artifact@v6 with: - pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip + pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }} path: code-coverage-report merge-multiple: true @@ -165,5 +165,5 @@ jobs: - name: Save Code Coverage Report uses: actions/upload-artifact@v5 with: - name: code-coverage-report-${{ steps.version.outputs.long }}.zip + name: code-coverage-report-${{ steps.version.outputs.long }} path: code-coverage-report diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4a97853e2..241f2cd10 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout code uses: actions/checkout@v6 - # - uses: actions/setup-python@v5 + # - uses: actions/setup-python@v6 # with: # python-version: '3.10' diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index af0557fda..d9ef98194 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -31,7 +31,7 @@ jobs: ./bin/regen-protos.sh - name: Create pull request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: branch: create-pull-request/update-protobufs labels: submodules diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 95e5b0dd2..565433c38 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,24 +9,24 @@ plugins: lint: enabled: - checkov@3.2.495 - - renovate@42.30.4 + - renovate@42.42.2 - prettier@3.7.4 - - trufflehog@3.91.2 + - trufflehog@3.92.1 - yamllint@1.37.1 - bandit@1.9.2 - - trivy@0.67.2 + - trivy@0.68.1 - taplo@0.10.0 - - ruff@0.14.7 + - ruff@0.14.8 - isort@7.0.0 - markdownlint@0.46.0 - - oxipng@9.1.5 + - oxipng@10.0.0 - svgo@4.0.0 - actionlint@1.7.9 - flake8@7.3.0 - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - - black@25.11.0 + - black@25.12.0 - git-diff-check - gitleaks@8.30.0 - clang-format@16.0.3 diff --git a/bin/build-esp32.sh b/bin/build-esp32.sh index 92836db23..8c684aa7e 100755 --- a/bin/build-esp32.sh +++ b/bin/build-esp32.sh @@ -5,7 +5,8 @@ set -e VERSION=`bin/buildinfo.py long` SHORT_VERSION=`bin/buildinfo.py short` -OUTDIR=release/ +BUILDDIR=.pio/build/$1 +OUTDIR=release rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true @@ -14,7 +15,7 @@ rm -r $OUTDIR/* || true platformio pkg install -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" -rm -f .pio/build/$1/firmware.* +rm -f $BUILDDIR/firmware* # The shell vars the build tool expects to find export APP_VERSION=$VERSION @@ -22,16 +23,14 @@ export APP_VERSION=$VERSION basename=firmware-$1-$VERSION pio run --environment $1 # -v -SRCELF=.pio/build/$1/firmware.elf -cp $SRCELF $OUTDIR/$basename.elf + +cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf echo "Copying ESP32 bin file" -SRCBIN=.pio/build/$1/firmware.factory.bin -cp $SRCBIN $OUTDIR/$basename.bin +cp $BUILDDIR/$basename.factory.bin $OUTDIR/$basename.factory.bin echo "Copying ESP32 update bin file" -SRCBIN=.pio/build/$1/firmware.bin -cp $SRCBIN $OUTDIR/$basename-update.bin +cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin echo "Building Filesystem for ESP32 targets" # If you want to build the webui, uncomment the following lines @@ -40,7 +39,13 @@ echo "Building Filesystem for ESP32 targets" # # Remove webserver files from the filesystem and rebuild # ls -l data/static # Diagnostic list of files # rm -rf data/static -pio run --environment $1 -t buildfs -cp .pio/build/$1/littlefs.bin $OUTDIR/littlefs-$1-$VERSION.bin -cp bin/device-install.* $OUTDIR -cp bin/device-update.* $OUTDIR \ No newline at end of file +pio run --environment $1 -t buildfs --disable-auto-clean +cp $BUILDDIR/littlefs-$1-$VERSION.bin $OUTDIR/littlefs-$1-$VERSION.bin +cp bin/device-install.* $OUTDIR/ +cp bin/device-update.* $OUTDIR/ + +# Generate the manifest file +echo "Generating Meshtastic manifest" +TIMEFORMAT="Generated manifest in %E seconds" +time pio run --environment $1 -t mtjson --silent --disable-auto-clean +cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json diff --git a/bin/build-native.sh b/bin/build-native.sh index fff86e87e..f35e46a87 100755 --- a/bin/build-native.sh +++ b/bin/build-native.sh @@ -17,15 +17,19 @@ VERSION=$(bin/buildinfo.py long) SHORT_VERSION=$(bin/buildinfo.py short) PIO_ENV=${1:-native} -OUTDIR=release/ +BUILDDIR=.pio/build/$PIO_ENV +OUTDIR=release -rm -f $OUTDIR/firmware* +rm -f $OUTDIR/meshtasticd* mkdir -p $OUTDIR/ rm -r $OUTDIR/* || true +basename=meshtasticd-$1-$VERSION + # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale 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 + +cp "$BUILDDIR/meshtasticd" "$OUTDIR/meshtasticd_linux_$(uname -m)" +cp bin/native-install.* $OUTDIR/ diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh index deca209d2..c605fb1e0 100755 --- a/bin/build-nrf52.sh +++ b/bin/build-nrf52.sh @@ -5,7 +5,8 @@ set -e VERSION=$(bin/buildinfo.py long) SHORT_VERSION=$(bin/buildinfo.py short) -OUTDIR=release/ +BUILDDIR=.pio/build/$1 +OUTDIR=release rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true @@ -14,7 +15,7 @@ rm -r $OUTDIR/* || true platformio pkg install -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" -rm -f .pio/build/$1/firmware.* +rm -f $BUILDDIR/firmware* # The shell vars the build tool expects to find export APP_VERSION=$VERSION @@ -22,32 +23,32 @@ export APP_VERSION=$VERSION basename=firmware-$1-$VERSION pio run --environment $1 # -v -SRCELF=.pio/build/$1/firmware.elf -cp $SRCELF $OUTDIR/$basename.elf -echo "Generating NRF52 dfu file" -DFUPKG=.pio/build/$1/firmware.zip -cp $DFUPKG $OUTDIR/$basename-ota.zip +cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf -echo "Generating NRF52 uf2 file" -SRCHEX=.pio/build/$1/firmware.hex +echo "Copying NRF52 dfu (OTA) file" +cp $BUILDDIR/$basename.zip $OUTDIR/$basename.zip -# if WM1110 target, merge hex with softdevice 7.3.0 +echo "Copying NRF52 UF2 file" +cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2 +cp bin/*.uf2 $OUTDIR/ + +SRCHEX=$BUILDDIR/$basename.hex + +# if WM1110 target, copy the merged.hex if (echo $1 | grep -q "wio-sdk-wm1110"); then - echo "Merging with softdevice" - bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/$basename.hex - SRCHEX=.pio/build/$1/$basename.hex - bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 - cp $SRCHEX $OUTDIR - cp bin/*.uf2 $OUTDIR -else - bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840 - cp bin/device-install.* $OUTDIR - cp bin/device-update.* $OUTDIR - cp bin/*.uf2 $OUTDIR + echo "Copying .merged.hex file" + SRCHEX=$BUILDDIR/$basename.merged.hex + cp $SRCHEX $OUTDIR/ fi if (echo $1 | grep -q "rak4631"); then - echo "Copying hex file" - cp .pio/build/$1/firmware.hex $OUTDIR/$basename.hex -fi \ No newline at end of file + echo "Copying .hex file" + cp $SRCHEX $OUTDIR/ +fi + +# Generate the manifest file +echo "Generating Meshtastic manifest" +TIMEFORMAT="Generated manifest in %E seconds" +time pio run --environment $1 -t mtjson --silent --disable-auto-clean +cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json diff --git a/bin/build-rp2xx0.sh b/bin/build-rp2xx0.sh index cb4865914..ae26fdfbf 100755 --- a/bin/build-rp2xx0.sh +++ b/bin/build-rp2xx0.sh @@ -5,7 +5,8 @@ set -e VERSION=`bin/buildinfo.py long` SHORT_VERSION=`bin/buildinfo.py short` -OUTDIR=release/ +BUILDDIR=.pio/build/$1 +OUTDIR=release rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true @@ -14,7 +15,7 @@ rm -r $OUTDIR/* || true platformio pkg install -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" -rm -f .pio/build/$1/firmware.* +rm -f $BUILDDIR/firmware* # The shell vars the build tool expects to find export APP_VERSION=$VERSION @@ -22,12 +23,14 @@ export APP_VERSION=$VERSION basename=firmware-$1-$VERSION pio run --environment $1 # -v -SRCELF=.pio/build/$1/firmware.elf -cp $SRCELF $OUTDIR/$basename.elf + +cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf echo "Copying uf2 file" -SRCBIN=.pio/build/$1/firmware.uf2 -cp $SRCBIN $OUTDIR/$basename.uf2 +cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2 -cp bin/device-install.* $OUTDIR -cp bin/device-update.* $OUTDIR +# Generate the manifest file +echo "Generating Meshtastic manifest" +TIMEFORMAT="Generated manifest in %E seconds" +time pio run --environment $1 -t mtjson --silent --disable-auto-clean +cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json diff --git a/bin/build-stm32wl.sh b/bin/build-stm32wl.sh index f62df4842..b85da04a6 100755 --- a/bin/build-stm32wl.sh +++ b/bin/build-stm32wl.sh @@ -5,7 +5,8 @@ set -e VERSION=$(bin/buildinfo.py long) SHORT_VERSION=$(bin/buildinfo.py short) -OUTDIR=release/ +BUILDDIR=.pio/build/$1 +OUTDIR=release rm -f $OUTDIR/firmware* rm -r $OUTDIR/* || true @@ -14,7 +15,7 @@ rm -r $OUTDIR/* || true platformio pkg install -e $1 echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" -rm -f .pio/build/$1/firmware.* +rm -f $BUILDDIR/firmware* # The shell vars the build tool expects to find export APP_VERSION=$VERSION @@ -22,8 +23,14 @@ export APP_VERSION=$VERSION basename=firmware-$1-$VERSION pio run --environment $1 # -v -SRCELF=.pio/build/$1/firmware.elf -cp $SRCELF $OUTDIR/$basename.elf -SRCBIN=.pio/build/$1/firmware.bin -cp $SRCBIN $OUTDIR/$basename.bin +cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf + +echo "Copying STM32 bin file" +cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin + +# Generate the manifest file +echo "Generating Meshtastic manifest" +TIMEFORMAT="Generated manifest in %E seconds" +time pio run --environment $1 -t mtjson --silent --disable-auto-clean +cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json diff --git a/bin/device-install.bat b/bin/device-install.bat index 519073b08..c200a3201 100755 --- a/bin/device-install.bat +++ b/bin/device-install.bat @@ -5,22 +5,14 @@ TITLE Meshtastic device-install SET "SCRIPT_NAME=%~nx0" SET "DEBUG=0" SET "PYTHON=" -SET "TFT_BUILD=0" -SET "BIGDB8=0" -SET "MUIDB8=0" -SET "BIGDB16=0" SET "ESPTOOL_BAUD=115200" SET "ESPTOOL_CMD=" SET "LOGCOUNTER=0" SET "BPS_RESET=0" - -@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable. -SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv heltec-v4" -SET "C3=esp32c3" -@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable. -SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger" -SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator" -SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv heltec-v4" +@REM Default offsets. +@REM https://github.com/meshtastic/web-flasher/blob/main/stores/firmwareStore.ts#L202 +SET "OTA_OFFSET=0x260000" +SET "SPIFFS_OFFSET=0x300000" GOTO getopts :help @@ -29,7 +21,7 @@ ECHO. ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] [--1200bps-reset] ECHO. ECHO Options: -ECHO -f filename The firmware .bin file to flash. Custom to your device type and region. (required) +ECHO -f filename The firmware .factory.bin file to flash. Custom to your device type and region. (required) ECHO The file must be located in this current directory. ECHO -p PORT Set the environment variable for ESPTOOL_PORT. ECHO If not set, ESPTOOL iterates all ports (Dangerous). @@ -40,12 +32,12 @@ ECHO --1200bps-reset Attempt to place the device in correct mode. (1200bps ECHO Some hardware requires this twice. ECHO. ECHO Example: %SCRIPT_NAME% -p COM17 --1200bps-reset -ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4.bin -p COM11 -ECHO Example: %SCRIPT_NAME% -f firmware-unphone-2.6.0.0b106d4.bin -p COM11 +ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4.factory.bin -p COM11 +ECHO Example: %SCRIPT_NAME% -f firmware-unphone-2.6.0.0b106d4.factory.bin -p COM11 GOTO eof :version -ECHO %SCRIPT_NAME% [Version 2.6.2] +ECHO %SCRIPT_NAME% [Version 2.7.0] ECHO Meshtastic GOTO eof @@ -78,8 +70,8 @@ IF "__!FILENAME!__"=="____" ( CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported." GOTO help ) - IF "__!FILENAME:firmware-=!__"=="__!FILENAME!__" ( - CALL :LOG_MESSAGE ERROR "Filename must be a firmware-* file." + IF NOT "__!FILENAME:.factory.bin=!__"=="__!FILENAME!__" ( + CALL :LOG_MESSAGE ERROR "Filename must be a firmware-*.factory.bin file." GOTO help ) @REM Remove ".\" or "./" file prefix if present. @@ -93,12 +85,26 @@ IF NOT EXIST !FILENAME! ( GOTO eof ) -IF NOT "!FILENAME:update=!"=="!FILENAME!" ( - CALL :LOG_MESSAGE DEBUG "We are working with a *update* file. !FILENAME!" - CALL :LOG_MESSAGE INFO "Use script device-update.bat to flash update !FILENAME!." - GOTO eof +CALL :LOG_MESSAGE DEBUG "Checking for metadata..." +@REM Derive metadata filename from firmware filename. +SET "METAFILE=!FILENAME:.factory.bin=!.mt.json" +IF EXIST !METAFILE! ( + @REM Print parsed json with powershell + CALL :LOG_MESSAGE INFO "Firmware metadata: !METAFILE!" + powershell -NoProfile -Command "(Get-Content '!METAFILE!' | ConvertFrom-Json | Out-String).Trim()" + + @REM Save metadata values to variables for later use. + FOR /f "usebackq" %%A IN (`powershell -NoProfile -Command ^ + "(Get-Content '!METAFILE!' | ConvertFrom-Json).mcu"`) DO SET "MCU=%%A" + FOR /f "usebackq" %%A IN (`powershell -NoProfile -Command ^ + "(Get-Content '!METAFILE!' | ConvertFrom-Json).part | Where-Object { $_.subtype -eq 'ota_1' } | Select-Object -ExpandProperty offset"` + ) DO SET "OTA_OFFSET=%%A" + FOR /f "usebackq" %%A IN (`powershell -NoProfile -Command ^ + "(Get-Content '!METAFILE!' | ConvertFrom-Json).part | Where-Object { $_.subtype -eq 'spiffs' } | Select-Object -ExpandProperty offset"` + ) DO SET "SPIFFS_OFFSET=%%A" ) ELSE ( - CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* file. !FILENAME!" + CALL :LOG_MESSAGE ERROR "No metadata file found: !METAFILE!" + GOTO eof ) :skip-filename @@ -108,7 +114,7 @@ IF NOT "__%PYTHON%__"=="____" ( SET "ESPTOOL_CMD=!PYTHON! -m esptool" CALL :LOG_MESSAGE DEBUG "Python interpreter supplied." ) ELSE ( - CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool... + CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool..." WHERE esptool >nul 2>&1 IF %ERRORLEVEL% EQU 0 ( @REM WHERE exits with code 0 if esptool is found. @@ -146,100 +152,26 @@ IF %BPS_RESET% EQU 1 ( GOTO eof ) -@REM Check if FILENAME contains "-tft-" and set target partitionScheme accordingly. -@REM https://github.com/meshtastic/web-flasher/blob/main/types/resources.ts#L3 -IF NOT "!FILENAME:-tft-=!"=="!FILENAME!" ( - CALL :LOG_MESSAGE DEBUG "We are working with a *-tft-* file. !FILENAME!" - SET "TFT_BUILD=1" +@REM Extract PROGNAME from %FILENAME% for later use. +SET "PROGNAME=!FILENAME:.factory.bin=!" +CALL :LOG_MESSAGE DEBUG "Computed PROGNAME: !PROGNAME!" + +IF "__!MCU!__" == "__esp32s3__" ( + @REM We are working with ESP32-S3 + SET "OTA_FILENAME=bleota-s3.bin" +) ELSE IF "__!MCU!__" == "__esp32c3__" ( + @REM We are working with ESP32-C3 + SET "OTA_FILENAME=bleota-c3.bin" ) ELSE ( - CALL :LOG_MESSAGE DEBUG "We are NOT working with a *-tft-* file. !FILENAME!" + @REM Everything else + SET "OTA_FILENAME=bleota.bin" ) - -FOR %%a IN (%BIGDB_8MB%) DO ( - IF NOT "!FILENAME:%%a=!"=="!FILENAME!" ( - @REM We are working with any of %BIGDB_8MB%. - SET "BIGDB8=1" - GOTO end_loop_bigdb_8mb - ) -) -:end_loop_bigdb_8mb - -FOR %%a IN (%MUIDB_8MB%) DO ( - IF NOT "!FILENAME:%%a=!"=="!FILENAME!" ( - @REM We are working with any of %MUIDB_8MB%. - SET "MUIDB8=1" - GOTO end_loop_muidb_8mb - ) -) -:end_loop_muidb_8mb - -FOR %%a IN (%BIGDB_16MB%) DO ( - IF NOT "!FILENAME:%%a=!"=="!FILENAME!" ( - @REM We are working with any of %BIGDB_16MB%. - SET "BIGDB16=1" - GOTO end_loop_bigdb_16mb - ) -) -:end_loop_bigdb_16mb - -IF %BIGDB8% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 8mb partition selected." -IF %MUIDB8% EQU 1 CALL :LOG_MESSAGE INFO "MUIDB 8mb partition selected." -IF %BIGDB16% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 16mb partition selected." - -@REM Extract BASENAME from %FILENAME% for later use. -SET "BASENAME=!FILENAME:firmware-=!" -CALL :LOG_MESSAGE DEBUG "Computed firmware basename: !BASENAME!" - -@REM Account for S3 and C3 board's different OTA partition. -FOR %%a IN (%S3%) DO ( - IF NOT "!FILENAME:%%a=!"=="!FILENAME!" ( - @REM We are working with any of %S3%. - SET "OTA_FILENAME=bleota-s3.bin" - GOTO :end_loop_s3 - ) -) - -FOR %%a IN (%C3%) DO ( - IF NOT "!FILENAME:%%a=!"=="!FILENAME!" ( - @REM We are working with any of %C3%. - SET "OTA_FILENAME=bleota-c3.bin" - GOTO :end_loop_c3 - ) -) - -@REM Everything else -SET "OTA_FILENAME=bleota.bin" -:end_loop_s3 -:end_loop_c3 CALL :LOG_MESSAGE DEBUG "Set OTA_FILENAME to: !OTA_FILENAME!" @REM Set SPIFFS filename with "littlefs-" prefix. -SET "SPIFFS_FILENAME=littlefs-%BASENAME%" +SET "SPIFFS_FILENAME=littlefs-!PROGNAME:firmware-=!.bin" CALL :LOG_MESSAGE DEBUG "Set SPIFFS_FILENAME to: !SPIFFS_FILENAME!" -@REM Default offsets. -@REM https://github.com/meshtastic/web-flasher/blob/main/stores/firmwareStore.ts#L202 -SET "OTA_OFFSET=0x260000" -SET "SPIFFS_OFFSET=0x300000" - -@REM Offsets for BigDB 8mb. -IF %BIGDB8% EQU 1 ( - SET "OTA_OFFSET=0x340000" - SET "SPIFFS_OFFSET=0x670000" -) - -@REM Offsets for MUIDB 8mb. -IF %MUIDB8% EQU 1 ( - SET "OTA_OFFSET=0x5D0000" - SET "SPIFFS_OFFSET=0x670000" -) - -@REM Offsets for BigDB 16mb. -IF %BIGDB16% EQU 1 ( - SET "OTA_OFFSET=0x650000" - SET "SPIFFS_OFFSET=0xc90000" -) - CALL :LOG_MESSAGE DEBUG "Set OTA_OFFSET to: !OTA_OFFSET!" CALL :LOG_MESSAGE DEBUG "Set SPIFFS_OFFSET to: !SPIFFS_OFFSET!" diff --git a/bin/device-install.sh b/bin/device-install.sh index 69e4794ba..1778a952d 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -2,69 +2,15 @@ PYTHON=${PYTHON:-$(which python3 python | head -n 1)} BPS_RESET=false -TFT_BUILD=false MCU="" # Constants RESET_BAUD=1200 FIRMWARE_OFFSET=0x00 - -# Variant groups -BIGDB_8MB=( - "crowpanel-esp32s3" - "heltec_capsule_sensor_v3" - "heltec-v3" - "heltec-vision-master-e213" - "heltec-vision-master-e290" - "heltec-vision-master-t190" - "heltec-wireless-paper" - "heltec-wireless-tracker" - "heltec-wsl-v3" - "icarus" - "seeed-xiao-s3" - "tbeam-s3-core" - "tracksenger" -) -MUIDB_8MB=( - "picomputer-s3" - "unphone" - "seeed-sensecap-indicator" -) -BIGDB_16MB=( - "dreamcatcher" - "elecrow-adv" - "ESP32-S3-Pico" - "heltec-v4" - "m5stack-cores3" - "mesh-tab" - "station-g2" - "t-deck" - "t-energy-s3" - "t-eth-elite" - "t-watch-s3" - "tlora-pager" -) -S3_VARIANTS=( - "s3" - "-v3" - "-v4" - "t-deck" - "wireless-paper" - "wireless-tracker" - "station-g2" - "unphone" - "t-eth-elite" - "tlora-pager" - "mesh-tab" - "dreamcatcher" - "ESP32-S3-Pico" - "seeed-sensecap-indicator" - "heltec_capsule_sensor_v3" - "vision-master" - "icarus" - "tracksenger" - "elecrow-adv" -) +# Default littlefs* offset. +OFFSET=0x300000 +# Default OTA Offset +OTA_OFFSET=0x260000 # Determine the correct esptool command to use if "$PYTHON" -m esptool version >/dev/null 2>&1; then @@ -78,6 +24,14 @@ else exit 1 fi +# Check for jq +if ! command -v jq >/dev/null 2>&1; then + echo "Error: jq not found" >&2 + echo "Install jq with your package manager." >&2 + echo "e.g. 'apt install jq', 'dnf install jq', 'brew install jq', etc." >&2 + exit 1 +fi + set -e # Usage info @@ -89,7 +43,7 @@ Flash image file to device, but first erasing and writing system information. -h Display this help and exit. -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous). -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON") - -f FILENAME The firmware .bin file to flash. Custom to your device type and region. + -f FILENAME The firmware *.factory.bin file to flash. Custom to your device type and region. --1200bps-reset Attempt to place the device in correct mode. Some hardware requires this twice. (1200bps Reset) EOF @@ -138,69 +92,43 @@ fi shift } -if [[ "$FILENAME" != firmware-* ]]; then - echo "Filename must be a firmware-* file." +if [[ $(basename "$FILENAME") != firmware-*.factory.bin ]]; then + echo "Filename must be a firmware-*.factory.bin file." exit 1 fi -# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly. -if [[ "${FILENAME//-tft-/}" != "$FILENAME" ]]; then - TFT_BUILD=true -fi +# Extract PROGNAME from %FILENAME% for later use. +PROGNAME="${FILENAME/.factory.bin/}" +# Derive metadata filename from %PROGNAME%. +METAFILE="${PROGNAME}.mt.json" -# Extract BASENAME from %FILENAME% for later use. -BASENAME="${FILENAME/firmware-/}" - -if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then - # Default littlefs* offset. - OFFSET=0x300000 - - # Default OTA Offset - OTA_OFFSET=0x260000 - - # littlefs* offset for BigDB 8mb and OTA OFFSET. - for variant in "${BIGDB_8MB[@]}"; do - if [ -z "${FILENAME##*"$variant"*}" ]; then - OFFSET=0x670000 - OTA_OFFSET=0x340000 - fi - done - - for variant in "${MUIDB_8MB[@]}"; do - if [ -z "${FILENAME##*"$variant"*}" ]; then - OFFSET=0x670000 - OTA_OFFSET=0x5D0000 - fi - done - - # littlefs* offset for BigDB 16mb and OTA OFFSET. - for variant in "${BIGDB_16MB[@]}"; do - if [ -z "${FILENAME##*"$variant"*}" ]; then - OFFSET=0xc90000 - OTA_OFFSET=0x650000 - fi - done - - # Account for S3 board's different OTA partition - # FIXME: Use PlatformIO info to determine MCU type, this is unmaintainable - for variant in "${S3_VARIANTS[@]}"; do - if [ -z "${FILENAME##*"$variant"*}" ]; then - MCU="esp32s3" - fi - done - - if [ "$MCU" != "esp32s3" ]; then - if [ -n "${FILENAME##*"esp32c3"*}" ]; then - OTAFILE=bleota.bin - else - OTAFILE=bleota-c3.bin +if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then + # Display metadata if it exists + if [[ -f "$METAFILE" ]]; then + echo "Firmware metadata: ${METAFILE}" + jq . "$METAFILE" + # Extract relevant fields from metadata + if [[ $(jq -r '.part' "$METAFILE") != "null" ]]; then + OTA_OFFSET=$(jq -r '.part[] | select(.subtype == "ota_1") | .offset' "$METAFILE") + SPIFFS_OFFSET=$(jq -r '.part[] | select(.subtype == "spiffs") | .offset' "$METAFILE") fi + MCU=$(jq -r '.mcu' "$METAFILE") else + echo "ERROR: No metadata file found at ${METAFILE}" + exit 1 + fi + + # Determine OTA filename based on MCU type + if [ "$MCU" == "esp32s3" ]; then OTAFILE=bleota-s3.bin + elif [ "$MCU" == "esp32c3" ]; then + OTAFILE=bleota-c3.bin + else + OTAFILE=bleota.bin fi # Set SPIFFS filename with "littlefs-" prefix. - SPIFFSFILE=littlefs-${BASENAME} + SPIFFSFILE="littlefs-${PROGNAME/firmware-/}.bin" if [[ ! -f "$FILENAME" ]]; then echo "Error: file ${FILENAME} wasn't found. Terminating." diff --git a/bin/device-update.bat b/bin/device-update.bat index a263da992..a9f7a9e1e 100755 --- a/bin/device-update.bat +++ b/bin/device-update.bat @@ -30,11 +30,11 @@ ECHO --change-mode Attempt to place the device in correct mode. (1200bps ECHO Some hardware requires this twice. ECHO. ECHO Example: %SCRIPT_NAME% -p COM17 --change-mode -ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4-update.bin -p COM11 +ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4.bin -p COM11 GOTO eof :version -ECHO %SCRIPT_NAME% [Version 2.6.2] +ECHO %SCRIPT_NAME% [Version 2.7.0] ECHO Meshtastic GOTO eof @@ -78,12 +78,12 @@ IF NOT EXIST !FILENAME! ( GOTO eof ) -IF "!FILENAME:update=!"=="!FILENAME!" ( - CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* file. !FILENAME!" +IF NOT "__!FILENAME:.factory.bin=!__"=="__!FILENAME!__" ( + CALL :LOG_MESSAGE DEBUG "We are working with a *.factory.bin* file. !FILENAME!" CALL :LOG_MESSAGE INFO "Use script device-install.bat to flash !FILENAME!." GOTO eof ) ELSE ( - CALL :LOG_MESSAGE DEBUG "We are working with a *update* file. !FILENAME!" + CALL :LOG_MESSAGE DEBUG "We are not working with a *.factory.bin* file. !FILENAME!" ) :skip-filename diff --git a/bin/device-update.sh b/bin/device-update.sh index f64280a5b..1c3d6be70 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -29,7 +29,7 @@ Flash image file to device, leave existing system intact." -h Display this help and exit -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous). -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON") - -f FILENAME The *update.bin file to flash. Custom to your device type. + -f FILENAME The *.bin file to flash. Custom to your device type. --change-mode Attempt to place the device in correct mode. Some hardware requires this twice. (1200bps Reset) EOF @@ -78,7 +78,7 @@ fi shift } -if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then +if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then echo "Trying to flash update ${FILENAME}" $ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}" else diff --git a/bin/native-gdbserver.sh b/bin/native-gdbserver.sh index f779d6670..a45a2dc26 100755 --- a/bin/native-gdbserver.sh +++ b/bin/native-gdbserver.sh @@ -2,4 +2,4 @@ set -e pio run --environment native -gdbserver --once localhost:2345 .pio/build/native/program "$@" +gdbserver --once localhost:2345 .pio/build/native/meshtasticd "$@" diff --git a/bin/native-run.sh b/bin/native-run.sh index 6566fc591..a8309c2d3 100755 --- a/bin/native-run.sh +++ b/bin/native-run.sh @@ -2,4 +2,4 @@ set -e pio run --environment native -.pio/build/native/program "$@" +.pio/build/native/meshtasticd "$@" diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index 243edca0c..140ac3e2a 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,9 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.16 diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index 4a1887d9d..151cf0a97 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -2,98 +2,77 @@ # trunk-ignore-all(ruff/F821) # trunk-ignore-all(flake8/F821): For SConstruct imports import sys -from os.path import join +from os.path import join, basename, isfile import subprocess import json import re -import time from datetime import datetime from readprops import readProps Import("env") platform = env.PioPlatform() +progname = env.get("PROGNAME") +lfsbin = f"{progname.replace('firmware-', 'littlefs-')}.bin" - -def esp32_create_combined_bin(source, target, env): - # this sub is borrowed from ESPEasy build toolchain. It's licensed under GPL V3 - # https://github.com/letscontrolit/ESPEasy/blob/mega/tools/pio/post_esp32.py - print("Generating combined binary for serial flashing") - - app_offset = 0x10000 - - new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin") - sections = env.subst(env.get("FLASH_EXTRA_IMAGES")) - firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") - chip = env.get("BOARD_MCU") - flash_size = env.BoardConfig().get("upload.flash_size") - flash_freq = env.BoardConfig().get("build.f_flash", "40m") - flash_freq = flash_freq.replace("000000L", "m") - flash_mode = env.BoardConfig().get("build.flash_mode", "dio") - memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi") - if flash_mode == "qio" or flash_mode == "qout": - flash_mode = "dio" - if memory_type == "opi_opi" or memory_type == "opi_qspi": - flash_mode = "dout" - cmd = [ - "--chip", - chip, - "merge_bin", - "-o", - new_file_name, - "--flash_mode", - flash_mode, - "--flash_freq", - flash_freq, - "--flash_size", - flash_size, +def manifest_gather(source, target, env): + out = [] + check_paths = [ + progname, + f"{progname}.elf", + f"{progname}.bin", + f"{progname}.factory.bin", + f"{progname}.hex", + f"{progname}.merged.hex", + f"{progname}.uf2", + f"{progname}.factory.uf2", + f"{progname}.zip", + lfsbin ] + for p in check_paths: + f = env.File(env.subst(f"$BUILD_DIR/{p}")) + if f.exists(): + d = { + "name": p, + "md5": f.get_content_hash(), # Returns MD5 hash + "bytes": f.get_size() # Returns file size in bytes + } + out.append(d) + print(d) + manifest_write(out, env) - print(" Offset | File") - for section in sections: - sect_adr, sect_file = section.split(" ", 1) - print(f" - {sect_adr} | {sect_file}") - cmd += [sect_adr, sect_file] +def manifest_write(files, env): + manifest = { + "version": verObj["long"], + "build_epoch": build_epoch, + "board": env.get("PIOENV"), + "mcu": env.get("BOARD_MCU"), + "repo": repo_owner, + "files": files, + "part": None, + "has_mui": False, + "has_inkhud": False, + } + # Get partition table (generated in esp32_pre.py) if it exists + if env.get("custom_mtjson_part"): + # custom_mtjson_part is a JSON string, convert it back to a dict + pj = json.loads(env.get("custom_mtjson_part")) + manifest["part"] = pj + # Enable has_mui for TFT builds + if ("HAS_TFT", 1) in env.get("CPPDEFINES", []): + manifest["has_mui"] = True + if "MESHTASTIC_INCLUDE_INKHUD" in env.get("CPPDEFINES", []): + manifest["has_inkhud"] = True - print(f" - {hex(app_offset)} | {firmware_name}") - cmd += [hex(app_offset), firmware_name] - - print("Using esptool.py arguments: %s" % " ".join(cmd)) - - esptool.main(cmd) - - -if platform.name == "espressif32": - sys.path.append(join(platform.get_package_dir("tool-esptoolpy"))) - import esptool - - env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) - - esp32_kind = env.GetProjectOption("custom_esp32_kind") - if esp32_kind == "esp32": - # Free up some IRAM by removing auxiliary SPI flash chip drivers. - # Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c. - env.Append( - LINKFLAGS=[ - "-Wl,--wrap=esp_flash_chip_gd", - "-Wl,--wrap=esp_flash_chip_issi", - "-Wl,--wrap=esp_flash_chip_winbond", - ] - ) - else: - # For newer ESP32 targets, using newlib nano works better. - env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) - -if platform.name == "nordicnrf52": - env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", - env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py \"$BUILD_DIR/firmware.hex\" -c -f 0xADA52840 -o \"$BUILD_DIR/firmware.uf2\"", - "Generating UF2 file")) + # Write the manifest to the build directory + with open(env.subst("$BUILD_DIR/${PROGNAME}.mt.json"), "w") as f: + json.dump(manifest, f, indent=2) Import("projenv") prefsLoc = projenv["PROJECT_DIR"] + "/version.properties" verObj = readProps(prefsLoc) -print("Using meshtastic platformio-custom.py, firmware version " + verObj["long"] + " on " + env.get("PIOENV")) +print(f"Using meshtastic platformio-custom.py, firmware version {verObj['long']} on {env.get('PIOENV')}") # get repository owner if git is installed try: @@ -139,10 +118,10 @@ flags = [ "-DBUILD_EPOCH=" + str(build_epoch), ] + pref_flags -print ("Using flags:") +print("Using flags:") for flag in flags: print(flag) - + projenv.Append( CCFLAGS=flags, ) @@ -181,3 +160,19 @@ def load_boot_logo(source, target, env): # Load the boot logo on TFT builds if ("HAS_TFT", 1) in env.get("CPPDEFINES", []): env.AddPreAction('$BUILD_DIR/littlefs.bin', load_boot_logo) + +# Rename (mv) littlefs.bin to include the PROGNAME +# This ensures the littlefs.bin is named consistently with the firmware +env.AddPostAction('$BUILD_DIR/littlefs.bin', env.VerboseAction( + f'mv $BUILD_DIR/littlefs.bin $BUILD_DIR/{lfsbin}', + f'Renaming littlefs.bin to {lfsbin}' +)) + +env.AddCustomTarget( + name="mtjson", + dependencies=None, + actions=[manifest_gather], + title="Meshtastic Manifest", + description="Generating Meshtastic manifest JSON + Checksums", + always_build=True, +) diff --git a/bin/platformio-pre.py b/bin/platformio-pre.py new file mode 100644 index 000000000..4e51a6544 --- /dev/null +++ b/bin/platformio-pre.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports +Import("env") +platform = env.PioPlatform() + +if platform.name == "native": + env.Replace(PROGNAME="meshtasticd") +else: + from readprops import readProps + prefsLoc = env["PROJECT_DIR"] + "/version.properties" + verObj = readProps(prefsLoc) + env.Replace(PROGNAME=f"firmware-{env.get('PIOENV')}-{verObj['long']}") + +# Print the new program name for verification +print(f"PROGNAME: {env.get('PROGNAME')}") diff --git a/bin/test-simulator.sh b/bin/test-simulator.sh index 3c5f8f811..92ed21a7a 100755 --- a/bin/test-simulator.sh +++ b/bin/test-simulator.sh @@ -3,7 +3,7 @@ set -e echo "Starting simulator" -.pio/build/native/program & +.pio/build/native/meshtasticd -s & sleep 20 # 5 seconds was not enough echo "Simulator started, launching python test..." diff --git a/boards/hackaday-communicator.json b/boards/hackaday-communicator.json new file mode 100644 index 000000000..6e6c1ad2d --- /dev/null +++ b/boards/hackaday-communicator.json @@ -0,0 +1,41 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "hackaday-communicator" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "hackaday-communicator (16 MB FLASH, 8 MB PSRAM)", + "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": 1500000 + }, + "url": "hackaday.com", + "vendor": "hackaday" +} diff --git a/boards/heltec_v4.json b/boards/heltec_v4.json index 8eac3a9b2..9827be83f 100644 --- a/boards/heltec_v4.json +++ b/boards/heltec_v4.json @@ -9,7 +9,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/debian/changelog b/debian/changelog index 5a0f543eb..b9212c1be 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.17.0) unstable; urgency=medium + + * Version 2.7.17 + + -- GitHub Actions Fri, 28 Nov 2025 15:11:34 +0000 + meshtasticd (2.7.16.0) unstable; urgency=medium * Version 2.7.16 diff --git a/debian/rules b/debian/rules index 0b5d1ac57..ebb572153 100755 --- a/debian/rules +++ b/debian/rules @@ -28,5 +28,4 @@ override_dh_auto_build: # Build with platformio $(PIO_ENV) platformio run -e native-tft # Move the binary and default config to the correct name - mv .pio/build/native-tft/program .pio/build/native-tft/meshtasticd cp bin/config-dist.yaml bin/config.yaml diff --git a/extra_scripts/disable_adafruit_usb.py b/extra_scripts/disable_adafruit_usb.py index 596242184..3b901e2db 100644 --- a/extra_scripts/disable_adafruit_usb.py +++ b/extra_scripts/disable_adafruit_usb.py @@ -1,10 +1,9 @@ +#!/usr/bin/env python3 # trunk-ignore-all(flake8/F821) # trunk-ignore-all(ruff/F821) Import("env") -# NOTE: This is not currently used, but can serve as an example on how to write extra_scripts - # print("Current CLI targets", COMMAND_LINE_TARGETS) # print("Current Build targets", BUILD_TARGETS) # print("CPP defs", env.get("CPPDEFINES")) diff --git a/extra_scripts/esp32_extra.py b/extra_scripts/esp32_extra.py new file mode 100755 index 000000000..f7698561a --- /dev/null +++ b/extra_scripts/esp32_extra.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports +# trunk-ignore-all(ruff/E402): Hacky esptool import +# trunk-ignore-all(flake8/E402): Hacky esptool import +import sys +from os.path import join + +Import("env") +platform = env.PioPlatform() + +sys.path.append(join(platform.get_package_dir("tool-esptoolpy"))) +# IntelHex workaround, remove after fixed upstream +# https://github.com/platformio/platform-espressif32/issues/1632 +try: + import intelhex +except ImportError: + env.Execute("$PYTHONEXE -m pip install intelhex") +import esptool + + +def esp32_create_combined_bin(source, target, env): + # this sub is borrowed from ESPEasy build toolchain. It's licensed under GPL V3 + # https://github.com/letscontrolit/ESPEasy/blob/mega/tools/pio/post_esp32.py + print("Generating combined binary for serial flashing") + + app_offset = 0x10000 + + new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin") + sections = env.subst(env.get("FLASH_EXTRA_IMAGES")) + firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") + chip = env.get("BOARD_MCU") + board = env.BoardConfig() + flash_size = board.get("upload.flash_size") + flash_freq = board.get("build.f_flash", "40m") + flash_freq = flash_freq.replace("000000L", "m") + flash_mode = board.get("build.flash_mode", "dio") + memory_type = board.get("build.arduino.memory_type", "qio_qspi") + if flash_mode == "qio" or flash_mode == "qout": + flash_mode = "dio" + if memory_type == "opi_opi" or memory_type == "opi_qspi": + flash_mode = "dout" + cmd = [ + "--chip", + chip, + "merge_bin", + "-o", + new_file_name, + "--flash_mode", + flash_mode, + "--flash_freq", + flash_freq, + "--flash_size", + flash_size, + ] + + print(" Offset | File") + for section in sections: + sect_adr, sect_file = section.split(" ", 1) + print(f" - {sect_adr} | {sect_file}") + cmd += [sect_adr, sect_file] + + print(f" - {hex(app_offset)} | {firmware_name}") + cmd += [hex(app_offset), firmware_name] + + print("Using esptool.py arguments: %s" % " ".join(cmd)) + + esptool.main(cmd) + + +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) + +esp32_kind = env.GetProjectOption("custom_esp32_kind") +if esp32_kind == "esp32": + # Free up some IRAM by removing auxiliary SPI flash chip drivers. + # Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c. + env.Append( + LINKFLAGS=[ + "-Wl,--wrap=esp_flash_chip_gd", + "-Wl,--wrap=esp_flash_chip_issi", + "-Wl,--wrap=esp_flash_chip_winbond", + ] + ) +else: + # For newer ESP32 targets, using newlib nano works better. + env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) diff --git a/extra_scripts/esp32_pre.py b/extra_scripts/esp32_pre.py new file mode 100755 index 000000000..8e21770e9 --- /dev/null +++ b/extra_scripts/esp32_pre.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports +import json +import sys +from os.path import isfile + +Import("env") + + +# From https://github.com/platformio/platform-espressif32/blob/develop/builder/main.py +def _parse_size(value): + if isinstance(value, int): + return value + elif value.isdigit(): + return int(value) + elif value.startswith("0x"): + return int(value, 16) + elif value[-1].upper() in ("K", "M"): + base = 1024 if value[-1].upper() == "K" else 1024 * 1024 + return int(value[:-1]) * base + return value + + +def _parse_partitions(env): + partitions_csv = env.subst("$PARTITIONS_TABLE_CSV") + if not isfile(partitions_csv): + sys.stderr.write( + "Could not find the file %s with partitions " "table.\n" % partitions_csv + ) + env.Exit(1) + return + + result = [] + # The first offset is 0x9000 because partition table is flashed to 0x8000 and + # occupies an entire flash sector, which size is 0x1000 + next_offset = 0x9000 + with open(partitions_csv) as fp: + for line in fp.readlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + tokens = [t.strip() for t in line.split(",")] + if len(tokens) < 5: + continue + + bound = 0x10000 if tokens[1] in ("0", "app") else 4 + calculated_offset = (next_offset + bound - 1) & ~(bound - 1) + partition = { + "name": tokens[0], + "type": tokens[1], + "subtype": tokens[2], + "offset": tokens[3] or calculated_offset, + "size": tokens[4], + "flags": tokens[5] if len(tokens) > 5 else None, + } + result.append(partition) + next_offset = _parse_size(partition["offset"]) + _parse_size( + partition["size"] + ) + + return result + + +def mtjson_esp32_part(target, source, env): + part = _parse_partitions(env) + pj = json.dumps(part) + # print(f"JSON_PARTITIONS: {pj}") + # Dump json string to 'custom_mtjson_part' variable to use later when writing the manifest + env.Replace(custom_mtjson_part=pj) + + +env.AddPreAction("mtjson", mtjson_esp32_part) diff --git a/extra_scripts/nrf52_extra.py b/extra_scripts/nrf52_extra.py new file mode 100755 index 000000000..8e95e42bf --- /dev/null +++ b/extra_scripts/nrf52_extra.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports + +import sys +from os.path import basename + +Import("env") + + +# Custom HEX from ELF +# Convert hex to uf2 for nrf52 +def nrf52_hex_to_uf2(source, target, env): + hex_path = target[0].get_abspath() + # When using merged hex, drop 'merged' from uf2 filename + uf2_path = hex_path.replace(".merged.", ".") + uf2_path = uf2_path.replace(".hex", ".uf2") + env.Execute( + env.VerboseAction( + f'"{sys.executable}" ./bin/uf2conv.py "{hex_path}" -c -f 0xADA52840 -o "{uf2_path}"', + f"Generating UF2 file from {basename(hex_path)}", + ) + ) + + +def nrf52_mergehex(source, target, env): + hex_path = target[0].get_abspath() + merged_hex_path = hex_path.replace(".hex", ".merged.hex") + merge_with = None + if "wio-sdk-wm1110" == str(env.get("PIOENV")): + merge_with = env.subst("$PROJECT_DIR/bin/s140_nrf52_7.3.0_softdevice.hex") + else: + print("merge_with not defined for this target") + + if merge_with is not None: + env.Execute( + env.VerboseAction( + f'"$PROJECT_DIR/bin/mergehex" -m "{hex_path}" "{merge_with}" -o "{merged_hex_path}"', + "Merging HEX with SoftDevice", + ) + ) + print(f'Merged file saved at "{basename(merged_hex_path)}"') + nrf52_hex_to_uf2([hex_path, merge_with], [env.File(merged_hex_path)], env) + + +# if WM1110 target, merge hex with softdevice 7.3.0 +if "wio-sdk-wm1110" == env.get("PIOENV"): + env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", nrf52_mergehex) +else: + env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", nrf52_hex_to_uf2) diff --git a/extra_scripts/extra_stm32.py b/extra_scripts/stm32_extra.py similarity index 95% rename from extra_scripts/extra_stm32.py rename to extra_scripts/stm32_extra.py index f3bd8c514..afceb7d81 100755 --- a/extra_scripts/extra_stm32.py +++ b/extra_scripts/stm32_extra.py @@ -1,7 +1,9 @@ +#!/usr/bin/env python3 # trunk-ignore-all(ruff/F821) # trunk-ignore-all(flake8/F821): For SConstruct imports Import("env") + # Custom HEX from ELF env.AddPostAction( "$BUILD_DIR/${PROGNAME}.elf", diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg index e2da172c3..3456001f0 100644 --- a/meshtasticd.spec.rpkg +++ b/meshtasticd.spec.rpkg @@ -76,7 +76,7 @@ platformio run -e native-tft %install # Install meshtasticd binary mkdir -p %{buildroot}%{_bindir} -install -m 0755 .pio/build/native-tft/program %{buildroot}%{_bindir}/meshtasticd +install -m 0755 .pio/build/native-tft/meshtasticd %{buildroot}%{_bindir}/meshtasticd # Install portduino VFS dir install -p -d -m 0770 %{buildroot}%{_localstatedir}/lib/meshtasticd diff --git a/platformio.ini b/platformio.ini index 631bb6fdd..ed784ae07 100644 --- a/platformio.ini +++ b/platformio.ini @@ -5,7 +5,7 @@ default_envs = tbeam extra_configs = - arch/*/*.ini + variants/*/*.ini variants/*/*/platformio.ini variants/*/diy/*/platformio.ini src/graphics/niche/InkHUD/PlatformioConfig.ini @@ -14,7 +14,9 @@ description = Meshtastic [env] test_build_src = true -extra_scripts = bin/platformio-custom.py +extra_scripts = + pre:bin/platformio-pre.py + bin/platformio-custom.py ; note: we add src to our include search path so that lmic_project_config can override ; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile ; of code is a heap corruption bug! @@ -182,8 +184,8 @@ lib_deps = dfrobot/DFRobot_BMM150@1.0.0 # renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561 adafruit/Adafruit TSL2561@1.1.2 - # renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/BH1750_WE@^1.1.10 - wollewald/BH1750_WE@^1.1.10 + # renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE + wollewald/BH1750_WE@1.1.10 ; (not included in native / portduino) [environmental_extra] diff --git a/protobufs b/protobufs index 52fa252f1..4095e5989 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 52fa252f1e01be87ad2f7ab17ceef7882b2a4a93 +Subproject commit 4095e598902b4cd893dbcb62842514704d0f64e0 diff --git a/src/AudioThread.h b/src/AudioThread.h index df4892b6e..23552c421 100644 --- a/src/AudioThread.h +++ b/src/AudioThread.h @@ -50,8 +50,11 @@ class AudioThread : public concurrency::OSThread delete i2sRtttl; i2sRtttl = nullptr; } - delete rtttlFile; - rtttlFile = nullptr; + + if (rtttlFile != nullptr) { + delete rtttlFile; + rtttlFile = nullptr; + } setCPUFast(false); #ifdef T_LORA_PAGER @@ -99,9 +102,9 @@ class AudioThread : public concurrency::OSThread }; AudioGeneratorRTTTL *i2sRtttl = nullptr; - AudioOutputI2S *audioOut; + AudioOutputI2S *audioOut = nullptr; - AudioFileSourcePROGMEM *rtttlFile; + AudioFileSourcePROGMEM *rtttlFile = nullptr; }; #endif diff --git a/src/Power.cpp b/src/Power.cpp index a2c559d91..7bb8896ce 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1453,7 +1453,7 @@ class LipoCharger : public HasBatteryLevel /** * return true if there is an external power source detected */ - virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; } + virtual bool isVbusIn() override { return PPM->isVbusIn(); } /** * return true if the battery is currently charging diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 322b877ff..9f8097b84 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -57,21 +57,21 @@ static bool isPowered() static void sdsEnter() { - LOG_DEBUG("State: SDS"); + LOG_POWERFSM("State: SDS"); // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, false); } static void lowBattSDSEnter() { - LOG_DEBUG("State: Lower batt SDS"); + LOG_POWERFSM("State: Lower batt SDS"); doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, true); } extern Power *power; static void shutdownEnter() { - LOG_DEBUG("State: SHUTDOWN"); + LOG_POWERFSM("State: SHUTDOWN"); shutdownAtMsec = millis(); } @@ -81,7 +81,7 @@ static uint32_t secsSlept; static void lsEnter() { - LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs); + LOG_POWERFSM("lsEnter begin, ls_secs=%u", config.power.ls_secs); if (screen) screen->setOn(false); secsSlept = 0; // How long have we been sleeping this time @@ -155,12 +155,12 @@ static void lsIdle() static void lsExit() { - LOG_INFO("Exit state: LS"); + LOG_POWERFSM("State: lsExit"); } static void nbEnter() { - LOG_DEBUG("State: NB"); + LOG_POWERFSM("State: nbEnter"); if (screen) screen->setOn(false); #ifdef ARCH_ESP32 @@ -173,6 +173,7 @@ static void nbEnter() static void darkEnter() { + LOG_POWERFSM("State: darkEnter"); setBluetoothEnable(true); if (screen) screen->setOn(false); @@ -180,7 +181,7 @@ static void darkEnter() static void serialEnter() { - LOG_DEBUG("State: SERIAL"); + LOG_POWERFSM("State: serialEnter"); setBluetoothEnable(false); if (screen) { screen->setOn(true); @@ -189,13 +190,14 @@ static void serialEnter() static void serialExit() { + LOG_POWERFSM("State: serialExit"); // Turn bluetooth back on when we leave serial stream API setBluetoothEnable(true); } static void powerEnter() { - // LOG_DEBUG("State: POWER"); + LOG_POWERFSM("State: powerEnter"); if (!isPowered()) { // If we got here, we are in the wrong state - we should be in powered, let that state handle things LOG_INFO("Loss of power in Powered"); @@ -210,6 +212,7 @@ static void powerEnter() static void powerIdle() { + // LOG_POWERFSM("State: powerIdle"); // very chatty if (!isPowered()) { // If we got here, we are in the wrong state LOG_INFO("Loss of power in Powered"); @@ -219,14 +222,13 @@ static void powerIdle() static void powerExit() { - if (screen) - screen->setOn(true); + LOG_POWERFSM("State: powerExit"); setBluetoothEnable(true); } static void onEnter() { - LOG_DEBUG("State: ON"); + LOG_POWERFSM("State: onEnter"); if (screen) screen->setOn(true); setBluetoothEnable(true); @@ -234,6 +236,7 @@ static void onEnter() static void onIdle() { + LOG_POWERFSM("State: onIdle"); if (isPowered()) { // If we got here, we are in the wrong state - we should be in powered, let that state handle things powerFSM.trigger(EVENT_POWER_CONNECTED); @@ -242,7 +245,7 @@ static void onIdle() static void bootEnter() { - LOG_DEBUG("State: BOOT"); + LOG_POWERFSM("State: bootEnter"); } State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN"); @@ -319,11 +322,6 @@ void PowerFSM_setup() // if any packet destined for phone arrives, turn on bluetooth at least powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); - // Removed 2.7: we don't show the nodes individually for every node on the screen anymore - // powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - // powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - // powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - // Show the received text message powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); @@ -372,7 +370,7 @@ void PowerFSM_setup() // Don't add power saving transitions if we are a power saving tracker or sensor or have Wifi enabled. Sleep will be initiated // through the modules -#if HAS_WIFI || !defined(MESHTASTIC_EXCLUDE_WIFI) +#if HAS_WIFI && !defined(MESHTASTIC_EXCLUDE_WIFI) bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR; diff --git a/src/PowerFSM.h b/src/PowerFSM.h index 6330a5fc6..182ac082a 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -2,6 +2,12 @@ #include "configuration.h" +#ifdef PowerFSMDebug +#define LOG_POWERFSM(...) LOG_DEBUG(__VA_ARGS__) +#else +#define LOG_POWERFSM(...) +#endif + // See sw-design.md for documentation #define EVENT_PRESS 1 diff --git a/src/buzz/buzz.cpp b/src/buzz/buzz.cpp index b0d162a44..aa8346585 100644 --- a/src/buzz/buzz.cpp +++ b/src/buzz/buzz.cpp @@ -16,6 +16,7 @@ struct ToneDuration { }; // Some common frequencies. +#define NOTE_SILENT 1 #define NOTE_C3 131 #define NOTE_CS3 139 #define NOTE_D3 147 @@ -29,11 +30,16 @@ struct ToneDuration { #define NOTE_AS3 233 #define NOTE_B3 247 #define NOTE_CS4 277 +#define NOTE_B4 494 +#define NOTE_F5 698 +#define NOTE_G6 1568 +#define NOTE_E7 2637 +const int DURATION_1_16 = 62; // 1/16 note const int DURATION_1_8 = 125; // 1/8 note const int DURATION_1_4 = 250; // 1/4 note const int DURATION_1_2 = 500; // 1/2 note -const int DURATION_3_4 = 750; // 1/4 note +const int DURATION_3_4 = 750; // 3/4 note const int DURATION_1_1 = 1000; // 1/1 note void playTones(const ToneDuration *tone_durations, int size) @@ -71,13 +77,24 @@ void playLongBeep() void playGPSEnableBeep() { +#if defined(R1_NEO) || defined(MUZI_BASE) + ToneDuration melody[] = { + {NOTE_F5, DURATION_1_2}, {NOTE_G6, DURATION_1_8}, {NOTE_E7, DURATION_1_4}, {NOTE_SILENT, DURATION_1_2}}; +#else ToneDuration melody[] = {{NOTE_C3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_CS4, DURATION_1_4}}; +#endif playTones(melody, sizeof(melody) / sizeof(ToneDuration)); } void playGPSDisableBeep() { +#if defined(R1_NEO) || defined(MUZI_BASE) + ToneDuration melody[] = {{NOTE_B4, DURATION_1_16}, {NOTE_B4, DURATION_1_16}, {NOTE_SILENT, DURATION_1_8}, + {NOTE_F3, DURATION_1_16}, {NOTE_F3, DURATION_1_16}, {NOTE_SILENT, DURATION_1_8}, + {NOTE_C3, DURATION_1_1}, {NOTE_SILENT, DURATION_1_1}}; +#else ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_C3, DURATION_1_4}}; +#endif playTones(melody, sizeof(melody) / sizeof(ToneDuration)); } diff --git a/src/configuration.h b/src/configuration.h index df04463aa..bafc9ad3e 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -36,6 +36,29 @@ along with this program. If not, see . /* Offer chance for variant-specific defines */ #include "variant.h" +// ----------------------------------------------------------------------------- +// Display feature overrides +// ----------------------------------------------------------------------------- + +// Allow build environments to opt-in explicitly to the E-Ink UI stack while +// keeping headless targets slim by default. Existing variants that already +// define USE_EINK continue to work without additional flags. +#ifndef MESHTASTIC_USE_EINK_UI +#ifdef USE_EINK +#define MESHTASTIC_USE_EINK_UI 1 +#else +#define MESHTASTIC_USE_EINK_UI 0 +#endif +#endif + +#if MESHTASTIC_USE_EINK_UI +#ifndef USE_EINK +#define USE_EINK +#endif +#else +#undef USE_EINK +#endif + // ----------------------------------------------------------------------------- // Version // ----------------------------------------------------------------------------- @@ -374,6 +397,9 @@ along with this program. If not, see . #ifndef HAS_BLUETOOTH #define HAS_BLUETOOTH 0 #endif +#ifndef USE_TFTDISPLAY +#define USE_TFTDISPLAY 0 +#endif #ifndef HW_VENDOR #error HW_VENDOR must be defined diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 0404ae5f8..a61a71dde 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -38,14 +38,16 @@ template std::size_t array_count(const T (&)[N]) return N; } -#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) -#if defined(GPS_SERIAL_PORT) -HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT; -#else -HardwareSerial *GPS::_serial_gps = &Serial1; +#ifndef GPS_SERIAL_PORT +#define GPS_SERIAL_PORT Serial1 #endif + +#if defined(ARCH_NRF52) +Uart *GPS::_serial_gps = &GPS_SERIAL_PORT; +#elif defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) +HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT; #elif defined(ARCH_RP2040) -SerialUART *GPS::_serial_gps = &Serial1; +SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT; #else HardwareSerial *GPS::_serial_gps = nullptr; #endif @@ -1525,10 +1527,7 @@ GPS *GPS::createGps() int8_t _rx_gpio = config.position.rx_gpio; int8_t _tx_gpio = config.position.tx_gpio; int8_t _en_gpio = config.position.gps_en_gpio; -#if HAS_GPS && !defined(ARCH_ESP32) - _rx_gpio = 1; // We only specify GPS serial ports on ESP32. Otherwise, these are just flags. - _tx_gpio = 1; -#endif + #if defined(GPS_RX_PIN) if (!_rx_gpio) _rx_gpio = GPS_RX_PIN; @@ -1602,16 +1601,28 @@ GPS *GPS::createGps() _serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256 #endif -// ESP32 has a special set of parameters vs other arduino ports -#if defined(ARCH_ESP32) LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio); LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio); + +// ESP32 has a special set of parameters vs other arduino ports +#if defined(ARCH_ESP32) _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio); #elif defined(ARCH_RP2040) + _serial_gps->setPinout(new_gps->tx_gpio, new_gps->rx_gpio); _serial_gps->setFIFOSize(256); _serial_gps->begin(GPS_BAUDRATE); -#else +#elif defined(ARCH_NRF52) + _serial_gps->setPins(new_gps->rx_gpio, new_gps->tx_gpio); _serial_gps->begin(GPS_BAUDRATE); +#elif defined(ARCH_STM32WL) + _serial_gps->setTx(new_gps->tx_gpio); + _serial_gps->setRx(new_gps->rx_gpio); + _serial_gps->begin(GPS_BAUDRATE); +#elif defined(ARCH_PORTDUINO) + // Portduino can't set the GPS pins directly. + _serial_gps->begin(GPS_BAUDRATE); +#else +#error Unsupported architecture! #endif } return new_gps; diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 8ba1ce0a6..59cee7113 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -194,6 +194,8 @@ class GPS : private concurrency::OSThread /** If !NULL we will use this serial port to construct our GPS */ #if defined(ARCH_RP2040) static SerialUART *_serial_gps; +#elif defined(ARCH_NRF52) + static Uart *_serial_gps; #else static HardwareSerial *_serial_gps; #endif diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index e8c2e4b88..8bac6936a 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -69,7 +69,11 @@ using graphics::Emote; using graphics::emotes; using graphics::numEmotes; +#if USE_TFTDISPLAY extern uint16_t TFT_MESH; +#else +uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94); +#endif #if HAS_WIFI && !defined(ARCH_PORTDUINO) #include "mesh/wifi/WiFiAPClient.h" @@ -226,24 +230,9 @@ void Screen::showTextInput(const char *header, const char *initialText, uint32_t { LOG_INFO("showTextInput called with header='%s', durationMs=%d", header ? header : "NULL", durationMs); - if (NotificationRenderer::virtualKeyboard) { - delete NotificationRenderer::virtualKeyboard; - NotificationRenderer::virtualKeyboard = nullptr; - } - - NotificationRenderer::textInputCallback = nullptr; - - NotificationRenderer::virtualKeyboard = new VirtualKeyboard(); - if (header) { - NotificationRenderer::virtualKeyboard->setHeader(header); - } - if (initialText) { - NotificationRenderer::virtualKeyboard->setInputText(initialText); - } - - // Set up callback with safer cleanup mechanism + // Start OnScreenKeyboardModule session (non-touch variant) + OnScreenKeyboardModule::instance().start(header, initialText, durationMs, textCallback); NotificationRenderer::textInputCallback = textCallback; - NotificationRenderer::virtualKeyboard->setCallback([textCallback](const std::string &text) { textCallback(text); }); // Store the message and set the expiration timestamp (use same pattern as other notifications) strncpy(NotificationRenderer::alertBannerMessage, header ? header : "Text Input", 255); @@ -363,11 +352,6 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #else dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); #endif -#if defined(USE_ST7789) - static_cast(dispdev)->setRGB(TFT_MESH); -#elif defined(USE_ST7796) - static_cast(dispdev)->setRGB(TFT_MESH); -#endif #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); @@ -380,7 +364,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O LOG_INFO("SSD1306 init success"); } #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR) dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) @@ -410,6 +394,12 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O isAUTOOled = true; #endif +#if defined(USE_ST7789) + static_cast(dispdev)->setRGB(TFT_MESH); +#elif defined(USE_ST7796) + static_cast(dispdev)->setRGB(TFT_MESH); +#endif + ui = new OLEDDisplayUi(dispdev); cmdQueue.setReader(this); } @@ -655,7 +645,7 @@ void Screen::setup() #else if (!config.display.flip_screen) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR) static_cast(dispdev)->flipScreenVertically(); #elif defined(USE_ST7789) static_cast(dispdev)->flipScreenVertically(); @@ -1508,14 +1498,14 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) // Incoming message devicestate.has_rx_text_message = true; // Needed to include the message frame hasUnreadMessage = true; // Enables mail icon in the header - setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view + setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view (no-op during text_input) // Only wake/force display if the configuration allows it if (shouldWakeOnReceivedMessage()) { setOn(true); // Wake up the screen first forceDisplay(); // Forces screen redraw } - // === Prepare banner content === + // === Prepare banner/popup content === const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); const meshtastic_Channel channel = channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex()); @@ -1539,38 +1529,84 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) // Unlike generic messages, alerts (when enabled via the ext notif module) ignore any // 'mute' preferences set to any specific node or channel. - if (isAlert) { - if (longName && longName[0]) { - snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); - } else { - strcpy(banner, "Alert Received"); + // If on-screen keyboard is active, show a transient popup over keyboard instead of interrupting it + if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) { + // Wake and force redraw so popup is visible immediately + if (shouldWakeOnReceivedMessage()) { + setOn(true); + forceDisplay(); } - screen->showSimpleBanner(banner, 3000); - } else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) { - if (longName && longName[0]) { -#if defined(M5STACK_UNITC6L) - strcpy(banner, "New Message"); -#else - snprintf(banner, sizeof(banner), "New Message from\n%s", longName); -#endif + // Build popup: title = message source name, content = message text (sanitized) + // Title + char titleBuf[64] = {0}; + if (longName && longName[0]) { + // Sanitize sender name + std::string t = sanitizeString(longName); + strncpy(titleBuf, t.c_str(), sizeof(titleBuf) - 1); } else { - strcpy(banner, "New Message"); + strncpy(titleBuf, "Message", sizeof(titleBuf) - 1); } + + // Content: payload bytes may not be null-terminated, remove ASCII_BELL and sanitize + char content[256] = {0}; + { + std::string raw; + raw.reserve(packet->decoded.payload.size); + for (size_t i = 0; i < packet->decoded.payload.size; ++i) { + char c = msgRaw[i]; + if (c == ASCII_BELL) + continue; // strip bell + raw.push_back(c); + } + std::string sanitized = sanitizeString(raw); + strncpy(content, sanitized.c_str(), sizeof(content) - 1); + } + + NotificationRenderer::showKeyboardMessagePopupWithTitle(titleBuf, content, 3000); + +// Maintain existing buzzer behavior on M5 if applicable #if defined(M5STACK_UNITC6L) - screen->setOn(true); - screen->showSimpleBanner(banner, 1500); if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || (isAlert && moduleConfig.external_notification.alert_bell_buzzer) || (!isBroadcast(packet->to) && isToUs(packet))) { - // Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either - // - packet contains an alert and alert bell buzzer is enabled - // - packet is a non-broadcast that is addressed to this node playLongBeep(); } -#else - screen->showSimpleBanner(banner, 3000); #endif + } else { + // No keyboard active: use regular banner flow, respecting mute settings + if (isAlert) { + if (longName && longName[0]) { + snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); + } else { + strcpy(banner, "Alert Received"); + } + screen->showSimpleBanner(banner, 3000); + } else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) { + if (longName && longName[0]) { +#if defined(M5STACK_UNITC6L) + strcpy(banner, "New Message"); +#else + snprintf(banner, sizeof(banner), "New Message from\n%s", longName); +#endif + } else { + strcpy(banner, "New Message"); + } +#if defined(M5STACK_UNITC6L) + screen->setOn(true); + screen->showSimpleBanner(banner, 1500); + if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || + (isAlert && moduleConfig.external_notification.alert_bell_buzzer) || + (!isBroadcast(packet->to) && isToUs(packet))) { + // Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either + // - packet contains an alert and alert bell buzzer is enabled + // - packet is a non-broadcast that is addressed to this node + playLongBeep(); + } +#else + screen->showSimpleBanner(banner, 3000); +#endif + } } } } @@ -1605,6 +1641,7 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event) int Screen::handleInputEvent(const InputEvent *event) { + LOG_INPUT("Screen Input event %u! kb %u", event->inputEvent, event->kbchar); if (!screenOn) return 0; @@ -1652,6 +1689,12 @@ int Screen::handleInputEvent(const InputEvent *event) showPrevFrame(); } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) { showNextFrame(); + } else if (event->inputEvent == INPUT_BROKER_UP_LONG) { + // Long press up button for fast frame switching + showPrevFrame(); + } else if (event->inputEvent == INPUT_BROKER_DOWN_LONG) { + // Long press down button for fast frame switching + showNextFrame(); } else if (event->inputEvent == INPUT_BROKER_SELECT) { if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) { menuHandler::homeBaseMenu(); diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index bcb4c4987..d54fc9958 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -73,7 +73,8 @@ #endif #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 1645789a7..892285dcb 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -17,6 +17,12 @@ namespace graphics void determineResolution(int16_t screenheight, int16_t screenwidth) { + +#ifdef FORCE_LOW_RES + isHighResolution = false; + return; +#endif + if (screenwidth > 128) { isHighResolution = true; } @@ -24,11 +30,6 @@ void determineResolution(int16_t screenheight, int16_t screenwidth) if (screenwidth > 128 && screenheight <= 64) { isHighResolution = false; } - - // Special case for Heltec Wireless Tracker v1.1 - if (screenwidth == 160 && screenheight == 80) { - isHighResolution = false; - } } // === Shared External State === diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 87593b0d4..12fac4f34 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -1,5 +1,6 @@ #include "configuration.h" #include "main.h" +#if USE_TFTDISPLAY #if ARCH_PORTDUINO #include "platform/portduino/PortduinoGlue.h" @@ -123,6 +124,11 @@ static void rak14014_tpIntHandle(void) _rak14014_touch_int = true; } +#elif defined(HACKADAY_COMMUNICATOR) +#include +Arduino_DataBus *bus = nullptr; +Arduino_GFX *tft = nullptr; + #elif defined(ST72xx_DE) #include #include @@ -1133,9 +1139,6 @@ static LGFX *tft = nullptr; #endif -#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \ - defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \ - (ARCH_PORTDUINO && HAS_SCREEN != 0) #include "SPILock.h" #include "TFTDisplay.h" #include @@ -1271,12 +1274,15 @@ void TFTDisplay::display(bool fromBlank) x_LastPixelUpdate = x; } } - +#if defined(HACKADAY_COMMUNICATOR) + tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate], + (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1); +#else // Step 4: Send the changed pixels on this line to the screen as a single block transfer. // This function accepts pixel data MSB first so it can dump the memory straight out the SPI port. tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1, &linePixelBuffer[x_FirstPixelUpdate]); - +#endif somethingChanged = true; } y++; @@ -1340,6 +1346,8 @@ void TFTDisplay::sendCommand(uint8_t com) display(true); if (portduino_config.displayBacklight.pin > 0) digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON); +#elif defined(HACKADAY_COMMUNICATOR) + tft->displayOn(); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->wakeup(); tft->powerSaveOff(); @@ -1352,7 +1360,8 @@ void TFTDisplay::sendCommand(uint8_t com) unphone.backlight(true); // using unPhone library #endif #ifdef RAK14014 -#elif !defined(M5STACK) && !defined(ST7789_CS) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function +#elif !defined(M5STACK) && !defined(ST7789_CS) && \ + !defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function tft->setBrightness(172); #endif break; @@ -1364,6 +1373,8 @@ void TFTDisplay::sendCommand(uint8_t com) tft->clear(); if (portduino_config.displayBacklight.pin > 0) digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON); +#elif defined(HACKADAY_COMMUNICATOR) + tft->displayOff(); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->sleep(); tft->powerSaveOn(); @@ -1376,7 +1387,7 @@ void TFTDisplay::sendCommand(uint8_t com) unphone.backlight(false); // using unPhone library #endif #ifdef RAK14014 -#elif !defined(M5STACK) +#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) tft->setBrightness(0); #endif break; @@ -1392,7 +1403,7 @@ void TFTDisplay::setDisplayBrightness(uint8_t _brightness) { #ifdef RAK14014 // todo -#else +#elif !defined(HACKADAY_COMMUNICATOR) tft->setBrightness(_brightness); LOG_DEBUG("Brightness is set to value: %i ", _brightness); #endif @@ -1410,7 +1421,7 @@ bool TFTDisplay::hasTouch(void) { #ifdef RAK14014 return true; -#elif !defined(M5STACK) +#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) return tft->touch() != nullptr; #else return false; @@ -1429,7 +1440,7 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y) } else { return false; } -#elif !defined(M5STACK) +#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) return tft->getTouch(x, y); #else return false; @@ -1448,6 +1459,12 @@ bool TFTDisplay::connect() LOG_INFO("Do TFT init"); #ifdef RAK14014 tft = new TFT_eSPI; +#elif defined(HACKADAY_COMMUNICATOR) + bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, 38 /* SCK */, 21 /* MOSI */, GFX_NOT_DEFINED /* MISO */, HSPI /* spi_num */); + tft = new Arduino_NV3007(bus, 40, 0 /* rotation */, false /* IPS */, 142 /* width */, 428 /* height */, 12 /* col offset 1 */, + 0 /* row offset 1 */, 14 /* col offset 2 */, 0 /* row offset 2 */, nv3007_279_init_operations, + sizeof(nv3007_279_init_operations)); + #else tft = new LGFX; #endif @@ -1458,8 +1475,15 @@ bool TFTDisplay::connect() #ifdef UNPHONE unphone.backlight(true); // using unPhone library #endif - +#ifdef HACKADAY_COMMUNICATOR + bool beginStatus = tft->begin(); + if (beginStatus) + LOG_DEBUG("TFT Success!"); + else + LOG_ERROR("TFT Fail!"); +#else tft->init(); +#endif #if defined(M5STACK) tft->setRotation(0); @@ -1492,4 +1516,4 @@ bool TFTDisplay::connect() return true; } -#endif +#endif // USE_TFTDISPLAY diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 6bccb1653..1b3a148d6 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -97,8 +97,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ - defined(USE_ST7796) || \ - ARCH_PORTDUINO) && \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -110,7 +109,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 #endif } else { #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -126,8 +126,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 // TODO: Raspberry Pi supports more than just the one screen size #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ - defined(USE_ST7796) || \ - ARCH_PORTDUINO) && \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index bd647c3d8..f782dabb6 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -13,6 +13,7 @@ #include "input/RotaryEncoderInterruptImpl1.h" #include "input/UpDownInterruptImpl1.h" #include "main.h" +#include "mesh/Default.h" #include "mesh/MeshTypes.h" #include "modules/AdminModule.h" #include "modules/CannedMessageModule.h" @@ -576,7 +577,7 @@ void menuHandler::textMessageBaseMenu() void menuHandler::systemBaseMenu() { - enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd }; + enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, WiFiToggle, PowerMenu, Test, enumEnd }; static const char *optionsArray[enumEnd] = {"Back"}; static int optionsEnumArray[enumEnd] = {Back}; int options = 1; @@ -592,6 +593,10 @@ void menuHandler::systemBaseMenu() optionsArray[options] = "Bluetooth Toggle"; #endif optionsEnumArray[options++] = Bluetooth; +#if HAS_WIFI && !defined(ARCH_PORTDUINO) + optionsArray[options] = "WiFi Toggle"; + optionsEnumArray[options++] = WiFiToggle; +#endif #if defined(M5STACK_UNITC6L) optionsArray[options] = "Power"; #else @@ -629,6 +634,11 @@ void menuHandler::systemBaseMenu() } else if (selected == Bluetooth) { menuQueue = bluetooth_toggle_menu; screen->runNow(); +#if HAS_WIFI && !defined(ARCH_PORTDUINO) + } else if (selected == WiFiToggle) { + menuQueue = wifi_toggle_menu; + screen->runNow(); +#endif } else if (selected == Back && !test_enabled) { test_count++; if (test_count > 4) { @@ -1038,7 +1048,8 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display) 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) || defined(T_LORA_PAGER) || HAS_TFT +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \ + HAS_TFT || defined(HACKADAY_COMMUNICATOR) uint8_t TFT_MESH_r = 0; uint8_t TFT_MESH_g = 0; uint8_t TFT_MESH_b = 0; @@ -1278,19 +1289,28 @@ void menuHandler::wifiBaseMenu() void menuHandler::wifiToggleMenu() { - enum optionsNumbers { Back, Wifi_toggle }; + enum optionsNumbers { Back, Wifi_disable, Wifi_enable }; - static const char *optionsArray[] = {"Back", "Disable"}; + static const char *optionsArray[] = {"Back", "WiFi Disabled", "WiFi Enabled"}; BannerOverlayOptions bannerOptions; - bannerOptions.message = "Disable Wifi and\nEnable Bluetooth?"; + bannerOptions.message = "WiFi Actions"; bannerOptions.optionsArrayPtr = optionsArray; - bannerOptions.optionsCount = 2; + bannerOptions.optionsCount = 3; + if (config.network.wifi_enabled == true) + bannerOptions.InitialSelected = 2; + else + bannerOptions.InitialSelected = 1; bannerOptions.bannerCallback = [](int selected) -> void { - if (selected == Wifi_toggle) { + if (selected == Wifi_disable) { config.network.wifi_enabled = false; config.bluetooth.enabled = true; service->reloadConfig(SEGMENT_CONFIG); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } else if (selected == Wifi_enable) { + config.network.wifi_enabled = true; + config.bluetooth.enabled = false; + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); } }; screen->showOverlayBanner(bannerOptions); @@ -1338,7 +1358,7 @@ void menuHandler::screenOptionsMenu() static int optionsEnumArray[5] = {Back}; int options = 1; -#if defined(T_DECK) || defined(T_LORA_PAGER) +#if defined(T_DECK) || defined(T_LORA_PAGER) || defined(HACKADAY_COMMUNICATOR) optionsArray[options] = "Show Long/Short Name"; optionsEnumArray[options++] = NodeNameLength; #endif @@ -1350,7 +1370,8 @@ void menuHandler::screenOptionsMenu() } // Only show screen color for TFT displays -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \ + HAS_TFT || defined(HACKADAY_COMMUNICATOR) optionsArray[options] = "Screen Color"; optionsEnumArray[options++] = ScreenColor; #endif diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index 26bfe8447..e95cc1610 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -85,9 +85,13 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat void NotificationRenderer::resetBanner() { + notificationTypeEnum previousType = current_notification_type; + alertBannerMessage[0] = '\0'; current_notification_type = notificationTypeEnum::none; + OnScreenKeyboardModule::instance().clearPopup(); + inEvent.inputEvent = INPUT_BROKER_NONE; inEvent.kbchar = 0; curSelected = 0; @@ -100,6 +104,13 @@ void NotificationRenderer::resetBanner() currentNumber = 0; nodeDB->pause_sort(false); + + // If we're exiting from text_input (virtual keyboard), stop module and trigger frame update + // to ensure any messages received during keyboard use are now displayed + if (previousType == notificationTypeEnum::text_input && screen) { + OnScreenKeyboardModule::instance().stop(false); + screen->setFrames(graphics::Screen::FOCUS_PRESERVE); + } } void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state) @@ -163,13 +174,15 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS // 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 (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS || + inEvent.inputEvent == INPUT_BROKER_UP_LONG) { 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) { + } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS || + inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) { if (this_digit == 0) { currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1)); } else { @@ -251,10 +264,10 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta // Handle input if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT || - inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { + inEvent.inputEvent == INPUT_BROKER_ALT_PRESS || inEvent.inputEvent == INPUT_BROKER_UP_LONG) { curSelected--; } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT || - inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { + inEvent.inputEvent == INPUT_BROKER_USER_PRESS || inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) { curSelected++; } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { alertBannerCallback(selectedNodenum); @@ -368,10 +381,10 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp // Handle input if (alertBannerOptions > 0) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT || - inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { + inEvent.inputEvent == INPUT_BROKER_ALT_PRESS || inEvent.inputEvent == INPUT_BROKER_UP_LONG) { curSelected--; } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT || - inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { + inEvent.inputEvent == INPUT_BROKER_USER_PRESS || inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) { curSelected++; } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { if (optionsEnumPtr != nullptr) { @@ -769,40 +782,8 @@ void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiStat } if (inEvent.inputEvent != INPUT_BROKER_NONE) { - if (inEvent.inputEvent == INPUT_BROKER_UP) { - // high frequency for move cursor left/right than up/down with encoders - extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1; - extern ::UpDownInterruptImpl1 *upDownInterruptImpl1; - if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) { - virtualKeyboard->moveCursorLeft(); - } else { - virtualKeyboard->moveCursorUp(); - } - } else if (inEvent.inputEvent == INPUT_BROKER_DOWN) { - extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1; - extern ::UpDownInterruptImpl1 *upDownInterruptImpl1; - if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) { - virtualKeyboard->moveCursorRight(); - } else { - virtualKeyboard->moveCursorDown(); - } - } else if (inEvent.inputEvent == INPUT_BROKER_LEFT) { - virtualKeyboard->moveCursorLeft(); - } else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) { - virtualKeyboard->moveCursorRight(); - } else if (inEvent.inputEvent == INPUT_BROKER_UP_LONG) { - virtualKeyboard->moveCursorUp(); - } else if (inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) { - virtualKeyboard->moveCursorDown(); - } else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { - virtualKeyboard->moveCursorLeft(); - } else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { - virtualKeyboard->moveCursorRight(); - } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { - virtualKeyboard->handlePress(); - } else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) { - virtualKeyboard->handleLongPress(); - } else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) { + bool handled = OnScreenKeyboardModule::processVirtualKeyboardInput(inEvent, virtualKeyboard); + if (!handled && inEvent.inputEvent == INPUT_BROKER_CANCEL) { auto callback = textInputCallback; delete virtualKeyboard; virtualKeyboard = nullptr; @@ -821,12 +802,28 @@ void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiStat inEvent.inputEvent = INPUT_BROKER_NONE; } + // Re-check pointer before drawing to avoid use-after-free and crashes + if (!virtualKeyboard) { + // Ensure we exit text_input state and restore frames + if (current_notification_type == notificationTypeEnum::text_input) { + resetBanner(); + } + if (screen) { + screen->setFrames(graphics::Screen::FOCUS_PRESERVE); + } + // If screen is null, do nothing (safe fallback) + return; + } + // Clear the screen to avoid overlapping with underlying frames or overlays display->setColor(BLACK); display->fillRect(0, 0, display->getWidth(), display->getHeight()); display->setColor(WHITE); // Draw the virtual keyboard virtualKeyboard->draw(display, 0, 0); + + // Draw transient popup overlay (if any) managed by OnScreenKeyboardModule + OnScreenKeyboardModule::instance().drawPopupOverlay(display); } else { // If virtualKeyboard is null, reset the banner to avoid getting stuck LOG_INFO("Virtual keyboard is null - resetting banner"); @@ -839,5 +836,12 @@ bool NotificationRenderer::isOverlayBannerShowing() return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil); } +void NotificationRenderer::showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs) +{ + if (!title || !content || current_notification_type != notificationTypeEnum::text_input) + return; + OnScreenKeyboardModule::instance().showPopup(title, content, durationMs); +} + } // namespace graphics #endif \ No newline at end of file diff --git a/src/graphics/draw/NotificationRenderer.h b/src/graphics/draw/NotificationRenderer.h index edb069513..e51bfa5ab 100644 --- a/src/graphics/draw/NotificationRenderer.h +++ b/src/graphics/draw/NotificationRenderer.h @@ -4,6 +4,7 @@ #include "OLEDDisplayUi.h" #include "graphics/Screen.h" #include "graphics/VirtualKeyboard.h" +#include "modules/OnScreenKeyboardModule.h" #include #include #define MAX_LINES 5 @@ -31,6 +32,7 @@ class NotificationRenderer static bool pauseBanner; static void resetBanner(); + static void showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs); static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state); static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state); static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state); diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 3d23acc9f..1f01640bf 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -257,7 +257,8 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes } #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) if (isHighResolution) { diff --git a/src/graphics/images.h b/src/graphics/images.h index 998fe8e2a..c268b3269 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -27,7 +27,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(USE_ST7796) || defined(ST7796_CS) || ARCH_PORTDUINO) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; diff --git a/src/input/HackadayCommunicatorKeyboard.cpp b/src/input/HackadayCommunicatorKeyboard.cpp new file mode 100644 index 000000000..87c8a24ae --- /dev/null +++ b/src/input/HackadayCommunicatorKeyboard.cpp @@ -0,0 +1,217 @@ +#if defined(HACKADAY_COMMUNICATOR) + +#include "HackadayCommunicatorKeyboard.h" +#include "main.h" + +#define _TCA8418_COLS 10 +#define _TCA8418_ROWS 8 +#define _TCA8418_NUM_KEYS 80 + +#define _TCA8418_MULTI_TAP_THRESHOLD 1500 + +using Key = TCA8418KeyboardBase::TCA8418Key; + +constexpr uint8_t modifierRightShiftKey = 30; +constexpr uint8_t modifierRightShift = 0b0001; +constexpr uint8_t modifierLeftShiftKey = 76; // keynum -1 +constexpr uint8_t modifierLeftShift = 0b0001; +// constexpr uint8_t modifierSymKey = 42; +// constexpr uint8_t modifierSym = 0b0010; + +// Num chars per key, Modulus for rotating through characters +static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = { + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 0, 0, 0, 2, 1, 2, 2, 0, 1, 1, 0, +}; + +static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{}, + {}, + {'+'}, + {'9'}, + {'8'}, + {'7'}, + {'2'}, + {'3'}, + {'4'}, + {'5'}, + {Key::ESC}, + {'q', 'Q'}, + {'w', 'W'}, + {'e', 'E'}, + {'r', 'R'}, + {'t', 'T'}, + {'y', 'Y'}, + {'u', 'U'}, + {'i', 'I'}, + {'o', 'O'}, + {Key::TAB}, + {'a', 'A'}, + {'s', 'S'}, + {'d', 'D'}, + {'f', 'F'}, + {'g', 'G'}, + {'h', 'H'}, + {'j', 'J'}, + {'k', 'K'}, + {'l', 'L'}, + {}, + {'z', 'Z'}, + {'x', 'X'}, + {'c', 'C'}, + {'v', 'V'}, + {'b', 'B'}, + {'n', 'N'}, + {'m', 'M'}, + {',', '<'}, + {'.', '>'}, + {}, + {}, + {}, + {'\\'}, + {' '}, + {}, + {Key::RIGHT}, + {Key::DOWN}, + {Key::LEFT}, + {}, + {}, + {}, + {'-'}, + {'6', '^'}, + {'5', '%'}, + {'4', '$'}, + {'[', '{'}, + {']', '}'}, + {'p', 'P'}, + {}, + {}, + {}, + {'*'}, + {'3', '#'}, + {'2', '@'}, + {'1', '!'}, + {Key::SELECT}, + {'\'', '"'}, + {';', ':'}, + {}, + {}, + {}, + {'/', '?'}, + {'='}, + {'.', '>'}, + {'0', ')'}, + {}, + {Key::UP}, + {Key::BSP}, + {}}; + +HackadayCommunicatorKeyboard::HackadayCommunicatorKeyboard() + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), + last_tap(0L), char_idx(0), tap_interval(0) +{ + reset(); +} + +void HackadayCommunicatorKeyboard::reset(void) +{ + TCA8418KeyboardBase::reset(); + enableInterrupts(); +} + +// handle multi-key presses (shift and alt) +void HackadayCommunicatorKeyboard::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 HackadayCommunicatorKeyboard::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 HackadayCommunicatorKeyboard::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 (HackadayCommunicatorTapMod[last_key]) + queueEvent(HackadayCommunicatorTapMap[last_key][modifierFlag % HackadayCommunicatorTapMod[last_key]]); + if (isModifierKey(last_key) == false) + modifierFlag = 0; +} + +void HackadayCommunicatorKeyboard::updateModifierFlag(uint8_t key) +{ + if (key == modifierRightShiftKey) { + modifierFlag ^= modifierRightShift; + } else if (key == modifierLeftShiftKey) { + modifierFlag ^= modifierLeftShift; + } +} + +bool HackadayCommunicatorKeyboard::isModifierKey(uint8_t key) +{ + return (key == modifierRightShiftKey || key == modifierLeftShiftKey); +} + +#endif \ No newline at end of file diff --git a/src/input/HackadayCommunicatorKeyboard.h b/src/input/HackadayCommunicatorKeyboard.h new file mode 100644 index 000000000..8316bed72 --- /dev/null +++ b/src/input/HackadayCommunicatorKeyboard.h @@ -0,0 +1,26 @@ +#include "TCA8418KeyboardBase.h" + +class HackadayCommunicatorKeyboard : public TCA8418KeyboardBase +{ + public: + HackadayCommunicatorKeyboard(); + void reset(void); + void trigger(void) override; + virtual ~HackadayCommunicatorKeyboard() {} + + protected: + void pressed(uint8_t key) override; + void released(void) override; + + void updateModifierFlag(uint8_t key); + bool isModifierKey(uint8_t key); + + private: + uint8_t modifierFlag; // 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/InputBroker.h b/src/input/InputBroker.h index 36328ca64..022101f7d 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -3,6 +3,12 @@ #include "Observer.h" #include "freertosinc.h" +#ifdef InputBrokerDebug +#define LOG_INPUT(...) LOG_DEBUG(__VA_ARGS__) +#else +#define LOG_INPUT(...) +#endif + enum input_broker_event { INPUT_BROKER_NONE = 0, INPUT_BROKER_SELECT = 10, diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index 2df1ace70..a5d2c614f 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -2,6 +2,8 @@ #include "configuration.h" #include +SerialKeyboard *globalSerialKeyboard = nullptr; + #ifdef INPUTBROKER_SERIAL_TYPE #define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file @@ -25,6 +27,8 @@ unsigned char KeyMap[3][4][10] = {{{'.', 'a', 'd', 'g', 'j', 'm', 'p', 't', 'w', SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name) { this->_originName = name; + + globalSerialKeyboard = this; } void SerialKeyboard::erase() @@ -85,9 +89,21 @@ int32_t SerialKeyboard::runOnce() e.source = this->_originName; // SELECT OR SEND OR CANCEL EVENT if (!(shiftRegister2 & (1 << 3))) { - e.inputEvent = INPUT_BROKER_UP; + if (shift > 0) { + e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED + e.kbchar = 0x09; // TAB + shift = 0; // reset shift after TAB + } else { + e.inputEvent = INPUT_BROKER_LEFT; + } } else if (!(shiftRegister2 & (1 << 2))) { - e.inputEvent = INPUT_BROKER_RIGHT; + if (shift > 0) { + e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED + e.kbchar = 0x09; // TAB + shift = 0; // reset shift after TAB + } else { + e.inputEvent = INPUT_BROKER_RIGHT; + } e.kbchar = 0; } else if (!(shiftRegister2 & (1 << 1))) { e.inputEvent = INPUT_BROKER_SELECT; diff --git a/src/input/SerialKeyboard.h b/src/input/SerialKeyboard.h index 1480c4d58..f25eb2630 100644 --- a/src/input/SerialKeyboard.h +++ b/src/input/SerialKeyboard.h @@ -8,6 +8,8 @@ class SerialKeyboard : public Observable, public concurrency public: explicit SerialKeyboard(const char *name); + uint8_t getShift() const { return shift; } + protected: virtual int32_t runOnce() override; void erase(); @@ -22,4 +24,6 @@ class SerialKeyboard : public Observable, public concurrency int lastKeyPressed = 13; int quickPress = 0; unsigned long lastPressTime = 0; -}; \ No newline at end of file +}; + +extern SerialKeyboard *globalSerialKeyboard; \ No newline at end of file diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp index 4ddaf7064..d2025c192 100644 --- a/src/input/TrackballInterruptBase.cpp +++ b/src/input/TrackballInterruptBase.cpp @@ -88,6 +88,50 @@ int32_t TrackballInterruptBase::runOnce() } } + if (directionDetected && directionStartTime > 0) { + uint32_t directionDuration = millis() - directionStartTime; + uint8_t directionPressedNow = 0; + directionInterval++; + + if (!digitalRead(_pinUp)) { + directionPressedNow = TB_ACTION_UP; + } else if (!digitalRead(_pinDown)) { + directionPressedNow = TB_ACTION_DOWN; + } else if (!digitalRead(_pinLeft)) { + directionPressedNow = TB_ACTION_LEFT; + } else if (!digitalRead(_pinRight)) { + directionPressedNow = TB_ACTION_RIGHT; + } + + const uint8_t DIRECTION_REPEAT_THRESHOLD = 3; + + if (directionPressedNow == TB_ACTION_NONE) { + // Reset state + directionDetected = false; + directionStartTime = 0; + directionInterval = 0; + this->action = TB_ACTION_NONE; + } else if (directionDuration >= LONG_PRESS_DURATION && directionInterval >= DIRECTION_REPEAT_THRESHOLD) { + // repeat event when long press these direction. + switch (directionPressedNow) { + case TB_ACTION_UP: + e.inputEvent = this->_eventUp; + break; + case TB_ACTION_DOWN: + e.inputEvent = this->_eventDown; + break; + case TB_ACTION_LEFT: + e.inputEvent = this->_eventLeft; + break; + case TB_ACTION_RIGHT: + e.inputEvent = this->_eventRight; + break; + } + + directionInterval = 0; + } + } + #if defined(T_DECK) // T-deck gets a super-simple debounce on trackball if (this->action == TB_ACTION_PRESSED && !pressDetected) { // Start long press detection @@ -113,17 +157,22 @@ int32_t TrackballInterruptBase::runOnce() pressDetected = true; pressStartTime = millis(); // Don't send event yet, wait to see if it's a long press - } else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp)) { - // LOG_DEBUG("Trackball event UP"); + } else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp) && !directionDetected) { + directionDetected = true; + directionStartTime = millis(); e.inputEvent = this->_eventUp; - } else if (this->action == TB_ACTION_DOWN && !digitalRead(_pinDown)) { - // LOG_DEBUG("Trackball event DOWN"); + // send event first,will automatically trigger every 50ms * 3 after 500ms + } else if (this->action == TB_ACTION_DOWN && !digitalRead(_pinDown) && !directionDetected) { + directionDetected = true; + directionStartTime = millis(); e.inputEvent = this->_eventDown; - } else if (this->action == TB_ACTION_LEFT && !digitalRead(_pinLeft)) { - // LOG_DEBUG("Trackball event LEFT"); + } else if (this->action == TB_ACTION_LEFT && !digitalRead(_pinLeft) && !directionDetected) { + directionDetected = true; + directionStartTime = millis(); e.inputEvent = this->_eventLeft; - } else if (this->action == TB_ACTION_RIGHT && !digitalRead(_pinRight)) { - // LOG_DEBUG("Trackball event RIGHT"); + } else if (this->action == TB_ACTION_RIGHT && !digitalRead(_pinRight) && !directionDetected) { + directionDetected = true; + directionStartTime = millis(); e.inputEvent = this->_eventRight; } #endif diff --git a/src/input/TrackballInterruptBase.h b/src/input/TrackballInterruptBase.h index 76a99f33d..67d4ee449 100644 --- a/src/input/TrackballInterruptBase.h +++ b/src/input/TrackballInterruptBase.h @@ -49,10 +49,14 @@ class TrackballInterruptBase : public Observable, public con // Long press detection for press button uint32_t pressStartTime = 0; + uint32_t directionStartTime = 0; + uint8_t directionInterval = 0; bool pressDetected = false; + bool directionDetected = false; uint32_t lastLongPressEventTime = 0; + uint32_t lastDirectionPressEventTime = 0; static const uint32_t LONG_PRESS_DURATION = 500; // ms - static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 500; // ms - interval between repeated long press events + static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300; // ms - interval between repeated long press events private: input_broker_event _eventDown = INPUT_BROKER_NONE; diff --git a/src/input/UpDownInterruptBase.h b/src/input/UpDownInterruptBase.h index ae84efdaf..2b9d38c83 100644 --- a/src/input/UpDownInterruptBase.h +++ b/src/input/UpDownInterruptBase.h @@ -3,6 +3,14 @@ #include "InputBroker.h" #include "mesh/NodeDB.h" +#ifndef UPDOWN_LONG_PRESS_DURATION +#define UPDOWN_LONG_PRESS_DURATION 300 +#endif + +#ifndef UPDOWN_LONG_PRESS_REPEAT_INTERVAL +#define UPDOWN_LONG_PRESS_REPEAT_INTERVAL 300 +#endif + class UpDownInterruptBase : public Observable, public concurrency::OSThread { public: @@ -40,8 +48,8 @@ class UpDownInterruptBase : public Observable, public concur uint32_t lastPressLongEventTime = 0; uint32_t lastUpLongEventTime = 0; uint32_t lastDownLongEventTime = 0; - static const uint32_t LONG_PRESS_DURATION = 300; - static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300; + static const uint32_t LONG_PRESS_DURATION = UPDOWN_LONG_PRESS_DURATION; + static const uint32_t LONG_PRESS_REPEAT_INTERVAL = UPDOWN_LONG_PRESS_REPEAT_INTERVAL; private: uint8_t _pinDown = 0; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 0ed2df116..0085c806b 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -7,6 +7,8 @@ #include "TDeckProKeyboard.h" #elif defined(T_LORA_PAGER) #include "TLoraPagerKeyboard.h" +#elif defined(HACKADAY_COMMUNICATOR) +#include "HackadayCommunicatorKeyboard.h" #else #include "TCA8418Keyboard.h" #endif @@ -20,6 +22,8 @@ KbI2cBase::KbI2cBase(const char *name) TCAKeyboard(*(new TDeckProKeyboard())) #elif defined(T_LORA_PAGER) TCAKeyboard(*(new TLoraPagerKeyboard())) +#elif defined(HACKADAY_COMMUNICATOR) + TCAKeyboard(*(new HackadayCommunicatorKeyboard())) #else TCAKeyboard(*(new TCA8418Keyboard())) #endif @@ -328,7 +332,7 @@ int32_t KbI2cBase::runOnce() break; } if (e.inputEvent != INPUT_BROKER_NONE) { - LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); + // LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); this->notifyObservers(&e); } TCAKeyboard.trigger(); diff --git a/src/main.cpp b/src/main.cpp index da2e39604..f8d89e1ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -394,7 +394,10 @@ void setup() io.pinMode(EXPANDS_GPIO_EN, OUTPUT); io.digitalWrite(EXPANDS_GPIO_EN, HIGH); io.pinMode(EXPANDS_SD_PULLEN, INPUT); +#elif defined(HACKADAY_COMMUNICATOR) + pinMode(KB_INT, INPUT); #endif + concurrency::hasBeenSetup = true; #if ARCH_PORTDUINO SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); @@ -877,8 +880,8 @@ void setup() if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796) || \ - defined(USE_SPISSD1306) + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_SPISSD1306) || defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR) screen = new graphics::Screen(screen_found, screen_model, screen_geometry); #elif defined(ARCH_PORTDUINO) if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) && @@ -1154,8 +1157,8 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796) || \ - defined(USE_SPISSD1306) + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_ST7796) || defined(USE_SPISSD1306) || defined(HACKADAY_COMMUNICATOR) if (screen) screen->setup(); #elif defined(ARCH_PORTDUINO) diff --git a/src/mesh/Default.h b/src/mesh/Default.h index 218d8d0fb..a60e3af9b 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -57,14 +57,7 @@ class Default // Note: Kept as uint32_t to match the public API parameter type static float congestionScalingCoefficient(uint32_t numOnlineNodes) { - // Increase frequency of broadcasts for small networks regardless of preset - if (numOnlineNodes <= 10) { - return 0.6; - } else if (numOnlineNodes <= 20) { - return 0.7; - } else if (numOnlineNodes <= 30) { - return 0.8; - } else if (numOnlineNodes <= 40) { + if (numOnlineNodes <= 40) { return 1.0; } else { float throttlingFactor = 0.075; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 4e99a22ef..d3000c500 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -664,7 +664,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.bluetooth.fixed_pin = defaultBLEPin; #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) || defined(USE_ST7796) + defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) || \ + defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR) bool hasScreen = true; #ifdef HELTEC_MESH_NODE_T114 uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET); diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 46de1dee0..0c48a7891 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -237,8 +237,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_T_ETH_ELITE = 91, /* Heltec HRI-3621 industrial probe */ meshtastic_HardwareModel_HELTEC_SENSOR_HUB = 92, - /* Reserved Fried Chicken ID for future use */ - meshtastic_HardwareModel_RESERVED_FRIED_CHICKEN = 93, + /* Muzi Works Muzi-Base device */ + meshtastic_HardwareModel_MUZI_BASE = 93, /* Heltec Magnetic Power Bank with Meshtastic compatible */ meshtastic_HardwareModel_HELTEC_MESH_POCKET = 94, /* Seeed Solar Node */ diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 9cbacc877..73ee26903 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -16,6 +16,7 @@ #include "graphics/draw/NotificationRenderer.h" #include "graphics/emotes.h" #include "graphics/images.h" +#include "input/SerialKeyboard.h" #include "main.h" // for cardkb_found #include "mesh/generated/meshtastic/cannedmessages.pb.h" #include "modules/AdminModule.h" @@ -1017,8 +1018,7 @@ int32_t CannedMessageModule::runOnce() // Clean up virtual keyboard if needed when going inactive if (graphics::NotificationRenderer::virtualKeyboard && graphics::NotificationRenderer::textInputCallback == nullptr) { LOG_INFO("Performing delayed virtual keyboard cleanup"); - delete graphics::NotificationRenderer::virtualKeyboard; - graphics::NotificationRenderer::virtualKeyboard = nullptr; + graphics::OnScreenKeyboardModule::instance().stop(false); } temporaryMessage = ""; @@ -1035,9 +1035,7 @@ int32_t CannedMessageModule::runOnce() // Clean up virtual keyboard after sending if (graphics::NotificationRenderer::virtualKeyboard) { LOG_INFO("Cleaning up virtual keyboard after message send"); - delete graphics::NotificationRenderer::virtualKeyboard; - graphics::NotificationRenderer::virtualKeyboard = nullptr; - graphics::NotificationRenderer::textInputCallback = nullptr; + graphics::OnScreenKeyboardModule::instance().stop(false); graphics::NotificationRenderer::resetBanner(); } @@ -1095,9 +1093,7 @@ int32_t CannedMessageModule::runOnce() // Clean up virtual keyboard if it exists during timeout if (graphics::NotificationRenderer::virtualKeyboard) { LOG_INFO("Cleaning up virtual keyboard due to module timeout"); - delete graphics::NotificationRenderer::virtualKeyboard; - graphics::NotificationRenderer::virtualKeyboard = nullptr; - graphics::NotificationRenderer::textInputCallback = nullptr; + graphics::OnScreenKeyboardModule::instance().stop(false); graphics::NotificationRenderer::resetBanner(); } @@ -1848,7 +1844,88 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); } - // --- Draw Free Text input with multi-emote support and proper line wrapping --- +#if INPUTBROKER_SERIAL_TYPE == 1 + // Chatter Modifier key mode label (right side) + { + uint8_t mode = globalSerialKeyboard ? globalSerialKeyboard->getShift() : 0; + const char *label = (mode == 0) ? "a" : (mode == 1) ? "A" : "#"; + + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + const int16_t th = FONT_HEIGHT_SMALL; + const int16_t tw = display->getStringWidth(label); + const int16_t padX = 3; + const int16_t padY = 2; + const int16_t r = 3; + + const int16_t bw = tw + padX * 2; + const int16_t bh = th + padY * 2; + + const int16_t bx = x + display->getWidth() - bw - 2; + const int16_t by = y + display->getHeight() - bh - 2; + + display->setColor(WHITE); + display->fillRect(bx + r, by, bw - r * 2, bh); + display->fillRect(bx, by + r, r, bh - r * 2); + display->fillRect(bx + bw - r, by + r, r, bh - r * 2); + display->fillCircle(bx + r, by + r, r); + display->fillCircle(bx + bw - r - 1, by + r, r); + display->fillCircle(bx + r, by + bh - r - 1, r); + display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r); + + display->setColor(BLACK); + display->drawString(bx + padX, by + padY, label); + } + + // LEFT-SIDE DESTINATION-HINT BOX (“Dest: Shift + ◄”) + { + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + const char *label = "Dest: Shift + "; + int16_t labelW = display->getStringWidth(label); + + // triangle size visually matches glyph height, not full line height + const int triH = FONT_HEIGHT_SMALL - 3; + const int triW = triH * 0.7; + + const int16_t padX = 3; + const int16_t padY = 2; + const int16_t r = 3; + + const int16_t bw = labelW + triW + padX * 2 + 2; + const int16_t bh = FONT_HEIGHT_SMALL + padY * 2; + + const int16_t bx = x + 2; + const int16_t by = y + display->getHeight() - bh - 2; + + // Rounded white box + display->setColor(WHITE); + display->fillRect(bx + r, by, bw - (r * 2), bh); + display->fillRect(bx, by + r, r, bh - (r * 2)); + display->fillRect(bx + bw - r, by + r, r, bh - (r * 2)); + display->fillCircle(bx + r, by + r, r); + display->fillCircle(bx + bw - r - 1, by + r, r); + display->fillCircle(bx + r, by + bh - r - 1, r); + display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r); + + // Draw text + display->setColor(BLACK); + display->drawString(bx + padX, by + padY, label); + + // Perfectly center triangle on text baseline + int16_t tx = bx + padX + labelW; + int16_t ty = by + padY + (FONT_HEIGHT_SMALL / 2) - (triH / 2) - 1; // -1 for optical centering + + // ◄ Left-pointing triangle + display->fillTriangle(tx + triW, ty, // top-right + tx, ty + triH / 2, // left center + tx + triW, ty + triH // bottom-right + ); + } +#endif + // Draw Free Text input with multi-emote support and proper line wrapping display->setColor(WHITE); { int inputY = 0 + y + FONT_HEIGHT_SMALL; diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 91e96b8d4..6d52a3e46 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -168,7 +168,7 @@ int32_t ExternalNotificationModule::runOnce() delay = EXT_NOTIFICATION_FAST_THREAD_MS; #endif -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) drv.go(); #endif } @@ -283,7 +283,7 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on) #ifdef UNPHONE unphone.rgb(red, green, blue); #endif -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) if (on) { drv.go(); } else { @@ -310,8 +310,7 @@ void ExternalNotificationModule::stopNow() rtttl::stop(); #ifdef HAS_I2S LOG_INFO("Stop audioThread playback"); - if (audioThread->isPlaying()) - audioThread->stop(); + audioThread->stop(); #endif // Turn off all outputs LOG_INFO("Turning off setExternalStates"); @@ -320,7 +319,7 @@ void ExternalNotificationModule::stopNow() externalTurnedOn[i] = 0; } setIntervalFromNow(0); -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) drv.stop(); #endif @@ -542,6 +541,19 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP (!isBroadcast(mp.to) && isToUs(&mp))) { // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us isNagging = true; +#ifdef T_LORA_PAGER + if (canBuzz()) { + drv.setWaveform(0, 16); // Long buzzer 100% + drv.setWaveform(1, 0); // Pause + drv.setWaveform(2, 16); + drv.setWaveform(3, 0); + drv.setWaveform(4, 16); + drv.setWaveform(5, 0); + drv.setWaveform(6, 16); + drv.setWaveform(7, 0); + drv.go(); + } +#endif if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { setExternalState(2, true); } else { diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 827524fc3..f918d630f 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -181,25 +181,25 @@ void setupModules() // new ReplyModule(); #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { -#ifndef T_LORA_PAGER - rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); - if (!rotaryEncoderInterruptImpl1->init()) { - delete rotaryEncoderInterruptImpl1; - rotaryEncoderInterruptImpl1 = nullptr; - } -#elif defined(T_LORA_PAGER) +#if defined(T_LORA_PAGER) // use a special FSM based rotary encoder version for T-LoRa Pager rotaryEncoderImpl = new RotaryEncoderImpl(); if (!rotaryEncoderImpl->init()) { delete rotaryEncoderImpl; rotaryEncoderImpl = nullptr; } -#else +#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2) upDownInterruptImpl1 = new UpDownInterruptImpl1(); if (!upDownInterruptImpl1->init()) { delete upDownInterruptImpl1; upDownInterruptImpl1 = nullptr; } +#else + rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); + if (!rotaryEncoderInterruptImpl1->init()) { + delete rotaryEncoderInterruptImpl1; + rotaryEncoderInterruptImpl1 = nullptr; + } #endif cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl->init(); diff --git a/src/modules/OnScreenKeyboardModule.cpp b/src/modules/OnScreenKeyboardModule.cpp new file mode 100644 index 000000000..e75d926bf --- /dev/null +++ b/src/modules/OnScreenKeyboardModule.cpp @@ -0,0 +1,272 @@ +#include "configuration.h" +#if HAS_SCREEN + +#include "graphics/SharedUIDisplay.h" +#include "graphics/draw/NotificationRenderer.h" +#include "input/RotaryEncoderInterruptImpl1.h" +#include "input/UpDownInterruptImpl1.h" +#include "modules/OnScreenKeyboardModule.h" +#include +#include + +namespace graphics +{ + +OnScreenKeyboardModule &OnScreenKeyboardModule::instance() +{ + static OnScreenKeyboardModule inst; + return inst; +} + +OnScreenKeyboardModule::~OnScreenKeyboardModule() +{ + if (keyboard) { + delete keyboard; + keyboard = nullptr; + } +} + +void OnScreenKeyboardModule::start(const char *header, const char *initialText, uint32_t durationMs, + std::function cb) +{ + if (keyboard) { + delete keyboard; + keyboard = nullptr; + } + keyboard = new VirtualKeyboard(); + callback = cb; + if (header) + keyboard->setHeader(header); + if (initialText) + keyboard->setInputText(initialText); + + // Route VK submission/cancel events back into the module + keyboard->setCallback([this](const std::string &text) { + if (text.empty()) { + this->onCancel(); + } else { + this->onSubmit(text); + } + }); + + // Maintain legacy compatibility hooks + NotificationRenderer::virtualKeyboard = keyboard; + NotificationRenderer::textInputCallback = callback; +} + +void OnScreenKeyboardModule::stop(bool callEmptyCallback) +{ + auto cb = callback; + callback = nullptr; + if (keyboard) { + delete keyboard; + keyboard = nullptr; + } + // Keep NotificationRenderer legacy pointers in sync + NotificationRenderer::virtualKeyboard = nullptr; + NotificationRenderer::textInputCallback = nullptr; + clearPopup(); + if (callEmptyCallback && cb) + cb(""); +} + +void OnScreenKeyboardModule::handleInput(const InputEvent &event) +{ + if (!keyboard) + return; + + if (processVirtualKeyboardInput(event, keyboard)) + return; + + if (event.inputEvent == INPUT_BROKER_CANCEL) + onCancel(); +} + +bool OnScreenKeyboardModule::processVirtualKeyboardInput(const InputEvent &event, VirtualKeyboard *targetKeyboard) +{ + if (!targetKeyboard) + return false; + + switch (event.inputEvent) { + case INPUT_BROKER_UP: + case INPUT_BROKER_UP_LONG: + targetKeyboard->moveCursorUp(); + return true; + case INPUT_BROKER_DOWN: + case INPUT_BROKER_DOWN_LONG: + targetKeyboard->moveCursorDown(); + return true; + case INPUT_BROKER_LEFT: + case INPUT_BROKER_ALT_PRESS: + targetKeyboard->moveCursorLeft(); + return true; + case INPUT_BROKER_RIGHT: + case INPUT_BROKER_USER_PRESS: + targetKeyboard->moveCursorRight(); + return true; + case INPUT_BROKER_SELECT: + targetKeyboard->handlePress(); + return true; + case INPUT_BROKER_SELECT_LONG: + targetKeyboard->handleLongPress(); + return true; + default: + return false; + } +} + +bool OnScreenKeyboardModule::draw(OLEDDisplay *display) +{ + if (!keyboard) + return false; + + // Timeout + if (keyboard->isTimedOut()) { + onCancel(); + return false; + } + + // Clear full screen behind keyboard + display->setColor(BLACK); + display->fillRect(0, 0, display->getWidth(), display->getHeight()); + display->setColor(WHITE); + keyboard->draw(display, 0, 0); + + // Draw popup overlay if needed + drawPopup(display); + return true; +} + +void OnScreenKeyboardModule::onSubmit(const std::string &text) +{ + auto cb = callback; + stop(false); + if (cb) + cb(text); +} + +void OnScreenKeyboardModule::onCancel() +{ + stop(true); +} + +void OnScreenKeyboardModule::showPopup(const char *title, const char *content, uint32_t durationMs) +{ + if (!title || !content) + return; + strncpy(popupTitle, title, sizeof(popupTitle) - 1); + popupTitle[sizeof(popupTitle) - 1] = '\0'; + strncpy(popupMessage, content, sizeof(popupMessage) - 1); + popupMessage[sizeof(popupMessage) - 1] = '\0'; + popupUntil = millis() + durationMs; + popupVisible = true; +} + +void OnScreenKeyboardModule::clearPopup() +{ + popupTitle[0] = '\0'; + popupMessage[0] = '\0'; + popupUntil = 0; + popupVisible = false; +} + +void OnScreenKeyboardModule::drawPopupOverlay(OLEDDisplay *display) +{ + // Only render the popup overlay (without drawing the keyboard) + drawPopup(display); +} + +void OnScreenKeyboardModule::drawPopup(OLEDDisplay *display) +{ + if (!popupVisible) + return; + if (millis() > popupUntil || popupMessage[0] == '\0') { + popupVisible = false; + return; + } + + // Build lines and leverage NotificationRenderer inverted box drawing for consistent style + constexpr uint16_t maxContentLines = 3; + const bool hasTitle = popupTitle[0] != '\0'; + + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + const uint16_t maxWrapWidth = display->width() - 40; + + auto wrapText = [&](const char *text, uint16_t availableWidth) -> std::vector { + std::vector wrapped; + std::string current; + std::string word; + const char *p = text; + while (*p && wrapped.size() < maxContentLines) { + while (*p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) { + if (*p == '\n') { + if (!current.empty()) { + wrapped.push_back(current); + current.clear(); + if (wrapped.size() >= maxContentLines) + break; + } + } + ++p; + } + if (!*p || wrapped.size() >= maxContentLines) + break; + word.clear(); + while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') + word += *p++; + if (word.empty()) + continue; + std::string test = current.empty() ? word : (current + " " + word); + uint16_t w = display->getStringWidth(test.c_str(), test.length(), true); + if (w <= availableWidth) + current = test; + else { + if (!current.empty()) { + wrapped.push_back(current); + current = word; + if (wrapped.size() >= maxContentLines) + break; + } else { + current = word; + while (current.size() > 1 && + display->getStringWidth(current.c_str(), current.length(), true) > availableWidth) + current.pop_back(); + } + } + } + if (!current.empty() && wrapped.size() < maxContentLines) + wrapped.push_back(current); + return wrapped; + }; + + std::vector allLines; + if (hasTitle) + allLines.emplace_back(popupTitle); + + char buf[sizeof(popupMessage)]; + strncpy(buf, popupMessage, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + char *paragraph = strtok(buf, "\n"); + while (paragraph && allLines.size() < maxContentLines + (hasTitle ? 1 : 0)) { + auto wrapped = wrapText(paragraph, maxWrapWidth); + for (const auto &ln : wrapped) { + if (allLines.size() >= maxContentLines + (hasTitle ? 1 : 0)) + break; + allLines.push_back(ln); + } + paragraph = strtok(nullptr, "\n"); + } + + std::vector ptrs; + for (const auto &ln : allLines) + ptrs.push_back(ln.c_str()); + ptrs.push_back(nullptr); + + // Use the standard notification box drawing from NotificationRenderer + NotificationRenderer::drawNotificationBox(display, nullptr, ptrs.data(), allLines.size(), 0, 0); +} + +} // namespace graphics + +#endif // HAS_SCREEN diff --git a/src/modules/OnScreenKeyboardModule.h b/src/modules/OnScreenKeyboardModule.h new file mode 100644 index 000000000..f86b71ec3 --- /dev/null +++ b/src/modules/OnScreenKeyboardModule.h @@ -0,0 +1,55 @@ +#pragma once + +#include "configuration.h" +#if HAS_SCREEN + +#include "graphics/Screen.h" // InputEvent +#include "graphics/VirtualKeyboard.h" +#include +#include +#include + +namespace graphics +{ +class OnScreenKeyboardModule +{ + public: + static OnScreenKeyboardModule &instance(); + + void start(const char *header, const char *initialText, uint32_t durationMs, + std::function callback); + + void stop(bool callEmptyCallback); + + void handleInput(const InputEvent &event); + static bool processVirtualKeyboardInput(const InputEvent &event, VirtualKeyboard *keyboard); + bool draw(OLEDDisplay *display); + + void showPopup(const char *title, const char *content, uint32_t durationMs); + void clearPopup(); + // Draw only the popup overlay (used when legacy virtualKeyboard draws the keyboard) + void drawPopupOverlay(OLEDDisplay *display); + + private: + OnScreenKeyboardModule() = default; + ~OnScreenKeyboardModule(); + OnScreenKeyboardModule(const OnScreenKeyboardModule &) = delete; + OnScreenKeyboardModule &operator=(const OnScreenKeyboardModule &) = delete; + + void onSubmit(const std::string &text); + void onCancel(); + + void drawPopup(OLEDDisplay *display); + + VirtualKeyboard *keyboard = nullptr; + std::function callback; + + char popupTitle[64] = {0}; + char popupMessage[256] = {0}; + uint32_t popupUntil = 0; + bool popupVisible = false; +}; + +} // namespace graphics + +#endif // HAS_SCREEN diff --git a/src/modules/SystemCommandsModule.cpp b/src/modules/SystemCommandsModule.cpp index dc5d8b41f..7fa4485c8 100644 --- a/src/modules/SystemCommandsModule.cpp +++ b/src/modules/SystemCommandsModule.cpp @@ -1,4 +1,5 @@ #include "SystemCommandsModule.h" +#include "input/InputBroker.h" #include "meshUtils.h" #if HAS_SCREEN #include "graphics/Screen.h" @@ -22,7 +23,7 @@ SystemCommandsModule::SystemCommandsModule() int SystemCommandsModule::handleInputEvent(const InputEvent *event) { - LOG_INFO("Input event %u! kb %u", event->inputEvent, event->kbchar); + LOG_INPUT("SystemCommands Input event %u! kb %u", event->inputEvent, event->kbchar); // System commands (all others fall through) switch (event->kbchar) { // Fn key symbols diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 69da25884..3b98eca3d 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -651,8 +651,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks { LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str()); -#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) const uint16_t connHandle = connInfo.getConnHandle(); +#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)) int phyResult = ble_gap_set_prefered_le_phy(connHandle, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_CODED_ANY); if (phyResult == 0) { @@ -660,6 +660,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks } else { LOG_WARN("Failed to prefer 2M PHY for conn %u, rc=%d", connHandle, phyResult); } +#endif int dataLenResult = ble_gap_set_data_len(connHandle, kPreferredBleTxOctets, kPreferredBleTxTimeUs); if (dataLenResult == 0) { @@ -670,9 +671,10 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu); pServer->updateConnParams(connHandle, 6, 12, 0, 200); -#endif } +#endif +#ifdef NIMBLE_TWO virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) { LOG_INFO("BLE disconnect reason: %d", reason); @@ -818,7 +820,7 @@ void NimbleBluetooth::setup() NimBLEDevice::init(getDeviceName()); NimBLEDevice::setPower(ESP_PWR_LVL_P9); -#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) +#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)) int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu); if (mtuResult == 0) { LOG_INFO("BLE MTU request set to %u", kPreferredBleMtu); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 9b5abfba0..085692f96 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -101,8 +101,6 @@ #define HW_VENDOR meshtastic_HardwareModel_T_WATCH_S3 #elif defined(GENIEBLOCKS) #define HW_VENDOR meshtastic_HardwareModel_GENIEBLOCKS -#elif defined(PRIVATE_HW) -#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #elif defined(NANO_G1) #define HW_VENDOR meshtastic_HardwareModel_NANO_G1 #elif defined(M5STACK) @@ -205,6 +203,8 @@ #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L #elif defined(HELTEC_WIRELESS_TRACKER_V2) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 +#else +#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #endif // ----------------------------------------------------------------------------- diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 4f7fb4776..f1bc43312 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -64,6 +64,16 @@ void onConnect(uint16_t conn_handle) connection->getPeerName(central_name, sizeof(central_name)); LOG_INFO("BLE Connected to %s", central_name); + // negotiate connections params as soon as possible + + ble_gap_conn_params_t newParams; + newParams.min_conn_interval = 24; + newParams.max_conn_interval = 40; + newParams.slave_latency = 5; + newParams.conn_sup_timeout = 400; + + sd_ble_gap_conn_param_update(conn_handle, &newParams); + // Notify UI (or any other interested firmware components) meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); bluetoothStatus->updateStatus(&newStatus); @@ -119,7 +129,7 @@ void startAdv(void) Bluefruit.Advertising.addService(meshBleService); /* Start Advertising * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Interval: fast mode = 20 ms, slow mode = 417,5 ms * - Timeout for fast mode is 30 seconds * - Start(timeout) with timeout = 0 will advertise forever (until connected) * @@ -127,7 +137,7 @@ void startAdv(void) * https://developer.apple.com/library/content/qa/qa1931/_index.html */ Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X } @@ -272,6 +282,24 @@ void NRF52Bluetooth::setup() // Set the connect/disconnect callback handlers Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); + + // Set slave latency to 5 to conserve power + // Despite name this does not impact data transfer + // https://docs.silabs.com/bluetooth/2.13/bluetooth-general-system-and-performance/optimizing-current-consumption-in-bluetooth-low-energy-devices + + Bluefruit.Periph.setConnSlaveLatency(5); + + // TODO: Adafruit defaul min, max interval seems to be (20,30) [in 1.25 ms units] -> (25.00, 31.25) milliseconds + // so using formula Interval Max * (Slave Latency + 1) ≤ 2 seconds + // max slave latency we can use is 30 (max available in BLE) + // and even double max inteval (see apple doc linked above for formulas) + // See Periph.SetConnInterval method + + // Tweak this later for even more power savings once those changes are confirmed to work well. + // Changing min, max interval may slow BLE transfer a bit - bumping slave latency will most likely not. + + + #ifndef BLE_DFU_SECURE bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bledfu.begin(); // Install the DFU helper @@ -300,7 +328,7 @@ void NRF52Bluetooth::setup() void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); } diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 1568e1790..d4699cd8c 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -109,7 +109,7 @@ #elif defined(HELTEC_MESH_SOLAR) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR #elif defined(MUZI_BASE) -#define HW_VENDOR meshtastic_HardwareModel_RESERVED_FRIED_CHICKEN +#define HW_VENDOR meshtastic_HardwareModel_MUZI_BASE #else #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN #endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index c03cc4454..5d1ba20ba 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -335,6 +335,16 @@ void cpuDeepSleep(uint32_t msecToWake) if (Serial1) // A straightforward solution to the wake from deepsleep problem Serial1.end(); #endif + +#ifdef TTGO_T_ECHO + // To power off the T-Echo, the display must be set + // as an input pin; otherwise, there will be leakage current. + pinMode(PIN_EINK_CS, INPUT); + pinMode(PIN_EINK_DC, INPUT); + pinMode(PIN_EINK_RES, INPUT); + pinMode(PIN_EINK_BUSY, INPUT); +#endif + setBluetoothEnable(false); #ifdef RAK4630 diff --git a/src/power.h b/src/power.h index 3f28dedb2..c826d98b4 100644 --- a/src/power.h +++ b/src/power.h @@ -13,6 +13,7 @@ #define NUM_OCV_POINTS 11 #endif +// Device specific curves go in variant.h #ifndef OCV_ARRAY #ifdef CELL_TYPE_LIFEPO4 #define OCV_ARRAY 3400, 3350, 3320, 3290, 3270, 3260, 3250, 3230, 3200, 3120, 3000 @@ -24,18 +25,6 @@ #define OCV_ARRAY 1400, 1300, 1280, 1270, 1260, 1250, 1240, 1230, 1210, 1150, 1000 #elif defined(CELL_TYPE_LTO) #define OCV_ARRAY 2700, 2560, 2540, 2520, 2500, 2460, 2420, 2400, 2380, 2320, 1500 -#elif defined(TRACKER_T1000_E) -#define OCV_ARRAY 4190, 4042, 3957, 3885, 3820, 3776, 3746, 3725, 3696, 3644, 3100 -#elif defined(HELTEC_MESH_POCKET_BATTERY_5000) -#define OCV_ARRAY 4300, 4240, 4120, 4000, 3888, 3800, 3740, 3698, 3655, 3580, 3400 -#elif defined(HELTEC_MESH_POCKET_BATTERY_10000) -#define OCV_ARRAY 4100, 4060, 3960, 3840, 3729, 3625, 3550, 3500, 3420, 3345, 3100 -#elif defined(SEEED_WIO_TRACKER_L1) -#define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300 -#elif defined(SEEED_SOLAR_NODE) -#define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786 -#elif defined(WISMESH_TAG) -#define OCV_ARRAY 4240, 4112, 4029, 3970, 3906, 3846, 3824, 3802, 3776, 3650, 3072 #else // LiIon #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 #endif diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index ff4f87bbe..0c1ef6967 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -62,10 +62,12 @@ #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 #define TFT_INVERT false +#define FORCE_LOW_RES 1 #define SCREEN_ROTATE #define SCREEN_TRANSITION_FRAMERATE 5 // fps #define DISPLAY_FORCE_SMALL_FONTS #define TFT_BACKLIGHT_ON LOW +#define USE_TFTDISPLAY 1 // Battery diff --git a/arch/esp32/esp32.ini b/variants/esp32/esp32.ini similarity index 95% rename from arch/esp32/esp32.ini rename to variants/esp32/esp32.ini index 08a547ca6..4bc48cebb 100644 --- a/arch/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -2,9 +2,15 @@ [esp32_base] extends = arduino_base custom_esp32_kind = esp32 +custom_mtjson_part = platform = # renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32 - platformio/espressif32@6.11.0 + platformio/espressif32@6.12.0 + +extra_scripts = + ${env.extra_scripts} + pre:extra_scripts/esp32_pre.py + extra_scripts/esp32_extra.py build_src_filter = ${arduino_base.build_src_filter} - - - - - diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index 469d93f94..a0443a918 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -7,7 +7,6 @@ build_src_filter = build_flags = ${esp32_base.build_flags} -I variants/esp32/m5stack_core - -DILI9341_DRIVER -DM5STACK -DUSER_SETUP_LOADED -DTFT_SDA_READ diff --git a/variants/esp32/m5stack_core/variant.h b/variants/esp32/m5stack_core/variant.h index 72aeb160e..cf741efe3 100644 --- a/variants/esp32/m5stack_core/variant.h +++ b/variants/esp32/m5stack_core/variant.h @@ -34,11 +34,13 @@ #define GPS_RX_PIN 16 #define GPS_TX_PIN 17 +#define ILI9341_DRIVER #define TFT_HEIGHT 240 #define TFT_WIDTH 320 #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 #define TFT_BUSY -1 +#define USE_TFTDISPLAY 1 // LCD screens are slow, so slowdown the wipe so it looks better #define SCREEN_TRANSITION_FRAMERATE 1 // fps diff --git a/variants/esp32/wiphone/variant.h b/variants/esp32/wiphone/variant.h index 70973db16..619ac622a 100644 --- a/variants/esp32/wiphone/variant.h +++ b/variants/esp32/wiphone/variant.h @@ -34,6 +34,7 @@ #define ST7789_SCK 18 #define ST7789_CS 5 #define ST7789_RS 26 +#define USE_TFTDISPLAY 1 // I don't have a 'wiphone' but this I think should not be defined this way (don't set TFT_BL if we don't have a hw way to control // it) // #define ST7789_BL -1 // EXTENDER_PIN(9) diff --git a/arch/esp32/esp32c3.ini b/variants/esp32c3/esp32c3.ini similarity index 100% rename from arch/esp32/esp32c3.ini rename to variants/esp32c3/esp32c3.ini diff --git a/arch/esp32/esp32c6.ini b/variants/esp32c6/esp32c6.ini similarity index 100% rename from arch/esp32/esp32c6.ini rename to variants/esp32c6/esp32c6.ini diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index da1c70c0a..9992ab2bf 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -22,8 +22,6 @@ build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 -D HAS_BLUETOOTH=1 - -D MESHTASTIC_EXCLUDE_WEBSERVER - -D MESHTASTIC_EXCLUDE_MQTT -DCONFIG_BT_NIMBLE_EXT_ADV=1 -DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 -D NIMBLE_TWO diff --git a/arch/esp32/esp32s2.ini b/variants/esp32s2/esp32s2.ini similarity index 100% rename from arch/esp32/esp32s2.ini rename to variants/esp32s2/esp32s2.ini diff --git a/arch/esp32/esp32s3.ini b/variants/esp32s3/esp32s3.ini similarity index 100% rename from arch/esp32/esp32s3.ini rename to variants/esp32s3/esp32s3.ini diff --git a/variants/esp32s3/hackaday-communicator/pins_arduino.h b/variants/esp32s3/hackaday-communicator/pins_arduino.h new file mode 100644 index 000000000..65d4e1751 --- /dev/null +++ b/variants/esp32s3/hackaday-communicator/pins_arduino.h @@ -0,0 +1,59 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// static const uint8_t TX = 43; +// static const uint8_t RX = 44; + +static const uint8_t SDA = 47; +static const uint8_t SCL = 14; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 17; +static const uint8_t MOSI = 3; +static const uint8_t MISO = 9; +static const uint8_t SCK = 8; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +// static const uint8_t BAT_ADC_PIN = 4; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/hackaday-communicator/platformio.ini b/variants/esp32s3/hackaday-communicator/platformio.ini new file mode 100644 index 000000000..970215045 --- /dev/null +++ b/variants/esp32s3/hackaday-communicator/platformio.ini @@ -0,0 +1,15 @@ +; Hackaday Communicator +[env:hackaday-communicator] +extends = esp32s3_base +board = hackaday-communicator +board_check = true +board_build.partitions = default_16MB.csv +upload_protocol = esptool + +build_flags = ${esp32s3_base.build_flags} + -D HACKADAY_COMMUNICATOR + -D BOARD_HAS_PSRAM + -I variants/esp32s3/hackaday-communicator + +lib_deps = ${esp32s3_base.lib_deps} + https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip \ No newline at end of file diff --git a/variants/esp32s3/hackaday-communicator/variant.h b/variants/esp32s3/hackaday-communicator/variant.h new file mode 100644 index 000000000..a127f548f --- /dev/null +++ b/variants/esp32s3/hackaday-communicator/variant.h @@ -0,0 +1,61 @@ +#define TFT_BL 2 +#define SPI_FREQUENCY 2000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 142 +#define TFT_WIDTH 428 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 0 +#define SCREEN_TRANSITION_FRAMERATE 5 +#define HAS_SCREEN 1 +#define TFT_BLACK 0 +#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness +#define USE_TFTDISPLAY 1 + +#define USE_POWERSAVE +#define SLEEP_TIME 120 + +#define GPS_DEFAULT_NOT_PRESENT 1 +// #define GPS_RX_PIN 44 +// #define GPS_TX_PIN 43 + +// #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +// ratio of voltage divider = 2.0 (RD2=100k, RD3=100k) +// #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. +// #define ADC_CHANNEL ADC1_GPIO4_CHANNEL + +// keyboard +#define I2C_SDA 47 // I2C pins for this board +#define I2C_SCL 14 +// #define KB_POWERON -1 // must be set to HIGH +// #define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55 +// #define KB_BL_PIN 46 // not used for now +#define KB_INT 13 +#define CANNED_MESSAGE_MODULE_ENABLE 1 + +#define TFT_DC 39 +#define TFT_CS 41 + +// LoRa +#define USE_SX1262 + +#define LORA_SCK 8 +#define LORA_MISO 9 +#define LORA_MOSI 3 +#define LORA_CS 17 + +// #define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 18 +#define LORA_DIO1 16 // SX1262 IRQ +#define LORA_DIO2 15 // 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 +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// #define LED_PIN 1 \ No newline at end of file diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h index 72bbf14fc..6524bbc72 100644 --- a/variants/esp32s3/heltec_v4/variant.h +++ b/variants/esp32s3/heltec_v4/variant.h @@ -34,6 +34,9 @@ #define LORA_PA_EN 2 #define LORA_PA_TX_EN 46 // enable tx +#if HAS_TFT +#define USE_TFTDISPLAY 1 +#endif /* * GPS pins */ diff --git a/variants/esp32s3/heltec_wireless_tracker/variant.h b/variants/esp32s3/heltec_wireless_tracker/variant.h index 79fa0e801..3b19f5afd 100644 --- a/variants/esp32s3/heltec_wireless_tracker/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker/variant.h @@ -27,6 +27,7 @@ #define TFT_OFFSET_Y -1 #define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS +#define USE_TFTDISPLAY 1 // pin 3 is Vext on v1.1 - HIGH enables LDO for Vext rail which goes to: // GPS UC6580: GPS V_DET(8), VDD_IO(7), DCDC_IN(21), pulls up RESETN(17), D_SEL(33) and BOOT_MODE(34) through 10kR diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h index 876ff1146..df5ab4716 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h @@ -27,6 +27,8 @@ #define VTFT_CTRL 46 // Heltec Tracker needs this pulled low for TFT #define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS +#define FORCE_LOW_RES 1 +#define USE_TFTDISPLAY 1 #define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost #define VEXT_ON_VALUE LOW diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h index 9ac064ea2..0ce6b3e00 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h @@ -27,6 +27,7 @@ #define TFT_INVERT false #define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS +#define USE_TFTDISPLAY 1 #define VEXT_ENABLE 3 // active HIGH - powers the GPS, GPS LNA and OLED #define VEXT_ON_VALUE HIGH diff --git a/variants/esp32s3/picomputer-s3/variant.h b/variants/esp32s3/picomputer-s3/variant.h index 8252e841c..275da1b61 100644 --- a/variants/esp32s3/picomputer-s3/variant.h +++ b/variants/esp32s3/picomputer-s3/variant.h @@ -32,6 +32,7 @@ #define ST7789_CS 6 #define ST7789_RS 1 #define ST7789_BL 5 +#define USE_TFTDISPLAY 1 #define ST7789_RESET -1 #define ST7789_MISO -1 diff --git a/variants/esp32s3/seeed-sensecap-indicator/variant.h b/variants/esp32s3/seeed-sensecap-indicator/variant.h index 8915395f3..f946528ae 100644 --- a/variants/esp32s3/seeed-sensecap-indicator/variant.h +++ b/variants/esp32s3/seeed-sensecap-indicator/variant.h @@ -37,6 +37,7 @@ #define TFT_BL 45 #define SCREEN_ROTATE #define SCREEN_TRANSITION_FRAMERATE 5 // fps +#define USE_TFTDISPLAY 1 #define HAS_TOUCHSCREEN 1 #define SCREEN_TOUCH_INT (6 | IO_EXPANDER) diff --git a/variants/esp32s3/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h index 9b0de631a..ece0cdeaf 100644 --- a/variants/esp32s3/t-deck/variant.h +++ b/variants/esp32s3/t-deck/variant.h @@ -22,6 +22,7 @@ #define SCREEN_ROTATE #define SCREEN_TRANSITION_FRAMERATE 5 #define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness +#define USE_TFTDISPLAY 1 #define HAS_TOUCHSCREEN 1 #define SCREEN_TOUCH_INT 16 diff --git a/variants/esp32s3/t-watch-s3/variant.h b/variants/esp32s3/t-watch-s3/variant.h index 578c23c0a..86b0a03c8 100644 --- a/variants/esp32s3/t-watch-s3/variant.h +++ b/variants/esp32s3/t-watch-s3/variant.h @@ -18,6 +18,7 @@ #define TFT_OFFSET_ROTATION 2 #define SCREEN_ROTATE #define SCREEN_TRANSITION_FRAMERATE 5 // fps +#define USE_TFTDISPLAY 1 #define HAS_TOUCHSCREEN 1 #define SCREEN_TOUCH_INT 16 diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h index 2875f6804..fe563cded 100644 --- a/variants/esp32s3/tlora-pager/variant.h +++ b/variants/esp32s3/tlora-pager/variant.h @@ -20,6 +20,7 @@ #define SCREEN_ROTATE #define SCREEN_TRANSITION_FRAMERATE 5 #define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness +#define USE_TFTDISPLAY 1 #define I2C_SDA SDA #define I2C_SCL SCL diff --git a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini index eca052f57..82bab453d 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini +++ b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini @@ -13,7 +13,10 @@ build_flags = -DEINK_WIDTH=250 -DEINK_HEIGHT=122 -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk - -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted //20 + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates //30 + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} diff --git a/variants/esp32s3/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h index 6f75ad0e2..2287dfe0b 100644 --- a/variants/esp32s3/tracksenger/internal/variant.h +++ b/variants/esp32s3/tracksenger/internal/variant.h @@ -28,6 +28,7 @@ #define TFT_OFFSET_Y -1 #define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS +#define USE_TFTDISPLAY 1 #define VEXT_ENABLE 3 // active HIGH, powers the lora antenna boost #define VEXT_ON_VALUE HIGH diff --git a/variants/esp32s3/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h index 843bf3924..f42a5b19f 100644 --- a/variants/esp32s3/tracksenger/lcd/variant.h +++ b/variants/esp32s3/tracksenger/lcd/variant.h @@ -16,6 +16,7 @@ #define ST7789_CS 38 #define ST7789_RS 40 #define ST7789_BL 21 +#define USE_TFTDISPLAY 1 // P#define TFT_BL 21 /* V1.1 PCB marking */ #define ST7789_RESET -1 diff --git a/variants/esp32s3/unphone/variant.h b/variants/esp32s3/unphone/variant.h index 366b49233..6f0710d62 100644 --- a/variants/esp32s3/unphone/variant.h +++ b/variants/esp32s3/unphone/variant.h @@ -36,6 +36,7 @@ #define TFT_INVERT false #define SCREEN_ROTATE true #define SCREEN_TRANSITION_FRAMERATE 5 +#define USE_TFTDISPLAY 1 #define HAS_TOUCHSCREEN 1 #define USE_XPT2046 1 diff --git a/variants/native/portduino-buildroot/variant.h b/variants/native/portduino-buildroot/variant.h index 3e91c6820..affd83051 100644 --- a/variants/native/portduino-buildroot/variant.h +++ b/variants/native/portduino-buildroot/variant.h @@ -1,4 +1,5 @@ #define HAS_SCREEN 1 +#define USE_TFTDISPLAY 1 #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE portduino_config.maxtophone diff --git a/arch/portduino/portduino.ini b/variants/native/portduino.ini similarity index 100% rename from arch/portduino/portduino.ini rename to variants/native/portduino.ini diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini index 474d45492..9cedfcc55 100644 --- a/variants/native/portduino/platformio.ini +++ b/variants/native/portduino/platformio.ini @@ -114,5 +114,5 @@ extends = env:native build_flags = -lgcov --coverage -fprofile-abs-path -fsanitize=address ${env:native.build_flags} ; https://docs.platformio.org/en/latest/projectconf/sections/env/options/test/test_testing_command.html test_testing_command = - ${platformio.build_dir}/${this.__env__}/program + ${platformio.build_dir}/${this.__env__}/meshtasticd -s diff --git a/variants/native/portduino/variant.h b/variants/native/portduino/variant.h index af05fcf8d..972443450 100644 --- a/variants/native/portduino/variant.h +++ b/variants/native/portduino/variant.h @@ -1,6 +1,7 @@ #ifndef HAS_SCREEN #define HAS_SCREEN 1 #endif +#define USE_TFTDISPLAY 1 #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE portduino_config.maxtophone diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index 79e31c54a..e4a6c0397 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -62,17 +62,11 @@ extern "C" { /* * Buttons */ -#define PIN_BUTTON2 (32 + 10) +#define PIN_BUTTON1 (32 + 10) +#define PIN_BUTTON2 (32 + 7) #define ALT_BUTTON_PIN PIN_BUTTON2 #define ALT_BUTTON_ACTIVE_LOW true #define ALT_BUTTON_ACTIVE_PULLUP true -#define PIN_BUTTON1 (32 + 7) - -// #define PIN_BUTTON1 (0 + 11) -// #define PIN_BUTTON1 (32 + 7) - -// #define BUTTON_CLICK_MS 400 -// #define BUTTON_TOUCH_MS 200 /* * Analog pins @@ -157,15 +151,15 @@ External serial flash WP25R1635FZUIL0 #define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 9) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 8) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 8) // This is for bits going TOWARDS the GPS +#define GPS_RX_PIN (32 + 9) // This is for bits going TOWARDS the CPU #define GPS_THREAD_INTERVAL 50 #define PIN_GPS_SWITCH (32 + 1) // GPS开关判断 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN // PCF8563 RTC Module #define PCF8563_RTC 0x51 @@ -203,4 +197,4 @@ External serial flash WP25R1635FZUIL0 } #endif -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index cf940172b..2ad3efa27 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -78,11 +78,11 @@ extern "C" { #define GPS_BAUDRATE 9600 #define PIN_GPS_RESET 25 #define PIN_GPS_STANDBY 21 -#define GPS_TX_PIN 20 -#define GPS_RX_PIN 22 +#define GPS_TX_PIN 22 +#define GPS_RX_PIN 20 #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN // Button #define BUTTON_PIN 12 #define BUTTON_PIN_ALT (0 + 12) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index 5e543b21f..d30b88d66 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -107,12 +107,12 @@ static const uint8_t A0 = PIN_A0; #define PIN_GPS_REINIT (29) #define PIN_GPS_STANDBY (30) #define PIN_GPS_PPS (31) -#define GPS_TX_PIN (3) -#define GPS_RX_PIN (2) +#define GPS_TX_PIN (2) +#define GPS_RX_PIN (3) #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN // Secondary UART #define PIN_SERIAL2_RX (22) diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 836fa74a3..204ca6306 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -128,13 +128,13 @@ static const uint8_t A0 = PIN_A0; // #define PIN_GPS_WAKE (GPIO_PORT1 + 2) // An output to wake GPS, low means allow sleep, high means force wake // Seems to be missing on this new board #define PIN_GPS_PPS (GPIO_PORT1 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (GPIO_PORT1 + 9) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (GPIO_PORT1 + 8) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (GPIO_PORT1 + 8) // This is for bits going TOWARDS the GPS +#define GPS_RX_PIN (GPIO_PORT1 + 9) // This is for bits going TOWARDS the CPU #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN #define GPS_RESET_PIN (GPIO_PORT1 + 5) // GPS reset pin diff --git a/arch/nrf52/cpp_overrides/lfs_util.h b/variants/nrf52840/cpp_overrides/lfs_util.h similarity index 100% rename from arch/nrf52/cpp_overrides/lfs_util.h rename to variants/nrf52840/cpp_overrides/lfs_util.h diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-micro_Pinouts_2025-12-04.pdf similarity index 82% rename from variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf rename to variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-micro_Pinouts_2025-12-04.pdf index 63a80dbbe..6fb9c11c6 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-micro_Pinouts_2025-12-04.pdf @@ -10,7 +10,7 @@ endobj 4 0 obj << -/Length 334350 +/Length 339732 >> stream 0.14 w @@ -364,14 +364,6 @@ BT /F2 9.818181818181817 Tf 10.80 TL 0.000 0.000 0.502 rg -730.08 129.04 Td -(2025-11-08) Tj -ET -7.20 w -BT -/F2 9.818181818181817 Tf -10.80 TL -0.000 0.000 0.502 rg 730.08 114.64 Td (2025-11-07) Tj ET @@ -4753,64 +4745,6 @@ ET 0.63 0.00 0.00 RG 0.00 g [] 0 d -370.080 1068.480 m -366.480 1064.880 l -355.680 1064.880 l -355.680 1072.080 l -366.480 1072.080 l -370.080 1068.480 l -S -1 J -1 j -0.72 w -0.63 0.00 0.00 RG -[] 0 d -370.080 1068.480 m -370.080 1068.480 l -S -7.20 w -BT -/F2 6.545454545454544 Tf -7.20 TL -0.000 0.000 1.000 rg -332.05 1065.61 Td -(GPSTX) Tj -ET -1 J -1 j -0.72 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d -370.080 1075.680 m -366.480 1072.080 l -355.680 1072.080 l -355.680 1079.280 l -366.480 1079.280 l -370.080 1075.680 l -S -1 J -1 j -0.72 w -0.63 0.00 0.00 RG -[] 0 d -370.080 1075.680 m -370.080 1075.680 l -S -7.20 w -BT -/F2 6.545454545454544 Tf -7.20 TL -0.000 0.000 1.000 rg -331.32 1072.81 Td -(GPSRX) Tj -ET -1 J -1 j -0.72 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d 370.080 1061.280 m 366.480 1057.680 l 355.680 1057.680 l @@ -16535,27 +16469,27 @@ ET 0.63 0.00 0.00 RG 0.00 g [] 0 d -85.680 1075.680 m +85.680 1068.480 m 82.080 1072.080 l 71.280 1072.080 l -71.280 1079.280 l -82.080 1079.280 l -85.680 1075.680 l +71.280 1064.880 l +82.080 1064.880 l +85.680 1068.480 l S 1 J 1 j 0.72 w 0.63 0.00 0.00 RG [] 0 d -85.680 1075.680 m -85.680 1075.680 l +85.680 1068.480 m +85.680 1068.480 l S 7.20 w BT /F2 6.545454545454544 Tf 7.20 TL 0.000 0.000 1.000 rg -46.92 1072.81 Td +46.92 1066.77 Td (GPSRX) Tj ET 1 J @@ -16564,27 +16498,27 @@ ET 0.63 0.00 0.00 RG 0.00 g [] 0 d -85.680 1068.480 m -82.080 1064.880 l -71.280 1064.880 l +85.680 1075.680 m +82.080 1079.280 l +71.280 1079.280 l 71.280 1072.080 l 82.080 1072.080 l -85.680 1068.480 l +85.680 1075.680 l S 1 J 1 j 0.72 w 0.63 0.00 0.00 RG [] 0 d -85.680 1068.480 m -85.680 1068.480 l +85.680 1075.680 m +85.680 1075.680 l S 7.20 w BT /F2 6.545454545454544 Tf 7.20 TL 0.000 0.000 1.000 rg -47.65 1065.61 Td +47.65 1073.97 Td (GPSTX) Tj ET 1 J @@ -17606,7 +17540,7 @@ ET 0 j 72 M 0.72 w -0.00 G +0.63 0.00 0.00 RG [] 0 d 35.28 476.64 777.60 -208.80 re S @@ -19796,42 +19730,6 @@ S 0.00 0.53 0.00 RG 0.00 g [] 0 d -85.680 1075.680 m -96.480 1075.680 l -S -1 J -1 j -0.72 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -85.680 1075.680 m -96.480 1075.680 l -S -1 J -1 j -0.72 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -85.680 1068.480 m -96.480 1068.480 l -S -1 J -1 j -0.72 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -85.680 1068.480 m -96.480 1068.480 l -S -1 J -1 j -0.72 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d 85.680 1061.280 m 96.480 1061.280 l S @@ -27766,6 +27664,556 @@ BT 672.48 619.79 Td (E22P-915M30S) Tj ET +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1075.680 m +85.680 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1075.680 m +85.680 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1068.480 m +85.680 1068.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1068.480 m +85.680 1068.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 1068.480 m +366.480 1072.080 l +355.680 1072.080 l +355.680 1064.880 l +366.480 1064.880 l +370.080 1068.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 1068.480 m +370.080 1068.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +331.32 1066.77 Td +(GPSRX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 1075.680 m +366.480 1079.280 l +355.680 1079.280 l +355.680 1072.080 l +366.480 1072.080 l +370.080 1075.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 1075.680 m +370.080 1075.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +332.05 1073.97 Td +(GPSTX) Tj +ET +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +150.48 112.52 Td +(Example GNSS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.080 60.480 m +229.680 56.880 l +240.480 56.880 l +240.480 64.080 l +229.680 64.080 l +226.080 60.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 60.480 m +226.080 60.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +241.92 57.61 Td +(GPSRX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.080 67.680 m +229.680 64.080 l +240.480 64.080 l +240.480 71.280 l +229.680 71.280 l +226.080 67.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 67.680 m +226.080 67.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +241.92 64.81 Td +(GPSTX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +229.680 85.680 m +229.680 78.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.080 85.680 m +233.280 85.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +229.680 78.480 m +229.680 78.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +0.00 1.00 -1.00 0.00 233.03 86.40 Tm +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +229.680 42.480 m +229.680 49.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +223.200 42.480 m +236.160 42.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +225.360 41.040 m +234.000 41.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +227.520 39.600 m +231.840 39.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +228.960 38.160 m +230.400 38.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +229.680 49.680 m +229.680 49.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +0.00 1.00 -1.00 0.00 230.91 23.26 Tm +(GND) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +150.93 28.72 Td +(NEO-6M) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +144.72 84.24 33.12 -43.20 re +S +q +1 0 0 1 177.84 51.12 cm +-0.0000 -1.0000 1.0000 -0.0000 0 0 cm +1 0 0 1 -33.12 -33.12 cm +43.20 0 0 33.12 0 0 cm +/I1 Do +Q +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +210.24 28.08 m 210.24 30.07 208.63 31.68 206.64 31.68 c +204.65 31.68 203.04 30.07 203.04 28.08 c +203.04 26.09 204.65 24.48 206.64 24.48 c +208.63 24.48 210.24 26.09 210.24 28.08 c +S +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +110.88 102.24 100.80 -79.20 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +210.24 97.20 m 210.24 99.19 208.63 100.80 206.64 100.80 c +204.65 100.80 203.04 99.19 203.04 97.20 c +203.04 95.21 204.65 93.60 206.64 93.60 c +208.63 93.60 210.24 95.21 210.24 97.20 c +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +119.52 28.08 m 119.52 30.07 117.91 31.68 115.92 31.68 c +113.93 31.68 112.32 30.07 112.32 28.08 c +112.32 26.09 113.93 24.48 115.92 24.48 c +117.91 24.48 119.52 26.09 119.52 28.08 c +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +119.52 97.20 m 119.52 99.19 117.91 100.80 115.92 100.80 c +113.93 100.80 112.32 99.19 112.32 97.20 c +112.32 95.21 113.93 93.60 115.92 93.60 c +117.91 93.60 119.52 95.21 119.52 97.20 c +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +119.88 36.00 m 119.88 36.99 119.07 37.80 118.08 37.80 c +117.09 37.80 116.28 36.99 116.28 36.00 c +116.28 35.01 117.09 34.20 118.08 34.20 c +119.07 34.20 119.88 35.01 119.88 36.00 c +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +200.16 95.04 m 200.16 97.43 198.23 99.36 195.84 99.36 c +193.45 99.36 191.52 97.43 191.52 95.04 c +191.52 92.65 193.45 90.72 195.84 90.72 c +198.23 90.72 200.16 92.65 200.16 95.04 c +S +7.20 w +BT +/F2 3.6363665454545444 Tf +4.00 TL +0.627 0.000 0.000 rg +178.56 94.04 Td +(Battery) Tj +ET +7.20 w +BT +/F2 3.6363665454545444 Tf +4.00 TL +0.627 0.000 0.000 rg +0.00 1.00 -1.00 0.00 119.00 39.48 Tm +(Antenna) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +194.98 50.99 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.28 54.59 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 53.280 m +211.680 53.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +201.15 58.19 Td +(TX) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.28 61.79 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 60.480 m +211.680 60.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +200.43 65.39 Td +(RX) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.28 68.99 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 67.680 m +211.680 67.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +1.000 0.000 0.000 rg +195.70 72.59 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.28 76.19 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 74.880 m +211.680 74.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 78.480 m +229.680 74.880 l +S +229.680 74.880 m +226.080 74.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 78.480 m +229.680 74.880 l +S +229.680 74.880 m +226.080 74.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 49.680 m +229.680 53.280 l +S +229.680 53.280 m +226.080 53.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 49.680 m +229.680 53.280 l +S +229.680 53.280 m +226.080 53.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 67.680 m +226.080 67.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 67.680 m +226.080 67.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 60.480 m +226.080 60.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 60.480 m +226.080 60.480 l +S +7.20 w +BT +/F2 10.307802433786685 Tf +11.34 TL +0.000 g +730.08 127.34 Td +(2025-12-04) Tj +ET 0.80 0.00 0.00 rg 656.28 233.28 m 656.28 234.27 655.47 235.08 654.48 235.08 c 653.49 235.08 652.68 234.27 652.68 233.28 c @@ -28290,6 +28738,294 @@ stream x?#j]0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L&== endstream endobj +12 0 obj +<< +/Type /XObject +/Subtype /Image +/Width 645 +/Height 455 +/ColorSpace /DeviceRGB +/BitsPerComponent 8 +/Length 67274 +/Filter /DCTDecode +>> +stream +JFIFHHExifMM* + (12BCiAppleiPhone 8HH11.2.62018:03:08 17:52:25<D"'d0221L` +t| + +  +|8848840100Ǣ2T3t4"z 2018:03:08 17:52:252018:03:08 17:52:25 ood2Apple iOSMM  .h     + +         bplist00O%&*07:33-0)+" ##&() ! + +NO_D@^M?ntHq s@Y]4b j  M<!# 4% "'M!"/<Cs#Ew2()'" Np bplist00UflagsUvalueYtimescaleUepoch|3;'-/8= ?`Mmey!wq825sdd  AppleiPhone 8 back camera 3.99mm f/1.8 +http://ns.adobe.com/xap/1.0/ 8Photoshop 3.08BIM8BIM%ُ B~4ICC_PROFILE$applmntrRGB XYZ   acspAPPLAPPL-appl%M8 +descecprtd#wtptrXYZgXYZbXYZrTRC chad,bTRC gTRC desc Display P3textCopyright Apple Inc., 2017XYZ QXYZ =XYZ J7 +XYZ (8 ȹparaff Y +[sf32 B&n" + }!1AQa"q2#BR$3br +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz + w!1AQaq"2B #3Rbr +$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC C  ) ?cط? jf"HL,} Tco^(XIlz2ՌWesR~Ԙu3 zT8FiƛeEcaE^5 ✨E$U +J󩼳@B +pLCgq j Xw֡ѹQE]IՂ7?Q~ՑkVj5Lԕ:Tl)ihO;TE8RY&sKEoș7 +("0)O>ͭKR]HAjit~mW?l +wAQE"g)A\vP>uvsRI!|Fڌ^I#=+o'/-i[Y">4*Rz#rXSn&H]=EICm1-MPީ̮QES(= 0}ihإs1rsJsږ9=iQZBhPя`b}h斊=[h$(* Ũek c/xԲ0?Wց^P0I 2+|>]R=*=^|E`3jI<~5v'YncMBpr~k/# wV!(~o +e,zz܍ںB?F_B◂|C2]9WgE.ʀPك +v:G8MPG緮qv.v)4RG޾f=a֓b횊IK@EX[([P^]\"倪xSeNXODeXG4^qLVUau`pVaQKc)EJ|UXו64rqRhCNC]]Raih10* +Q|b3Uܙ_ɧymt9SqR/--Tȼ?*QL;CVbjcF.R&"# +~I?@*mV yyjA/~ӟq㻫]Mw,Ļ`I|}cUIn<76nkW;| FiZlcwIJ(c=:WGk[\d,x w5oY˫!3l2^|og \Iah*=x#񯱾9H?Ң 3jRP#oJKr/9VVU}gVL3O fY? +WTYQ cwk ߋA|yDj`O1z_}sURQ<*I N?%Q{F.Gr(he}(Sh=夢 wGM wIQG*夢"h;h)QN~jeq7Cnlq;;Y.8XԱ>¿,5|leKÛI*J6Tzw?3{#>,Ԟ-y4cqʿ>f)G0B2~4|v¯KNdH!P&~\e-N.Y^3!wW)|cON~.9IYaN+W}[nhqVxmXm"(U2ݰP937y3˖>szx3ZP`cP+0ZF9!Σ>̴mW*}L}̊\}gNZ@S˨?һz'?_LeU O^;UFFhu9bͯoM|!dڟ }[LQv<`{`zױ"j?ߴkvbfufI\[y71fi*dFS';O?bo-L23 +ҿǟ \N>"|8'חDŽ5x2X"TPe+Zif?c~}%@czyqtҴ E7m`jMy¿ZŌ" +9+ӼuV.VUJ_, +Z]\諴UGU--GN4$J@tIMI Bqɮ_֡l^cE'ξ6#\>UfjM*WOۣLm3AeApSm}"&C{z +)MgW6̖rq?:tjʎZѣ{ ;'K ##V߷~c WĐ|? `."e6Ҹ"(ji6AñTѴ[Q1~BO[;~, :{Wm|$`lҚIm-ݣtg*ې|b+96:#(? V7>CYv A'5:y?041;v'ŏ7?*RƱoitgewFN}+Ǎ^gʑS)TQ4WF4{D-eWi'ھ~|2xcWf3  =kz*E^Q7]V*>碹c6Z6&;8~G~uPg+^/Žͬ#) +Tu'ފIAu-6>~5U?g2okw={Ks u#$]Jb %II-O^E#Xڡx;nR3^sG.Ì^n@ `Gݬd3=z1|IL1 PG r}E{/_ ~ߟ'̰޿z?L##\hĄ8d=_爼ksAR6e;(|½V2Wci˞<>S|)֕_ hB%Lzvߵ<)¸$1b֍o'޸X|GxJKSp +m#@>oqRI$-y2X㐡E*Kؕw{L> :k^;Agy%9RH?f~Ѵ{TEҾYbY4|eWŠ(yEPEPEPEPEPEP^Jm )+H>^* 5rW ;Op+R?mG^l?k Tpk4^\XZĶ"~rG+BMkk?hTtRk M,<}N\p@Zԏ!Pv㿱&_W>3buS_]Q6(%GHV;g +C?aoڳc \Ƽ/ +G|_Eyaigm=#GnXaNLu5Mө8* + ~?l_ +i J=Cy摉u޿/~u-PM!8!18R~^_.f?~7Od@# Uyy9U5*CCS/Ex\usWiooGglcB7MHuBV>z~Q$5E$=LƧ5vvfV%[;/)j\?n*o>97A=d>i`Guټ=WVSx+I׊;[eZ]jR_F~=k\ N] +ʩ.W#u:oXW +{ٶ4] Y61>0x~ SoyOjR_ xUԼ`ٶI7IH=jeF2sz O/# @i$@z_ǿ:W' 9sԞWo5 tt:dB;K ׵vm +$Ҧ7fb%U  +륆p=L:Og%jK$/ao _%D[ no$$!*w*G?<9em[x8'2ʝV9Ψ'x_'\ogc|V&,L.2s5dM4qt/E^ +$u#$Ư.UcY-fk%>6O?4X@Q:W!P W؊۸"W!Zah-QYHQE(ik%΁cľUōbYQ'}3ҿ)oċ]RM,`%#X3NdGCT0_iǚ0$pWZ텬0%&M8gʊcO&ڍNP2egRxmQ[Q<p_qm#/:?i)|_/5iL%SRzOQ~ ܐbF8ힵ [?>9 ftE=0?: F+V3Yɲ~&}+K_|p[_ۙ5v\O.E"n#v!mEs~HW'oǏD';YA`;zGor#xNԭULolJtt>Qxn㶝whoPÆn+,*p1]3iWaƷ 암$4xzQuV ⿮ e~;48IJg!߶=\8]\':krFX*vdo{zq[$t0~?>xcTzЂ1He9+oM' 7oM$ǀڿ%O|W2[c(Y9OZʽ*-0]G?7»m[WédV.sǵ~F#R^Mr\HϦϠYO6Tcx=鉨5ȡR*ƥJ1eةʧ,%w{~ڟŽηsNdp2\zg 㿆Ѧ0+/9ۭul\Zս.a!_?Ofv<|y݈S*Sԝ:ցL{߆A3VјvkFyn_OosJɯ..,YʦAڿ)>(˦uGZH_88x_ GZ[]YlhQ[FrKSC!ß>ib+kV1d`׭AޣoU֛e&]3_)bfQ/lNĝ{_ƾaPNnB*t~[i?ILP`p0[y|}JHQ]z`պ :)=ERqQEH(((((('8>IASp> +2Iuٗ +Hzh H݆ +:FwNUf)NC?^i-l Sk4*}NI4϶൞$oiIqc0?ޟZ."xf.|ː +ٟ%NV 㑁`OZf^A!;ҼL*nPUPFUP~Ed?fSR2#"L㏩c5?>h, UG= (Ӛcb-Q9W~ؚumZ'6WyO"ǯt?7\~2Cmm{1'#?E֛4Vy)f  ޿ړ o|D|BBο{WS0Wwq?"'#;^KfL_ዟ# +3x6Jιd 3JVK|_èkۛqhʄ˳!3_?Eض/؈ID۸1^uHʌ> @Mѭ`]jVlj|ԝ߶I$`G8k5KTm;),ntr 05\ˡф)O#kfw VGsV<}:WiOYx;IU Nx"߱u톨>(|>s$Y\/x;z={̠R9Y?gRI/* ڹM_' -N|enĀ2Mn/SMV4IMLMJ5ՕyZ>hz|8$fmoڂ?_ϦOHfvI|(0u޿@_WFn4,1=;/$/3xb.{`dž}A⽼5շ{#PT =';|;ـ d(fwM~OOK 8:qZ^Z]y܏[WdTTZ]Z]_Ռy橼_&x#4')\ @2q_;ITW#+yJRُ !{ҿ2@:_ޡac"8(>k(w+zWg(Ęu|zYv*ɳn1OՁ_5ZR?u5Pk!AVn&n"`Ka8=*WEbbetH♶`{8f4vTٿc6o,^?1 wvA5?~֟ht 3*{0'|]Kz=O~\8tyq+75HbW[C{ux唂t# yoǫ/Ha_\} 𮬱V"PCt +S bEkfӗtQF?Z\/oI۪?;5j9b>rUFќ|aΈjOL,8 N~5qL*W3|eY)pwL zWGtpʜff9*;?7~h!O>T,KX)w\/!u(/4KmM U-վj] Ś3Vmβ#0W߲AKkŃ>RqQ"4J:&qgGݮoh"Ƀ; m>[~73(%N9$WjEEPƛ+K_H/~#'WhZ59^_TZpEIz)m}[#*\Od6guoD7VB[8?ZTx/MW-`q__4~?n/|UiD؎?5 s{|t> =So{?4?,Zd+,@_߶[ +@q6xt1_ɮos޷RO?~<5k}I x[FtXDVQ|O +W[7.69ۂ+sQEj鎖i1[}㟅7h%6mfg{VNoIFP$0jW󸚉͸,O)M;\AEO['qWuS$BQ#KE&z*r$ֳ,QEQEQEQEQE*Ի֡KKÿs1Xh#ky"~sOGN*sRGw|d~7]xM.Lg +k߈3k7@BpN3zt/0\[kZ|fiAÅ7ڿ/ڇyuow&i#8?*jU>FLu|9Έ?+bL0Iw_o߁:%{<(^0]^-Β 8U䩕E;7Z1db&,?ݧ߄*|3f dq'As~Ь^5"Y{ +-製\s-گ>g&2A]J$u;wŸ|Dkr<#}7: DCnUW/|/xZ=7E`%8J胁W-YJyo~D`b.H2栢0K7DNU+axQo|>.4'nq5Cuu <8joS ΓџŧV~ ]_M: ~| &4G%.OJY5|cR5)7`|)ҽMS`js_<1q%oi +͸l.5uy0Gh9Z/2LQ&{ *r2(#? !ux<^4$~(_k7 +*CRп7 >-s#&w`HdQõwPҖ ?Hi.ZOrw'~ etwG +Qw.#Gq-& |6]6{~">|O~ AT.9l҅ztkchQVU+iҵ3`'~xsZ?t}UQU +08#`+QIswgbq&7b袵J(QE(5zy8Z=_ ~_{Baھw +4]R2?C_;LqXzdh&kG2û84/įj_ uҵ4*aI=xBC#_}5įG%șNX1 WWBD<+w\~6eSӯJrSB"&F)#;JtKa]$pj2; +#VjI-Ƥl| +^-f070'6{׫|\2YĒm*A#'H|9{ƕKmymc?kUC ^?ϯ>}_>5uyM!\B.n+?7v8o7n!m>%xj q!,[#>#2Dz87/j*u#kl>6ʖWVQ}CQԵEr3u +("*7IE&ez\s.9ū +7E+3X4SL..IR EEJ "X)PNhشҖ$nţbӨuPEPEPA(6@EGS?ݨk9 +(@??'ީh4t")eLvQA!EPNSm& )I045_RZ*}|/>p: γ1UJNFuԨ~V&`--EgSZI p)J3S`SPFmܯEX2QE@Q@HO ET {kVmZe]M{VRVļ*p#QP-8QHWЋ#ҙhh(XJ *ZL +Z +(Q@Q@\N7;Z}|UEư3?RJT1w1u"Wk+ ;~[[+_LQdb?>Z>|E͸~2~ ZHs:@lKyp>4FVsQ`zU^HTԑM:69j毂|7-: ڡFƿW㵅Svd>`0U9v?0ߥg 5OLѠXQF9+:T8(Y̸EJu*;#%FߓRS8Š((e"W\SkER(j>M[G` +(((((?J~e0 +(ZN-kچPD +((('ީ5m ( +( +(WCPTL(((r}ꚡOSV(*&m ( +( +( +(?ӭKQ椯:(Q\uդEV 5:RֆsŠ(<(,+0jL,:@sKH(?J~+)A!(jMKԹXY}ꚣU ԕYQUfaEQfaERQEQE1TU+)ETRI֥ PjEP@QEQE8)#4Ҹ5Bzc(((CPT硨+:QEfEPEPT +}ꚶQEPQY6EVeQ@Q@Q@IQTs<@QE +(CԌk *&`VNnbSӗQjI썚+^ȿN>}rv+ʁp(-~VttTp{©*։s\;mP۶Z)kcEסi{QN [Qw¥6QE?Jjujd2JB$[Vi>ԄӚ{,{X4<麾A.x#7F~TW<<$^ȣ?RR<7zkQcqNV{Sx=TPN92V0J UWnHɩxez w89YR"R֚āJFhf +2iJZ]#,z׍jkUvmYעcZt Gwk(]5ҊGEEVhc4# Tl9-% r 56ڿ/NZI*׉x#'Sxoݭ6ϖEzVR2ZJu"u Ƞ ^fv ,?yC{ZVPd׉+ XG4HѦVV+AX,BTQ>o fzS}*@Cq]P(b0Rj*8<#颇7ڴ$pYNj;ù;E]RN*Q`xw:\-zA[Ҕb7 biQT`Š(((= ASQEQ@Q@N-DjZQE@QEgPQYQEQEQEH,t=#xOQ_(Fot?5_6?g_.?Gٝ:ŤhV0FzWݿUx[8fLImU=GU?\x1\,,In8$F9Z+˰tG[eh1ѡnqXVZRPoIV%Luk95WVq^瓍:59qj[a=i4:9ȫo|z?_ u/xxXf7'Oc~m,W;Rs*\nFprQWqUฎ ` 4!S{ -PDLBI=Lo]VZ&s9W?gًX~q9Hꤎ M%Lrxo2q2ˌ7qSz^0KV{Gc(2뒣X 1N+?'J|%wwMȎߓkw_Nҡ7ɂ1Ack#ÿk:\p6xo^BH_Jq䓇cQ? _P/۫/ËF?k[ofsֿAu{i\]gTN +?n7O~yM[,X0+AVmeݔ`E +|$owT N1+  ?|F~bq5 2A4|w߄:g<;j#lOإFdt Z0别-ǣ\kJTew_oGN sڸxß=s\-zK`?_A?nYԑw)F28=MeR+l߷> | +x6UNߘ׍x]?`ǖ]][$_.@u${wя7{᷂_|Fa[RS2ܓ\GF7K&YH ϥtbq*3tGK5=Gǿ/_zRZA>4[9fhċxk +kKԬZ}3ǡ5`GA+vaT?Fįٮ]VgQf۱}~'5ܿ{oO_ug$kuSP+^].y|\(B((ݨjI;Tu(((ZN-m ( +(m ( +( +( +(?X=EAڦ'_*X.Cmћ̴ep8c3_q_Ϗm("zWRX|lT3<;v8{ Ode MkfImYp/:{E{xg#j1N8>\ßkO\F@\,8e{96xoyG1xȅA߈zWG|aտ,\jJ Fٴ;:CޖkARGXfΫ&$r#G>O<ol8M2/Ŷ|wo?j?-o%nU'.}}1d12x6%$B2}@jeN=EFG $d/6 yoqm@GXw@zwoSJԗy":#xgQa,</*4ɹߊ:Q?7jQ},M2*?JO37P.q2M vl`p 䑢}JU[kʩը=ӋJ /TŮ#X/?o#7JOv&#u_J-τ?+7L$PSۧ[e)FN=YY=~G }~vӢ$q_GW MMMzl =SJk%USCP3vU.B5v%V&!!;x_G%aWBʜ/ï?ַ(I#xX*㴂= evhr+ӄg[ 8AɯT`M|,o38V׿_7vg$u@2#\%)Ս*xT^><]?dMt9َ1_]/|E|`|*[k}e_j +o1miv~ЗOD8'Lk 7t!tC! 35URmb:^ges-}]]\8ʌ_U2फ़*mٯ9PO''ֿIoo²Gm+z`!=+|U)ijȲ%ƹWӌхӗmRfreK \v ?W~Ė_GgY(_;፵y- Ew;sUcB69= #IՆW8N]rzkIl?n'u7x{WJc!c_?DZ<]FvӔl_'*1<؟ 'ke:ޝos ’`b8kT?a/^5ob"#2+Oꦻ3 +5㟉<.<1++ਿO#s<=-*Q<W@~+ĸ7ga9k96xٞqgjxQȘ^0G)ɦ~`}Q|YׯJm_]K*MzW~?P[?/1$5Q<̌X6܏JS*qeդc[O]cRyVʼn0J:+SS +d,dgYFCM5 + C*:oBE簝WtbNF z*N'v=e fD'gW[[\ '*H~ +_ tgKV貂m('Zj?⿋ 񇇴[QT02LkFXۡt}=S_~ͺ;HV` R#ҿL~ 5I|Qiy#7*Ah  G>\jcgQ.=ž"JXr0aLKv>2`?,xBե +j_*6гג@=%sxo٭7>3iײ.KmXF8nYJHpoԮE_m:a")^u[_}f-dkxt>5x_ 귂9>SA'Jj> |UZ1k". r +PÎ'G>VkQjp~by=G2Tt{C_Cqg o~ПQRtK BNzW;?<+VǒJ7s~ml-X ?d},}KUO㗩ϜQZcΆET;QY(NLvEV`QEQE=:ԵuI[` +(QYOshlQEAAEPEPEPX`ӳ7@hPO>Z6~vV.aO#*?~5:Gb0 mOhxk3-luھ2?_ +e?xh;UJq9NTa"#!G9 -jq2^),Ϝ1⿡98bh yTg*orJ&^(/ś +|YJTo =Uf?C_3|ZX?%>]C3𦐁-S~S? \韷C1&3򜎹9H[#;TQP8M:ǚq{9 =y^3c̖Q/uֻ*j#{UPvF"k6Oz<i ź 8:澢?~ǚp-e|lNJ|x?^kd# mi2[lh>q `לIOB~8xYTZZ<^B,>_k:42]` Ì:Z}?iVgqmfiSĀ_ y=nƫP+>| ow!tv{>P?OrtK:E +@|c㫿)5׈gS͍ҬFp}C2F1^3QxxF}P>ɟ<-IыUr`3%JnxJiR`xWFVVӿhO~Kݖyqй't8¿{{x-0xO$ƹgגw'- +uh븰'}S?߰!-ϮUqm\?pAOŵDQIʜ^ƿ {Ha[x" @k> +όkHݑC:tQU*YI- 8h^%Gr$m#es~|"u^I:=r2{Ix_GO͔VP.њ8rg>+0stR!m,M%V)k +']:Ox;[h]2GnƿtsR:,UAZ|09smo(ׅ-/ŭgq%1} ov~/Igk"a![@)Cp8~g fKהD? meЭcCg3Z"oN8Oԡ/bYPV֬M%b|6a!/ g^f6-7^)MJ.ſ/|B>:$p VŇIxν/Gj?X-hB;6 x,ޕ7zn’+6/ ~n"IrkWcY]O'߱muYv*qοC~ȞҭWJH|'ϵ~q:Q{-zȔrk; A(|@|5a.©=8Z \Ʈ?Rр{s~O#g/_?()#$!i~qE(#B0*JQܷdQE`QE +(g5(kچ*0j&J(((Hԕ^V(jQE#EQE +(QE+ (ȗ&ʊFhS];%E-!n"jP01KS(S>V#f`qnf~[]r &= #u1~Q.-E )j29;S2GJ{|( Yɣ83ieI Yᛯ +ELG=>i-S6mEU,y y$?O}OjV9nQiÂ# +F`'ue!g(SYeG[;N**qIɶC(a𰂕MY8~i]=_zI}ZV|T9⾸<|vk[ٵڀVba@9w濼٣Ƿ~cڮ>}c͏~'9LSٞNMxhϊu[+@#AnyV>3+ql=R@m=cs ;`H(Ća&1*gM62HLFKmF/l"V^Ak~= +g_ _zV5Xos?|M+º,z]9As޿_+Q~l3LQUCnX!+z NUf=ſp,>{V)PUؽIaouK_JZ.Yݎ~"~ӿ]ٛįj0`u;_f{??{/p +W7}ko!/gJZIT.r{Q y Np=_wu~kچ=op1+I;ҺaMsGc/MfG;#ccTĎ+ۮk  }/iW&r?/ʿJ%owݻ =5Ikz-̡ ÀI4z5V RgW.*9H#ie8UZbn~F?l='; }{^΍d%)s43Ej J>imFX%8vW7 $n7WO>#lU ^٣$FEҿO~~+תkT}8\bϏw?u{^+c_p +;g񯸼O | +sR 8RS^[8iPf<)r-O7? +4tukNvn!FTg_xN~36Gv>''&Ү9\'s"b~5ux#+(?m.>(i=~_@~'gQG`%gy?฿&!YrCo!vɰXP9VW⥀}N~5xtfk0r2WuY^?kNZ=!L\/糸\$J8j1y*{52qQ=*F$nU@R^>cQZ rA$ϜeAPj1j/)WjWiQOQE()I]%p+& +(PaHFF)i?, + "S.zɏٲ +*|-!E4rva XQNlQE\$(uQE (((dөQ<?e5'ύ| ~:V] w]A JzO_#?W.-6Tq!㞆*2̽m1[ +/ux4kW(B6n;tcZ|6vi 3å,A/SO_mZ?^'k oa +[C&=DͪҁG3:BD9myesi>;|!?mMsQD]azb0|u/m:'mENxB0q:MA(A?cyR!\s,' k We [+4tF(@5[i:lڅ. +HY|"ڳ_q+%5]]m"`3) +W#߶ĺmWKtRtWg?jyZUM["lx7"Ëώ>򠺄 Mr0z[ ҄h}?7].ϰC5:Γ6m_W-؀+[ ~76^>}t +WJ6Ugw/ +'|dL7y˓{da{+JRZ@n=y3#~lƷ[;Ÿr$Q|ᦏm23ZupF ؠs[JT{(g? &c%Vv $yH$o +s"~+eO0`Gđ+d= ~|cnÌ9qJP'dlh|Y#UAۃ¿i9~ɺr:~~'ӵufh3K1 q_Y/zW]KhN +L4=,o~Oſſ¨WBZ8s 9Wm/ ՜VdW_8x/čoŞ8K٬g3Jg:?f Hzg6׷,E9W5>?fKw۸nS/< +~We.J_n%/$^9[QoN n1\{;1jWrKȉ { +'?_?l/ek#Zi.J(Ċ?? V|MZMؒ0+l/~5[/Y$~fr#\sgGk :eeeOW +}8G₿?-4ںćʴML_#JK_h:7=r'xH&C(q\ZjL2NiU,=Y)F3%Ʊ)IJ6 + ~_/ Wkh"m89ȯ}m-'2\vȒ ƅvZ.Fu=Gd >׿E#UkV%,'2P'*=|")>LJ?g$2mI +?n;ƾ1~h[cX-'߄,k]\4HKcxTӕ.in#F$^CW˿eZ-NjoqI}xƓGh +3޿8cmΝfuwh~J{ +ՕZ90yzu4>>|A5` gsӁֿlm/K9Ie>ƿs5{oګE4+HllPIӳt]sfLF}5*RWl04xG]Zx\ůFy需3UnA>|M%!;ҿ'?/4ڛBo8d0N~%{UQRtomOֶ"X |P~ҞM ȹp y?J]}K+gW6):ʲ3arr0zv\xjEN][Kcൿ hOֿ=R68R2 #~0EgUa99Hkm ?[0Fk=h5YdHjr迭~`2b?it?7s8~kq couK&<oz&FYurёC_WŸ$WڝߋŮ$/bFᾁxsZvfF*1\ÖKٻ^24MT+YQŧ1/W20ߏLWwH cLcerq_6Fj PCp3s[}i?hiC~c_ _??Q Gw ;b?Sg_\|vS_"uܧ ~~̟ %Q[EmBX%*03W???k |aoCERՆPXzl{~eM&L;[?3z___> ״((3Ӏ5' 4Ԡբ q6go5:NӼi[30ڄ: b|ʮsS*ѣ5?ho ¿_Ie_D\1?/N ?n~ۿnu !7_}}:zWY~ǿٶs]L,~W9V&?g5{h&f%B=q? ^ +N +TagKGOzg|?izfyQ.Bk<ffoğy8%>|~8@[Mr*'k B?|U6<ǩbSZRQSS0).u;{wͶv,'^+ ?3GV|[$a<;׭Q^J}<VM r 2ʮ~q<42ILc8*3=kO=M:qtϻ n9_~5N\xgĐ-ťʕt`׎:_S>l @w_ŭ{BxmB6LGVC_TunK_ 587Q#ICw  ]Kv|7X`]<x_ MZ5i. IX,/U=N~k}Ng 7)/-$W8+?j!j/-kWki`N_М+|-Z[Bh5ZF2m'6zT?$e?Ⱦ!z9, +%P<c6v:Dt8ߎOnOጟ|e'~Y6=r:ח~O~u֗;[01׏tԩzN/sXf1nSd ~Կ_1) 2Gcz8>5O jKn#@rʀx[qOЌW=)J }?dŒI.ZW?62UF==ʘ;ȝ~He>'Ob;`QL_aoU<hRa2BI~*~+OFۧ +!|-B+5Q#b9\jyFԩogclۈ68CG|v,'s_ҷ?UqU2ևğ./Ҡi5Kf*Ut~~ %~ѿl?&[A-Ü s_ִF5D:Ou\H>g - 6|£rWoJ(v,҅*mSݟEi0l"PGvZpK]ir7/KEiȄS<5F,+B9>OSKAz9ͿI>¶ʩݞ}_Wz#Au!`Fqڿ/F -ccK?NEڬZlej[͆QlU(ţ? +!SiXYrD;@;#<{cxܗw9`OS_ ?ůxzSKkxf!}'7]S xO}!$`@b?1ʹ1w積 !4+{/miGch!jRN#8۞?Oƿrk_8tu=3ˉoѭPr _|MڟٰLEF-u4Q+g7৚1=6gKzO/u|E % pwH%>ӦU6_ԗI@4!cג9+{E~p .@}+l[Pmma30#ҿ:TѷG*Syw:.~teX۹Ǘ£?ZЭ2$[ 8_h2V}ёwW3|CC/F' ;/xN ?gWg$ W~sZkuG2,|lJ;k "fB7wt'ŏ,/>LDUv@r;ؗ;/3⟌ά"sYƬ(:R1,p|(w=|'_p {{t-aBϕOW_y~^M&ő^4 9>arMN[q3]⸼vڬz}j˵'AT:.ylvۏab|I-ž[i6A;6WI+ą +1QVeO:3(.xgOxU_ۿW>)|<]tG/2?z;'Mgnh֦%#G'!%5Zj o3 {c'֮ۚ.! bn>K]+k'=(t)lQE]QEL$'+QSi QRG4w~4šR@`hBGƾ=Ox,l#篠d˖ȸAdu#1!j7yv $K86k-UTuv隨auЧגH) +:W_kpt 2m6[x8etPOQߊ_<^4v[`e5.I-NyF|jpw_>5䬡W + 0xrdTm ?jy$t7քy04WVċ',p_>?c +$]q>BV1`Uv?u(Wi9_>' x2b9# +rvᜐÀ WC{~W,dAwXTݤTu*0)sAK^:pRycRGJ][ǗvᱨhԻڝ;cJRrsIKZ1޹-;!+on%$.ш ~Y|V>,|fK#<İ)9ׁ{9s3>Y;LܭINpsU~ܲgYy;?]o|'߈O,IJ#8+?MwwI+̀ЌZfJnOy |)2-+$"ab@bj?|^waX dw]#F.I7]IfcJ5IEj#A=͵y2,k,@C z&T?_uO +xOZ]נH1UC $saD+'uqR߄nX`.ß~M~kNѮ ɟT\5!'+a&|!᫤׵-epI$U8Lɱ[€{e|'O:< ㉎1g _W)J~zmmsL ,no7@NaekKsrN+])&}rkI#< vI^PR/6OLϖ8{/@ ##+++mcFӁ35Y<(>f1S ;H?k;+" ҿI=c|eM,M,iqPʿ# ]f +Nxieq&n`ς߳ /GX[O`O1AрP0z' >%|@Ɨ]ZD&W%' +ekxr/]ٲ[FE>_>|4x>l*nKJT%|r3RRgKre9Zu5>:.HQE޶7[Q] cQE *\\(HQE)A%vI%S+)7sXX(\|((((k w% dܱ=1?iZ''Sv(}i> D o]_m.ڬ]4}q?rcFDr__!O뵹3xI[ąR HMGWJY+NG\x :p[' M˩=ފxez 6j)]3|Fӟ fo 7~#]yH u_k2ԼGFf88Pi^a|%E$'א5Mjūih4+@*g9C ),-(ԿߌUyOnK>P`yWW3vFx |.y^r: 8M2KbHRs{5feTH,1aO{+&~G?I~?u.f-$cVeUBNc55_-/ž4q +_?m +]{iQ+۾Ppv{1(G;~l-ivJ&GZT2õV:dߵg'"8*Ny W׼=мߋfgdTNk>6r}1)t\Gh9hb?7b4V}AАa8bwq_opyB,ԎcˈfY9D?گu'{:Rr?Ɵ c]ncRK$OGO+>~w|YkuR^c,F.LjLMJBqME bW-lQE?fQÈQE2,]Z]>H_5 +)sHFTW2ye+ \ӥŮ~\V'AM?TxL\Oc>~%xf^-[+*׭ m ec's {[_>(i/mG8Q⹱$[8uҎ`osE׊>iIo-P¹޾Vki>o8 * +2kؾMgVh,5Y\11} M|*e%ows8 XHl:g5TNEZ.kc ?!F~2 )W +sa;j\1 ߈ϝfNI!8#>ӽ|(8m1y쭷G=kAlvPjX[Iv6';O*9' +F+/k)nȞmWY;&%8f;3wo +|s_6~ Gp,<̬r)5&77[Ri_vebFaf(h/j?KX. oxW  άIM-6B# 5SՇQ2,BraGOi*N8Ios"ܚ?KNg%MMْ ų|5>!@s[rKf"o&> PA e?Cҿ{>imp1_wi$_V1ws}ayUsԔw! l$~\OaurGߓW坢&#W'Ӣ?.{A nWMm7X9hBr@'?ʻso_|?(u I,SC_]RMXТk$\ ss\sa__?೟_]xvQkȅ:~`V?M ~ N Pc9inB_.f$"Zn_To0x^qz- ˴S^=cZ֩ c q^P=ɭjȐ*$ +#MikmzRTd-)k(?s61z3ҿ>?jS쭩 n:S%pO"dkg|$i/t:֍՞0Wu܌8q_垫 3W 73Z;]#-fjNg8wX{ū_[D3F7pt]{- T?b Y_ٴF`CaI->2j۹KMߓ ,Ufe'=7 +{Xum;XOgr~jz-SΞlp*ƽ4Sޝ[=Ϩ'6j^Ms.TLp{m +˯J'EXX*G3_ϟqQxG%ʂTsQ\WK|述o¨arۊs@'^vc?Szb1)E~Gމ;R!9,it>bkBJ(l0*((L(7L KP( s^Up?n_7xn.o† Qz x>M;^խ,$2#k+KC)FJPH/&z KF8צ@8"w|w}/xԡ0 ,-/U)UІR=շ8xI:Is?eŻ yhr2"rs_,x?FkD>YyFHÁێ+wOxS\) z?~7b𯈬dKN^G*o&wmZ ;9;zTQ&Oۇ7ݨsCsT&dEQ5&*)s !tOlF%؊:z +Cu߃;q>h+&c-gE +x_ &U5IQn܏q_ɧ7[NVÍdXiJdXWb,| _Vk|) ayB68 3XmK{AA#s1ėX%IG}[jz4ϲx ++_/|1M"m~l _?O/G:V.A8eցbp&~[Pc_ Z__@7(dYd8#xW}owҝ!cH +<.m|Ui)$]: t2 VTj5#?æ\PFY|Ӫ0k#?`_i6ٶ]$*|rG_u-TƓHK'''=R*.JqG.u8lmfyN +3ʾ T4×PM^Tl3~8_AO_"G5 Ѥ9!x11Rt +kxHP0yNs^f78>D8}9|"Ѵa o#m"B|3/ x7U:1; O֦b +*>!|\m9yYY+, '+(`M{g19te+M>j{-zXQEEhES((`QEs(T^25G + ?'|;w +xPCsʻ!#^$FA1~_s?&gur12lFcҢ?cEه#=Uq7g+gkf|S>3kyyoca?(Yb\2cYOŞ<ࣿ%㸾/0ýVS>ğova'mVW#|q9׎4-HԴ {WW1~ j:<8GEJʎ*uk{.[D[>[hϼQu WkM4,XK7NY,s oFS qE~?i{x2Ln̠qcW Gj:cM!ŬEo^pZiOIWo<,uo\zH6F:eoO߳WKt&v8P;o2>i؝1}+]j2?Uul9nd=zVe,Kwc +jlycҷ?IVꝍxҥ)J4tO;UG$=AAMsB&l7J ~W: !;+ +u +僱 Tږ K{ά}]z#&zlL#й9Ng.?sc_-6ѭwf/X(~N񼉤Bvc;W| ggSzy矯~mEO( +GS*o>|u 9dLL6.#Ҽls*NZqӏ?S\~˞7:Rl+Q?:,>ο5kk*o3&q|W +%mGᮃzq{9 zu(Ujy4s_2iϒ +(QX=(@QEtQEQEt (((()=|ྌev8*;d 0E_֞ [`6\q_ +h-do׿+Ixkַ-ob8+ M}^VS0?|)aG%kh7{kl3{?v:ۄHN +~U5ߵ|S6) +ּdyT\57UcC &:Ídn=3P>}ZW| k]o\(iɯ*GUO}NUG QIy_i~οo?hEL8ş3(SJ!ar3O7ǁ/Fy[\/4~ڞ5 +@_~rxuqnv3WO}A~ԞYfx!ޜ>Sg8vXi/g4!x-$>v7Q6x ŖxRCٻo' -gwPi;P$ͷ>)i^#fybTg0]ӭG'6Z?o& S"0s3oI? xno>Gq_kfo7w 27)(MqZK;001KEt#.QEjEPEPER`QE`JSE!8 +? VAb"TA"o巙kE|ff$~ly^7AxopGϦʿ`BP<uigF3\;RcsY2JtXeQF09k@ xi>_/_wKqNHJz*8~զL@ +zk t&BjKs~n( )8?s=M _Ro$ǟ٧÷_G~|I-ГHcoN8Ix];nxbpL_?^"&gPUfT#*.s-58hg ޶7 ً&/-em\W+_)սR$g>? eMsָ& me0$Ө,\\FuDs We+}vWc*8cu'" ⿄*DxxʟCuƲ5ٯ—.vؤdF+"jb(N&Qp9 ?*|^YirY+,v{ntͿ6uIy8jk);g2bL{X=k ~U^6F4Dhw='?z/y]|kVf!VMp.?iD01 |6}zWfثPsBq_lw!v1 +r3*p͇v]O>:v^v>5@y9#e3ߴg8.5I.td(Jj(O^ e Ba&د> Zx?>(-STnr6=2i>qĞ%49meW0v!1^*%}= pIN')u/~ dR]ep>ḷS,,`(ڧ vl~^gb}=, 22NP+S~ڜj7RHp;yvmhW2~ +6h#ڧ#J."{o&շ0vz_?ߟcƅ7Iy#r67 {_-' wG8=s,s).~? ;SПmҽe]DQ@,2A\Oٻ'x<# =In!8ӟl~ݶRbv'NJPzJk͟ Q P9s[ ,|F[zoM`ҶZ5<Ru*52H;Cb߶WSBu[EzU{Zjfxik/`meQ mϥx`G-g08/ߴG|(ƧrdPD`.vg;(WW<,ԼXYY4QNRv6?W|EOd~U=y⿭M٧GWbVٕ'1vֿ&7hֺ7CG$=+y֥?X_++OUNec~.43w1f +$q_:GMwI0n}GֿtjC?5imU+0 ÿlY$3k:C*##IԔZV? c)Ν}zV5jI*YQ=υi(j7qɪL0_ާxq &[v: ܨ'^ᯅvml|HUnz@]8aͥQ-Ah(Uσ׬߷od~?1yi D̐/>UVe]/NOa9 +4W#3Z=c_e(b#0;WÏC Dj/ÝKhM7ppzga?ιkGQ閳ܰd$vՁ5Ry#8y垨j# +uK6_n?0\r} ;YZY$~A ηeR/!R> +1v*shHFiQ_FEW38U+⎥!D ԒUshKoH#^v_7_Tx[%}/$r6`O5i"CC0XZnXhTW?!M ~O"(4!?&_@`p)HVhrw7vQEtQ@QQ)tE7 +(=(0(+ +( +( +( +( +( e#[_B'vc?Tp4oON+\~bpw_kXIr2Ap:g", xrEEeUBb'+WK6<0D6BA+K~^5<>E#s 98vpNg|\ya+kyT ='{-l w?ZP/S7kࣗWU=때^?"EtZܬ66 T<@@*Þ+y76 +!gӵWY|\_.I4Hͷwۃ~h?TڗN}Jkk 7+zip]KMj +o ~+Ay%iiܑEX_lhtOϋ.6\3ciT)۟2+ٻ/j |GR#bpl_]xGESwu!xobqi_ +u=Oٟ~ '4xzoQQױ#%YS#EP|w#/]kGoe+TA=??j/|YxkhŘ1JxO`Fg6;*J#Ec]A5IOĊ}vlf|gA5!JmǍPzBQC:Ru$`gO㏄Q?^ Kl3q'޿>h!$d1_ᆟ[K?lu%\_08:dA*G{5+BTdi~?ɪZl*JJִj>n7/^3)ucB77f2( +( +(΀(+(qZ |]_~ڿ dK]Dn"+v+3c+$kז;t(J>ǹ.XFK1{ӟk%2S0~_^>֮|/er'GǾ*{5c|?kENK#҆HO_i`|QjA+ټ+o᷍[ k6׈t~DֿŻKHt]jUT '5)z2]c9$ W\$g_7#|Uvy῀ztk+Q:zfB*QslmFقL٘VdnF~jψwk3YN%#U~ +?6>we-^+ }k +X7dII˨Wv῁ Ş!p?_J+[K;_i 3ֳKCT$)HMW2,e=?LWo20i=)*S J<[4JIubވ}?(OzG$2T9$)Úl`ʡԏCfvb0u(ԷJvQLjGsYj5~\ ?lk^D1t|cNRj(e+cA ?a+hW$! ۸~Cu +[tp +r= )T$d03+L(v +()=zMH *{_4$51_G_k0}I?|$aԾ]7j%A+7OO30*t$Eh5Ҽ9H;7ԍWƯ.cشچl@ޤQ|>xLt=F `&6 wN#,/~#`[vlF{q^hsZ:q?ht$';i+sdɼGcdH?cGgős|I<&~՟?ilkK*`JUc?{Us7uehäx־BRjfyocqǽ+.}|w4c`ViG|w?f{~y[ڼv͑TklTja$O؏Ip;drO5wj.md?JcGو6{gq_e^Ffqkc TĥVsO pzҸ.t_.yF#|gπ +s$x;!}{yXis6x .Q;ٯίrbCg 򁞤&O8VZ*K,:95FEߗC h/+CVg‘[{pᝣ t=k⇁Km;{\ V Y cy`O_{x±beaf8|?+ +4S.rp8[Ш=zMwX~V)\yn-1xi#Wu0ѳq>?߲X|>ռ/iy1(vx185c+wWm;kBZ:><|3;AX_;xM>]?c=y>5|&Դ%sU, `^E~*v|+¯" rЈ`G 7zϧJ^W%Wm*޻ڈf~>W0=kա]~ŸR1tU6Fz@"w FT;R?mYbbϡIW;^/x_Oq"]*C9j4=~ϙ0ю _dR+7#4pBܸpwSH??~k7Ȱ0őG`k(ď|*uxfMPO#' SrH'Zi_sKkDr,|15G㿌_$~&z\9]@TL.1ҿW|׼dq0ʣcVN_mtŜ#oᏧkwߏ|[('5;kSo";GCuM'@Ԝ2.R7`x% %k"J!@]©a(;;m\f5!;C0~{beyVQ<]In|ykfh +(㉊ Hc5|~7Z,Dr s~ב(T,;tU>6ŎEBOH࿳Mm"ʿB>,nۏnV)H] d :s__H3m?g%ZQn'bKH%A8ں1jR;wh*6џӜ~ m>+t81sV (ɒ$r-߶/@,|ML\̞fvK}@WR~<-ot 2۱ 3W,1\9f%Z>mf9c1,LH#r>Jfl۽0U~Zib3u{gm{W+BvV)%ȧNՔQ1~?uSҺal+( =G{ cni;,}~Q_;?eX$u!aR`+rT|m?_cu|g'r]YNXFqfQkd|+|4 ޯӂrx2bo"߄ ⶫ% n@OZ?,.7k=oźyd?M swǽ[SZڇZe@Oayⷍf1#~=+_^ Ҽ+O 0[U>VNnrfXRl(c/p+p)K`?/E8 D7 ;ˉn\Aaӛ + Obmk:p6?Zm?ig2 N:{W-B> i(ڟ{ i:`G6Wz״_P_ڞe{-Zl:HFp>šέ,7mf㸫!FQ#۵rtۻdG/д-HUϩbsiP]ɯ7'"T}>hGi[iLLNT1Mlc^USrIk8X8*Xcrk o"; ~Pn5eGqx7Bm0>9 ̳ٔ}F=>> '6[yx\rGjG +e/'=7pַcw+l$|;2{ V~ZOПl)\PpK/7GgW 2>9zO5{!@Cc~yAMbXSռu OxώERkۇ#b0{W\:cn_l2x3`=^6gܱGAҟֿJigy់t=#V,1%t/KxmXјzq&]Xψ]Ǔ$S -ëx#_B0Ѻy?1WC\q?[UˬeTgL^=MxT8W^r%*px^5ߧ|F2BIiiRoAƭ8#X|AqɌJxtO~i-.8ֿ[6sqkwX'ڻ-7 .{'R qIt'ȝN8A6:k~~8|@;\4oϼv_Q9hfggWS.ӴnrG_O!,kg49#m +;ghjrÑw'J} v +QEP(vL~6EEW :(&Ma?2(d*{j|֛񶫧Zb1(RYx}kpnn%Ϙ`xC_-~'٪6#-G`v㏭:|0ncۜ]F3x g)ip@qcKkCZG*8bdk߉_w^Z8P=*sƾ1O0OVQG<ǞR[S/9Ǡ5>? [SZZ7⍂MiyNjC 䢏&"9n%Fr]Yyc_#| * :mu\3Ҿ +s_?瑍i5ceJºcLYNY?_ǵg~\Wu/xCJŠۇJuY6YNA,9vRN5<= yks~؀Zo m !_DijZvyG~;zF+Eh?goI{5r0i\m2_߶}C (eqMC|𗊴ox~ĺ P1kDM?3tp7 n8KZf?GPc1G;GȿFtOP( =g5_"|Q#up2IʯClIT_ +?$?7KHOZYiCFh,4kbDS5I%? +KSБ]N-wR~ůYd?0&eK{ǽS Iݟ\`~|>ѾO_X)X/cm'._E~Vt${y;l}_BEWG;ܧ+s{ZXdCve"C(* ucX۩L. ן- xQUkAx +?_SCaȳkSI(Fz +w:ڗi +rٷf br;>9k?g; BJsf4O,v?>J/t$ϥOEeZ}#-{8n2ֹ]C$ahkO@$7~רW''Z.M_cAm/ȗNL*l(ru,fh)8Qy8zWwoCkK7[`c$d8s wd_a|b Rx,`q޹M\;cUP^~x+^ }=vcBkUjk TkA$|Jrrc0y5~w⟃o|Dבr2TGҽJ%Q7tiOaq6&F*C l{s|&t(WIJi +tC`3cs\-{(b_vgЯ? b5J} ~gUS 0hR[sUGo~|a'^ !. $̀㞕K IJ̺>WqylP9-]%Gޔ;=sK|}:kV۠HݽFM'?cV 5Ĭ>`"l|X-ծY(9?~inUhfIgIYTDџU9&1UˡvgjO?goZ㔢;/?ROڛ݋LK6RLq_2>< [W$#稯?gO&G_#Ŷ0/y@`cҸ"ڊN7knس xV@ 0`iA1P8qR`o <؇)3ÿ<+^Y|J𕼲1DdBa=_1hs5ܺx) +1݁W}߀? >%O:1]@N+Ꮎ&G/ǙGeUj 5CM֗nf{e%Z?쟩e2o,YBUl\Ʒ va-R8ȧ|9d4 +-`A\¯/*8- i?m&vO:sç]N?' +A=yW?L~k|' <=m: ("JOMY!m?o/_O܍N ,z4W_Ӧ/6HBW?Ïh:ĺE/*sWCx5tʇf}"ֈx$<K-O$8eziUm`z_fiv XD"Tps_ÿhޙ ijʨ1>?M7x|0kin 㷌`"Cf/o|J  p<]klj7,ahr3D0209ma~S(SI'ֿsQVy="G#=+hP8ǥ|Z1.SG$>b8Ͼ*R8{7dt-D +~hۿ̈́>$3)_9X~pEt߲'wJTMCz9K)| +%O=РR18]Ԫ6Ɏs.H;;Ǟ(4 (`QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEkRkr(QE?@)TeM4ԩj 6:aWPȌu @aQ^EsԤ*qwGPǁ̠KNp v濙:oiϖo`$ReAC:5 8g?Q\(Vas5i?_na4ۨX(JzO;=5U]E;Z|-=Bi +gWE 0.ؔ a@CդmW7؉ 9>~˺{Tg$ +cDqqN Ҕz0=|D;B9Z+`QE`IKE0mSKE+ :RE0 +(vzZ)QE&-/]((#> /XObject << /I0 10 0 R +/I1 12 0 R >> >> endobj -319 0 obj -<> endobj -320 0 obj -<> endobj -321 0 obj -<> endobj -322 0 obj -<> endobj -323 0 obj -<> endobj 324 0 obj -<> endobj +<> endobj 325 0 obj -<> endobj +<> endobj 326 0 obj -<> endobj +<> endobj 327 0 obj -<> endobj +<> endobj 328 0 obj -<> endobj +<> endobj 329 0 obj -<> endobj +<> endobj 330 0 obj -<> endobj +<> endobj 331 0 obj -<> endobj +<> endobj 332 0 obj -<> endobj +<> endobj 333 0 obj -<> endobj +<> endobj 334 0 obj -<> endobj +<> endobj 335 0 obj -<> endobj +<> endobj 336 0 obj -<> endobj +<> endobj 337 0 obj -<> endobj +<> endobj 338 0 obj -<> endobj +<> endobj 339 0 obj -<> endobj +<> endobj 340 0 obj -<> endobj +<> endobj 341 0 obj -<> endobj +<> endobj 342 0 obj -<> endobj +<> endobj 343 0 obj -<> endobj +<> endobj 344 0 obj -<> endobj +<> endobj 345 0 obj -<> endobj +<> endobj 346 0 obj -<> endobj +<> endobj 347 0 obj -<> endobj +<> endobj 348 0 obj -<> endobj +<> endobj 349 0 obj -<> endobj +<> endobj 350 0 obj -<> endobj +<> endobj 351 0 obj -<> endobj +<> endobj 352 0 obj -<> endobj +<> endobj 353 0 obj -<> endobj +<> endobj 354 0 obj -<> endobj +<> endobj 355 0 obj -<> endobj +<> endobj 356 0 obj -<> endobj +<> endobj 357 0 obj -<> endobj +<> endobj 358 0 obj -<> endobj +<> endobj 359 0 obj -<> endobj +<> endobj 360 0 obj -<> endobj +<> endobj 361 0 obj -<> endobj +<> endobj 362 0 obj -<> endobj +<> endobj 363 0 obj -<> endobj +<> endobj 364 0 obj -<> endobj +<> endobj 365 0 obj -<> endobj +<> endobj 366 0 obj -<> endobj +<> endobj 367 0 obj -<> endobj +<> endobj 368 0 obj -<> endobj +<> endobj 369 0 obj -<> endobj +<> endobj 370 0 obj -<> endobj +<> endobj 371 0 obj -<> endobj +<> endobj 372 0 obj -<> endobj +<> endobj 373 0 obj -<> endobj +<> endobj 374 0 obj -<> endobj +<> endobj 375 0 obj -<> endobj +<> endobj 376 0 obj -<> endobj +<> endobj 377 0 obj -<> endobj +<> endobj 378 0 obj -<> endobj +<> endobj 379 0 obj -<> endobj +<> endobj 380 0 obj -<> endobj +<> endobj 381 0 obj -<> endobj +<> endobj 382 0 obj -<> endobj +<> endobj 383 0 obj -<> endobj +<> endobj 384 0 obj -<> endobj +<> endobj 385 0 obj -<> endobj +<> endobj 386 0 obj -<> endobj +<> endobj 387 0 obj -<> endobj +<> endobj 388 0 obj -<> endobj +<> endobj 389 0 obj -<> endobj +<> endobj 390 0 obj -<> endobj +<> endobj 391 0 obj -<> endobj +<> endobj 392 0 obj -<> endobj +<> endobj 393 0 obj -<> endobj +<> endobj 394 0 obj -<> endobj +<> endobj 395 0 obj -<> endobj +<> endobj 396 0 obj -<> endobj +<> endobj 397 0 obj -<> endobj +<> endobj 398 0 obj -<> endobj +<> endobj 399 0 obj -<> endobj +<> endobj 400 0 obj -<> endobj +<> endobj 401 0 obj -<> endobj +<> endobj 402 0 obj -<> endobj +<> endobj 403 0 obj -<> endobj +<> endobj 404 0 obj -<> endobj +<> endobj 405 0 obj -<> endobj +<> endobj 406 0 obj -<> endobj +<> endobj 407 0 obj -<> endobj +<> endobj 408 0 obj -<> endobj +<> endobj 409 0 obj -<> endobj +<> endobj 410 0 obj -<> endobj +<> endobj 411 0 obj -<> endobj +<> endobj 412 0 obj -<> endobj +<> endobj 413 0 obj -<> endobj +<> endobj 414 0 obj -<> endobj +<> endobj 415 0 obj -<> endobj +<> endobj 416 0 obj -<> endobj +<> endobj 417 0 obj -<> endobj +<> endobj 418 0 obj -<> endobj +<> endobj 419 0 obj -<> endobj +<> endobj 420 0 obj -<> endobj +<> endobj 421 0 obj -<> endobj +<> endobj 422 0 obj -<> endobj +<> endobj 423 0 obj -<> endobj +<> endobj 424 0 obj -<> endobj +<> endobj 425 0 obj -<> endobj +<> endobj 426 0 obj -<> endobj +<> endobj 427 0 obj -<> endobj +<> endobj 428 0 obj -<> endobj +<> endobj 429 0 obj -<> endobj +<> endobj 430 0 obj -<> endobj +<> endobj 431 0 obj -<> endobj +<> endobj 432 0 obj -<> endobj +<> endobj 433 0 obj -<> endobj +<> endobj 434 0 obj -<> endobj +<> endobj 435 0 obj -<> endobj +<> endobj 436 0 obj -<> endobj +<> endobj 437 0 obj -<> endobj +<> endobj 438 0 obj -<> endobj +<> endobj 439 0 obj -<> endobj +<> endobj 440 0 obj -<> endobj +<> endobj 441 0 obj -<> endobj +<> endobj 442 0 obj -<> endobj +<> endobj 443 0 obj -<> endobj +<> endobj 444 0 obj -<> endobj +<> endobj 445 0 obj -<> endobj +<> endobj 446 0 obj -<> endobj +<> endobj 447 0 obj -<> endobj +<> endobj 448 0 obj -<> endobj +<> endobj 449 0 obj -<> endobj +<> endobj 450 0 obj -<> endobj +<> endobj 451 0 obj -<> endobj +<> endobj 452 0 obj -<> endobj +<> endobj 453 0 obj -<> endobj +<> endobj 454 0 obj -<> endobj +<> endobj 455 0 obj -<> endobj +<> endobj 456 0 obj -<> endobj +<> endobj 457 0 obj -<> endobj +<> endobj 458 0 obj -<> endobj +<> endobj 459 0 obj -<> endobj +<> endobj 460 0 obj -<> endobj +<> endobj 461 0 obj -<> endobj +<> endobj 462 0 obj -<> endobj +<> endobj 463 0 obj -<> endobj +<> endobj 464 0 obj -<> endobj +<> endobj 465 0 obj -<> endobj +<> endobj 466 0 obj -<> endobj +<> endobj 467 0 obj -<> endobj +<> endobj 468 0 obj -<> endobj +<> endobj 469 0 obj -<> endobj +<> endobj 470 0 obj -<> endobj +<> endobj 471 0 obj -<> endobj +<> endobj 472 0 obj -<> endobj +<> endobj 473 0 obj -<> endobj +<> endobj 474 0 obj -<> endobj +<> endobj 475 0 obj -<> endobj +<> endobj 476 0 obj -<> endobj +<> endobj 477 0 obj -<> endobj +<> endobj 478 0 obj -<> endobj +<> endobj 479 0 obj -<> endobj +<> endobj 480 0 obj -<> endobj +<> endobj 481 0 obj -<> endobj +<> endobj 482 0 obj -<> endobj +<> endobj 483 0 obj -<> endobj +<> endobj 484 0 obj -<> endobj +<> endobj 485 0 obj -<> endobj +<> endobj 486 0 obj -<> endobj +<> endobj 487 0 obj -<> endobj +<> endobj 488 0 obj -<> endobj +<> endobj 489 0 obj -<> endobj +<> endobj 490 0 obj -<> endobj +<> endobj 491 0 obj -<> endobj +<> endobj 492 0 obj -<> endobj +<> endobj 493 0 obj -<> endobj +<> endobj 494 0 obj -<> endobj +<> endobj 495 0 obj -<> endobj +<> endobj 496 0 obj -<> endobj +<> endobj 497 0 obj -<> endobj +<> endobj 498 0 obj -<> endobj +<> endobj 499 0 obj -<> endobj +<> endobj 500 0 obj -<> endobj +<> endobj 501 0 obj -<> endobj +<> endobj 502 0 obj -<> endobj +<> endobj 503 0 obj -<> endobj +<> endobj 504 0 obj -<> endobj +<> endobj 505 0 obj -<> endobj +<> endobj 506 0 obj -<> endobj +<> endobj 507 0 obj -<> endobj +<> endobj 508 0 obj -<> endobj +<> endobj 509 0 obj -<> endobj +<> endobj 510 0 obj -<> endobj +<> endobj 511 0 obj -<> endobj +<> endobj 512 0 obj -<> endobj +<> endobj 513 0 obj -<> endobj +<> endobj 514 0 obj -<> endobj +<> endobj 515 0 obj -<> endobj +<> endobj 516 0 obj -<> endobj +<> endobj 517 0 obj -<> endobj +<> endobj 518 0 obj -<> endobj +<> endobj 519 0 obj -<> endobj +<> endobj 520 0 obj -<> endobj +<> endobj 521 0 obj -<> endobj +<> endobj 522 0 obj -<> endobj +<> endobj 523 0 obj -<> endobj +<> endobj 524 0 obj -<> endobj +<> endobj 525 0 obj -<> endobj +<> endobj 526 0 obj -<> endobj +<> endobj 527 0 obj -<> endobj +<> endobj 528 0 obj -<> endobj +<> endobj 529 0 obj -<> endobj +<> endobj 530 0 obj -<> endobj +<> endobj 531 0 obj -<> endobj +<> endobj 532 0 obj -<> endobj +<> endobj 533 0 obj -<> endobj +<> endobj 534 0 obj -<> endobj +<> endobj 535 0 obj -<> endobj +<> endobj 536 0 obj -<> endobj +<> endobj 537 0 obj -<> endobj +<> endobj 538 0 obj -<> endobj +<> endobj 539 0 obj -<> endobj +<> endobj 540 0 obj -<> endobj +<> endobj 541 0 obj -<> endobj +<> endobj 542 0 obj -<> endobj +<> endobj 543 0 obj -<> endobj +<> endobj 544 0 obj -<> endobj +<> endobj 545 0 obj -<> endobj +<> endobj 546 0 obj -<> endobj +<> endobj 547 0 obj -<> endobj +<> endobj 548 0 obj -<> endobj +<> endobj 549 0 obj -<> endobj +<> endobj 550 0 obj -<> endobj +<> endobj 551 0 obj -<> endobj +<> endobj 552 0 obj -<> endobj +<> endobj 553 0 obj -<> endobj +<> endobj 554 0 obj -<> endobj +<> endobj 555 0 obj -<> endobj +<> endobj 556 0 obj -<> endobj +<> endobj 557 0 obj -<> endobj +<> endobj 558 0 obj -<> endobj +<> endobj 559 0 obj -<> endobj +<> endobj 560 0 obj -<> endobj +<> endobj 561 0 obj -<> endobj +<> endobj 562 0 obj -<> endobj +<> endobj 563 0 obj -<> endobj +<> endobj 564 0 obj -<> endobj +<> endobj 565 0 obj -<> endobj +<> endobj 566 0 obj -<> endobj +<> endobj 567 0 obj -<> endobj +<> endobj 568 0 obj -<> endobj +<> endobj 569 0 obj -<> endobj +<> endobj 570 0 obj -<> endobj +<> endobj 571 0 obj -<> endobj +<> endobj 572 0 obj -<> endobj +<> endobj 573 0 obj -<> endobj +<> endobj 574 0 obj -<> endobj +<> endobj 575 0 obj -<> endobj +<> endobj 576 0 obj -<> endobj +<> endobj 577 0 obj -<> endobj +<> endobj 578 0 obj -<> endobj +<> endobj 579 0 obj -<> endobj +<> endobj 580 0 obj -<> endobj +<> endobj 581 0 obj -<> endobj +<> endobj 582 0 obj -<> endobj +<> endobj 583 0 obj -<> endobj +<> endobj 584 0 obj -<> endobj +<> endobj 585 0 obj -<> endobj +<> endobj 586 0 obj -<> endobj +<> endobj 587 0 obj -<> endobj +<> endobj 588 0 obj -<> endobj +<> endobj 589 0 obj -<> endobj +<> endobj 590 0 obj -<> endobj +<> endobj 591 0 obj -<> endobj +<> endobj 592 0 obj -<> endobj +<> endobj 593 0 obj -<> endobj +<> endobj 594 0 obj -<> endobj +<> endobj 595 0 obj -<> endobj +<> endobj 596 0 obj -<> endobj +<> endobj 597 0 obj -<> endobj +<> endobj 598 0 obj -<> endobj +<> endobj 599 0 obj -<> endobj +<> endobj 600 0 obj -<> endobj +<> endobj 601 0 obj -<> endobj +<> endobj 602 0 obj -<> endobj +<> endobj 603 0 obj -<> endobj +<> endobj 604 0 obj -<> endobj +<> endobj 605 0 obj -<> endobj +<> endobj 606 0 obj -<> endobj +<> endobj 607 0 obj -<> endobj +<> endobj 608 0 obj -<> endobj +<> endobj 609 0 obj -<> endobj +<> endobj 610 0 obj -<> endobj +<> endobj 611 0 obj -<> endobj +<> endobj 612 0 obj -<> endobj +<> endobj 613 0 obj -<> endobj +<> endobj 614 0 obj -<> endobj +<> endobj 615 0 obj -<> endobj +<> endobj 616 0 obj -<> endobj +<> endobj 617 0 obj -<> endobj +<> endobj 618 0 obj -<> endobj +<> endobj 619 0 obj -<> endobj +<> endobj 620 0 obj -<> endobj +<> endobj 621 0 obj -<> endobj +<> endobj 622 0 obj -<> endobj +<> endobj 623 0 obj -<> endobj +<> endobj 624 0 obj -<> endobj +<> endobj 625 0 obj -<> endobj +<> endobj 626 0 obj -<> endobj +<> endobj 627 0 obj -<> endobj +<> endobj 628 0 obj -<> endobj +<> endobj 629 0 obj -<> endobj +<> endobj 630 0 obj -<> endobj +<> endobj 631 0 obj -<> endobj +<> endobj 632 0 obj -<> endobj +<> endobj 633 0 obj -<> endobj +<> endobj 634 0 obj -<> endobj +<> endobj 635 0 obj -<> endobj +<> endobj 636 0 obj -<> endobj +<> endobj 637 0 obj -<> endobj +<> endobj 638 0 obj -<> endobj +<> endobj 639 0 obj -<> endobj +<> endobj 640 0 obj -<> endobj +<> endobj 641 0 obj -<> endobj +<> endobj 642 0 obj -<> endobj +<> endobj 643 0 obj -<> endobj +<> endobj 644 0 obj -<> endobj +<> endobj 645 0 obj -<> endobj +<> endobj 646 0 obj -<> endobj +<> endobj 647 0 obj -<> endobj +<> endobj 648 0 obj -<> endobj +<> endobj 649 0 obj -<> endobj +<> endobj 650 0 obj -<> endobj +<> endobj 651 0 obj -<> endobj +<> endobj 652 0 obj -<> endobj +<> endobj 653 0 obj -<> endobj +<> endobj 654 0 obj -<> endobj +<> endobj 655 0 obj -<> endobj +<> endobj 656 0 obj -<> endobj +<> endobj 657 0 obj -<> endobj +<> endobj 658 0 obj -<> endobj +<> endobj 659 0 obj -<> endobj +<> endobj 660 0 obj -<> endobj +<> endobj 661 0 obj -<> endobj +<> endobj 662 0 obj -<> endobj +<> endobj 663 0 obj -<> endobj +<> endobj 664 0 obj -<> endobj +<> endobj 665 0 obj -<> endobj +<> endobj 666 0 obj -<> endobj +<> endobj 667 0 obj -<> endobj +<> endobj 668 0 obj -<> endobj +<> endobj 669 0 obj -<> endobj +<> endobj 670 0 obj -<> endobj +<> endobj 671 0 obj -<> endobj +<> endobj 672 0 obj -<> endobj +<> endobj 673 0 obj -<> endobj +<> endobj 674 0 obj -<> endobj +<> endobj 675 0 obj -<> endobj +<> endobj 676 0 obj -<> endobj +<> endobj 677 0 obj -<> endobj +<> endobj 678 0 obj -<> endobj +<> endobj 679 0 obj -<> endobj +<> endobj 680 0 obj -<> endobj +<> endobj 681 0 obj -<> endobj +<> endobj 682 0 obj -<> endobj +<> endobj 683 0 obj -<> endobj +<> endobj 684 0 obj -<> endobj +<> endobj 685 0 obj -<> endobj +<> endobj 686 0 obj -<> endobj +<> endobj 687 0 obj -<> endobj +<> endobj 688 0 obj -<> endobj +<> endobj 689 0 obj -<> endobj +<> endobj 690 0 obj -<> endobj +<> endobj 691 0 obj -<> endobj +<> endobj 692 0 obj -<> endobj +<> endobj 693 0 obj -<> endobj +<> endobj 694 0 obj -<> endobj +<> endobj 695 0 obj -<> endobj +<> endobj 696 0 obj -<> endobj +<> endobj 697 0 obj -<> endobj +<> endobj 698 0 obj -<> endobj +<> endobj 699 0 obj -<> endobj +<> endobj 700 0 obj -<> endobj +<> endobj 701 0 obj -<> endobj +<> endobj 702 0 obj -<> endobj +<> endobj 703 0 obj -<> endobj +<> endobj 704 0 obj -<> endobj +<> endobj 705 0 obj -<> endobj +<> endobj 706 0 obj -<> endobj +<> endobj 707 0 obj -<> endobj +<> endobj 708 0 obj -<> endobj +<> endobj 709 0 obj -<> endobj +<> endobj 710 0 obj -<> endobj +<> endobj 711 0 obj -<> endobj +<> endobj 712 0 obj -<> endobj +<> endobj 713 0 obj -<> endobj +<> endobj 714 0 obj -<> endobj +<> endobj 715 0 obj -<> endobj +<> endobj 716 0 obj -<> endobj +<> endobj 717 0 obj -<> endobj +<> endobj 718 0 obj -<> endobj +<> endobj 719 0 obj -<> endobj +<> endobj 720 0 obj -<> endobj +<> endobj 721 0 obj -<> endobj +<> endobj 722 0 obj -<> endobj +<> endobj 723 0 obj -<> endobj +<> endobj 724 0 obj -<> endobj +<> endobj 725 0 obj -<> endobj +<> endobj 726 0 obj -<> endobj +<> endobj 727 0 obj -<> endobj +<> endobj 728 0 obj -<> endobj +<> endobj 729 0 obj -<> endobj +<> endobj 730 0 obj -<> endobj +<> endobj 731 0 obj -<> endobj +<> endobj 732 0 obj -<> endobj +<> endobj 733 0 obj -<> endobj +<> endobj 734 0 obj -<> endobj +<> endobj 735 0 obj -<> endobj +<> endobj 736 0 obj -<> endobj +<> endobj 737 0 obj -<> endobj +<> endobj 738 0 obj -<> endobj +<> endobj 739 0 obj -<> endobj +<> endobj 740 0 obj -<> endobj +<> endobj 741 0 obj -<> endobj +<> endobj 742 0 obj -<> endobj +<> endobj 743 0 obj -<> endobj +<> endobj 744 0 obj -<> endobj +<> endobj 745 0 obj -<> endobj +<> endobj 746 0 obj -<> endobj +<> endobj 747 0 obj -<> endobj +<> endobj 748 0 obj -<> endobj +<> endobj 749 0 obj -<> endobj +<> endobj 750 0 obj -<> endobj +<> endobj 751 0 obj -<> endobj +<> endobj 752 0 obj -<> endobj +<> endobj 753 0 obj -<> endobj +<> endobj 754 0 obj -<> endobj +<> endobj 755 0 obj -<> endobj +<> endobj 756 0 obj -<> endobj +<> endobj 757 0 obj -<> endobj +<> endobj 758 0 obj -<> endobj +<> endobj 759 0 obj -<> endobj +<> endobj 760 0 obj -<> endobj +<> endobj 761 0 obj -<> endobj +<> endobj 762 0 obj -<> endobj +<> endobj 763 0 obj -<> endobj +<> endobj 764 0 obj -<> endobj +<> endobj 765 0 obj -<> endobj +<> endobj 766 0 obj -<> endobj +<> endobj 767 0 obj -<> endobj +<> endobj 768 0 obj -<> endobj +<> endobj 769 0 obj -<> endobj +<> endobj 770 0 obj -<> endobj +<> endobj 771 0 obj -<> endobj +<> endobj 772 0 obj -<> endobj +<> endobj 773 0 obj -<> endobj +<> endobj 774 0 obj -<> endobj +<> endobj 775 0 obj -<> endobj +<> endobj 776 0 obj -<> endobj +<> endobj 777 0 obj -<> endobj +<> endobj 778 0 obj -<> endobj +<> endobj 779 0 obj -<> endobj +<> endobj 780 0 obj -<> endobj +<> endobj 781 0 obj -<> endobj +<> endobj 782 0 obj -<> endobj +<> endobj 783 0 obj -<> endobj +<> endobj 784 0 obj -<> endobj +<> endobj 785 0 obj -<> endobj +<> endobj 786 0 obj -<> endobj +<> endobj 787 0 obj -<> endobj +<> endobj 788 0 obj -<> endobj +<> endobj 789 0 obj -<> endobj +<> endobj 790 0 obj -<> endobj +<> endobj 791 0 obj -<> endobj +<> endobj 792 0 obj -<> endobj +<> endobj 793 0 obj -<> endobj +<> endobj 794 0 obj -<> endobj +<> endobj 795 0 obj -<> endobj +<> endobj 796 0 obj -<> endobj +<> endobj 797 0 obj -<> endobj +<> endobj 798 0 obj -<> endobj +<> endobj 799 0 obj -<> endobj +<> endobj 800 0 obj -<> endobj +<> endobj 801 0 obj -<> endobj +<> endobj 802 0 obj -<> endobj +<> endobj 803 0 obj -<> endobj +<> endobj 804 0 obj -<> endobj +<> endobj 805 0 obj -<> endobj +<> endobj 806 0 obj -<> endobj +<> endobj 807 0 obj -<> endobj +<> endobj 808 0 obj -<> endobj +<> endobj 809 0 obj -<> endobj +<> endobj 810 0 obj -<> endobj +<> endobj 811 0 obj -<> endobj +<> endobj 812 0 obj -<> endobj +<> endobj 813 0 obj -<> endobj +<> endobj 814 0 obj -<> endobj +<> endobj 815 0 obj -<> endobj +<> endobj 816 0 obj -<> endobj +<> endobj 817 0 obj -<> endobj +<> endobj 818 0 obj -<> endobj +<> endobj 819 0 obj -<> endobj +<> endobj 820 0 obj -<> endobj +<> endobj 821 0 obj -<> endobj +<> endobj 822 0 obj -<> endobj +<> endobj 823 0 obj -<> endobj +<> endobj 824 0 obj -<> endobj +<> endobj 825 0 obj -<> endobj +<> endobj 826 0 obj -<> endobj +<> endobj 827 0 obj -<> endobj +<> endobj 828 0 obj -<> endobj +<> endobj 829 0 obj -<> endobj +<> endobj 830 0 obj -<> endobj +<> endobj 831 0 obj -<> endobj +<> endobj 832 0 obj -<> endobj +<> endobj 833 0 obj -<> endobj +<> endobj 834 0 obj -<> endobj +<> endobj 835 0 obj -<> endobj +<> endobj 836 0 obj -<> endobj +<> endobj 837 0 obj -<> endobj +<> endobj 838 0 obj -<> endobj +<> endobj 839 0 obj -<> endobj +<> endobj 840 0 obj -<> endobj +<> endobj 841 0 obj -<> endobj +<> endobj 842 0 obj -<> endobj +<> endobj 843 0 obj -<> endobj +<> endobj 844 0 obj -<> endobj +<> endobj 845 0 obj -<> endobj +<> endobj 846 0 obj -<> endobj +<> endobj 847 0 obj -<> endobj +<> endobj 848 0 obj -<> endobj +<> endobj 849 0 obj -<> endobj +<> endobj 850 0 obj -<> endobj +<> endobj 851 0 obj -<> endobj +<> endobj 852 0 obj -<> endobj +<> endobj 853 0 obj -<> endobj +<> endobj 854 0 obj -<> endobj +<> endobj 855 0 obj -<> endobj +<> endobj 856 0 obj -<> endobj +<> endobj 857 0 obj -<> endobj +<> endobj 858 0 obj -<> endobj +<> endobj 859 0 obj -<> endobj +<> endobj 860 0 obj +<> endobj +861 0 obj +<> endobj +862 0 obj +<> endobj +863 0 obj +<> endobj +864 0 obj +<> endobj +865 0 obj +<> endobj +866 0 obj +<> endobj +867 0 obj +<> endobj +868 0 obj +<> endobj +869 0 obj +<> endobj +870 0 obj +<> endobj +871 0 obj +<> endobj +872 0 obj +<> endobj +873 0 obj <> endobj -12 0 obj -<< -/Type /Outlines -/First 13 0 R -/Last 15 0 R -/Count 306 ->> -endobj - 13 0 obj << -/Title (Pages) -/Parent 12 0 R -/Next 15 0 R +/Type /Outlines /First 14 0 R -/Last 14 0 R -/Count 1 ->> -endobj - -15 0 obj -<< -/Title (Net) -/Parent 12 0 R -/Prev 13 0 R -/First 16 0 R -/Last 315 0 R -/Count 303 +/Last 16 0 R +/Count 310 >> endobj 14 0 obj << -/Title (SCH_Pro-micro_Pinouts 1-Sheet_1) +/Title (Pages) /Parent 13 0 R -/Dest [3 0 R /XYZ 0 1197.36 0] +/Next 16 0 R +/First 15 0 R +/Last 15 0 R +/Count 1 >> endobj 16 0 obj << -/Title (3V3) -/Parent 15 0 R -/Next 35 0 R +/Title (Net) +/Parent 13 0 R +/Prev 14 0 R /First 17 0 R -/Last 34 0 R -/Count 18 +/Last 320 0 R +/Count 307 >> endobj -35 0 obj +15 0 obj +<< +/Title (SCH_Pro-micro_Pinouts 1-Sheet_1) +/Parent 14 0 R +/Dest [3 0 R /XYZ 0 1197.36 0] +>> +endobj + +17 0 obj +<< +/Title (3V3) +/Parent 16 0 R +/Next 37 0 R +/First 18 0 R +/Last 36 0 R +/Count 19 +>> +endobj + +37 0 obj << /Title (+5V) -/Parent 15 0 R -/Prev 16 0 R -/Next 39 0 R -/First 36 0 R -/Last 38 0 R -/Count 3 ->> -endobj - -39 0 obj -<< -/Title ($1N1) -/Parent 15 0 R -/Prev 35 0 R +/Parent 16 0 R +/Prev 17 0 R /Next 41 0 R -/First 40 0 R +/First 38 0 R /Last 40 0 R -/Count 1 +/Count 3 >> endobj 41 0 obj << -/Title ($1N25) -/Parent 15 0 R -/Prev 39 0 R +/Title ($1N1) +/Parent 16 0 R +/Prev 37 0 R /Next 43 0 R /First 42 0 R /Last 42 0 R @@ -29475,8 +30216,8 @@ endobj 43 0 obj << -/Title ($1N62) -/Parent 15 0 R +/Title ($1N25) +/Parent 16 0 R /Prev 41 0 R /Next 45 0 R /First 44 0 R @@ -29487,153 +30228,153 @@ endobj 45 0 obj << -/Title (ADC) -/Parent 15 0 R +/Title ($1N62) +/Parent 16 0 R /Prev 43 0 R -/Next 50 0 R +/Next 47 0 R /First 46 0 R -/Last 49 0 R +/Last 46 0 R +/Count 1 +>> +endobj + +47 0 obj +<< +/Title (ADC) +/Parent 16 0 R +/Prev 45 0 R +/Next 52 0 R +/First 48 0 R +/Last 51 0 R /Count 4 >> endobj -50 0 obj +52 0 obj << /Title (BATT) -/Parent 15 0 R -/Prev 45 0 R -/Next 57 0 R -/First 51 0 R -/Last 56 0 R +/Parent 16 0 R +/Prev 47 0 R +/Next 59 0 R +/First 53 0 R +/Last 58 0 R /Count 6 >> endobj -57 0 obj +59 0 obj << /Title (BUSY) -/Parent 15 0 R -/Prev 50 0 R -/Next 73 0 R -/First 58 0 R -/Last 72 0 R +/Parent 16 0 R +/Prev 52 0 R +/Next 75 0 R +/First 60 0 R +/Last 74 0 R /Count 15 >> endobj -73 0 obj +75 0 obj << /Title (CS) -/Parent 15 0 R -/Prev 57 0 R -/Next 89 0 R -/First 74 0 R -/Last 88 0 R +/Parent 16 0 R +/Prev 59 0 R +/Next 91 0 R +/First 76 0 R +/Last 90 0 R /Count 15 >> endobj -89 0 obj +91 0 obj << /Title (DIO2) -/Parent 15 0 R -/Prev 73 0 R -/Next 102 0 R -/First 90 0 R -/Last 101 0 R -/Count 12 ->> -endobj - -102 0 obj -<< -/Title (DIO3) -/Parent 15 0 R -/Prev 89 0 R +/Parent 16 0 R +/Prev 75 0 R /Next 104 0 R -/First 103 0 R +/First 92 0 R /Last 103 0 R -/Count 1 +/Count 12 >> endobj 104 0 obj << -/Title (E_INK_BUSY) -/Parent 15 0 R -/Prev 102 0 R -/Next 109 0 R +/Title (DIO3) +/Parent 16 0 R +/Prev 91 0 R +/Next 106 0 R /First 105 0 R -/Last 108 0 R +/Last 105 0 R +/Count 1 +>> +endobj + +106 0 obj +<< +/Title (E_INK_BUSY) +/Parent 16 0 R +/Prev 104 0 R +/Next 111 0 R +/First 107 0 R +/Last 110 0 R /Count 4 >> endobj -109 0 obj +111 0 obj << /Title (E_INK_CS) -/Parent 15 0 R -/Prev 104 0 R -/Next 113 0 R -/First 110 0 R -/Last 112 0 R +/Parent 16 0 R +/Prev 106 0 R +/Next 115 0 R +/First 112 0 R +/Last 114 0 R /Count 3 >> endobj -113 0 obj +115 0 obj << /Title (E_INK_D/C) -/Parent 15 0 R -/Prev 109 0 R -/Next 117 0 R -/First 114 0 R -/Last 116 0 R +/Parent 16 0 R +/Prev 111 0 R +/Next 119 0 R +/First 116 0 R +/Last 118 0 R /Count 3 >> endobj -117 0 obj +119 0 obj << /Title (E_INK_NRST) -/Parent 15 0 R -/Prev 113 0 R -/Next 121 0 R -/First 118 0 R -/Last 120 0 R +/Parent 16 0 R +/Prev 115 0 R +/Next 123 0 R +/First 120 0 R +/Last 122 0 R /Count 3 >> endobj -121 0 obj +123 0 obj << /Title (GND) -/Parent 15 0 R -/Prev 117 0 R -/Next 196 0 R -/First 122 0 R -/Last 195 0 R -/Count 74 ->> -endobj - -196 0 obj -<< -/Title (GPSEN) -/Parent 15 0 R -/Prev 121 0 R +/Parent 16 0 R +/Prev 119 0 R /Next 199 0 R -/First 197 0 R +/First 124 0 R /Last 198 0 R -/Count 2 +/Count 75 >> endobj 199 0 obj << -/Title (GPSRX) -/Parent 15 0 R -/Prev 196 0 R +/Title (GPSEN) +/Parent 16 0 R +/Prev 123 0 R /Next 202 0 R /First 200 0 R /Last 201 0 R @@ -29643,1812 +30384,1826 @@ endobj 202 0 obj << -/Title (GPSTX) -/Parent 15 0 R +/Title (GPSRX) +/Parent 16 0 R /Prev 199 0 R -/Next 205 0 R +/Next 206 0 R /First 203 0 R -/Last 204 0 R -/Count 2 +/Last 205 0 R +/Count 3 >> endobj -205 0 obj +206 0 obj +<< +/Title (GPSTX) +/Parent 16 0 R +/Prev 202 0 R +/Next 210 0 R +/First 207 0 R +/Last 209 0 R +/Count 3 +>> +endobj + +210 0 obj << /Title (IRQ) -/Parent 15 0 R -/Prev 202 0 R -/Next 221 0 R -/First 206 0 R -/Last 220 0 R +/Parent 16 0 R +/Prev 206 0 R +/Next 226 0 R +/First 211 0 R +/Last 225 0 R /Count 15 >> endobj -221 0 obj +226 0 obj << /Title (LORA_ANT) -/Parent 15 0 R -/Prev 205 0 R -/Next 223 0 R -/First 222 0 R -/Last 222 0 R +/Parent 16 0 R +/Prev 210 0 R +/Next 228 0 R +/First 227 0 R +/Last 227 0 R /Count 1 >> endobj -223 0 obj +228 0 obj << /Title (MISO) -/Parent 15 0 R -/Prev 221 0 R -/Next 239 0 R -/First 224 0 R -/Last 238 0 R +/Parent 16 0 R +/Prev 226 0 R +/Next 244 0 R +/First 229 0 R +/Last 243 0 R /Count 15 >> endobj -239 0 obj +244 0 obj << /Title (MOSI) -/Parent 15 0 R -/Prev 223 0 R -/Next 257 0 R -/First 240 0 R -/Last 256 0 R +/Parent 16 0 R +/Prev 228 0 R +/Next 262 0 R +/First 245 0 R +/Last 261 0 R /Count 17 >> endobj -257 0 obj +262 0 obj << /Title (NRST) -/Parent 15 0 R -/Prev 239 0 R -/Next 273 0 R -/First 258 0 R -/Last 272 0 R +/Parent 16 0 R +/Prev 244 0 R +/Next 278 0 R +/First 263 0 R +/Last 277 0 R /Count 15 >> endobj -273 0 obj +278 0 obj << /Title (RBTN) -/Parent 15 0 R -/Prev 257 0 R -/Next 277 0 R -/First 274 0 R -/Last 276 0 R +/Parent 16 0 R +/Prev 262 0 R +/Next 282 0 R +/First 279 0 R +/Last 281 0 R /Count 3 >> endobj -277 0 obj +282 0 obj << /Title (RXEN) -/Parent 15 0 R -/Prev 273 0 R -/Next 285 0 R -/First 278 0 R -/Last 284 0 R +/Parent 16 0 R +/Prev 278 0 R +/Next 290 0 R +/First 283 0 R +/Last 289 0 R /Count 7 >> endobj -285 0 obj +290 0 obj << /Title (SCK) -/Parent 15 0 R -/Prev 277 0 R -/Next 303 0 R -/First 286 0 R -/Last 302 0 R +/Parent 16 0 R +/Prev 282 0 R +/Next 308 0 R +/First 291 0 R +/Last 307 0 R /Count 17 >> endobj -303 0 obj +308 0 obj << /Title (SCL) -/Parent 15 0 R -/Prev 285 0 R -/Next 306 0 R -/First 304 0 R -/Last 305 0 R +/Parent 16 0 R +/Prev 290 0 R +/Next 311 0 R +/First 309 0 R +/Last 310 0 R /Count 2 >> endobj -306 0 obj +311 0 obj << /Title (SDA) -/Parent 15 0 R -/Prev 303 0 R -/Next 309 0 R -/First 307 0 R -/Last 308 0 R +/Parent 16 0 R +/Prev 308 0 R +/Next 314 0 R +/First 312 0 R +/Last 313 0 R /Count 2 >> endobj -309 0 obj +314 0 obj << /Title (SERIAL2RX) -/Parent 15 0 R -/Prev 306 0 R -/Next 312 0 R -/First 310 0 R -/Last 311 0 R +/Parent 16 0 R +/Prev 311 0 R +/Next 317 0 R +/First 315 0 R +/Last 316 0 R /Count 2 >> endobj -312 0 obj +317 0 obj << /Title (SERIAL2TX) -/Parent 15 0 R -/Prev 309 0 R -/Next 315 0 R -/First 313 0 R -/Last 314 0 R +/Parent 16 0 R +/Prev 314 0 R +/Next 320 0 R +/First 318 0 R +/Last 319 0 R /Count 2 >> endobj -315 0 obj +320 0 obj << /Title (UBTN) -/Parent 15 0 R -/Prev 312 0 R -/First 316 0 R -/Last 318 0 R -/Count 3 ->> -endobj - -17 0 obj -<< -/Title ($1N5) /Parent 16 0 R -/Next 18 0 R -/A 319 0 R +/Prev 317 0 R +/First 321 0 R +/Last 323 0 R +/Count 3 >> endobj 18 0 obj << -/Title ($1N29) -/Parent 16 0 R -/Prev 17 0 R +/Title ($1N5) +/Parent 17 0 R /Next 19 0 R -/A 321 0 R +/A 324 0 R >> endobj 19 0 obj << -/Title ($1N35) -/Parent 16 0 R +/Title ($1N29) +/Parent 17 0 R /Prev 18 0 R /Next 20 0 R -/A 323 0 R +/A 326 0 R >> endobj 20 0 obj << -/Title ($1N54) -/Parent 16 0 R +/Title ($1N35) +/Parent 17 0 R /Prev 19 0 R /Next 21 0 R -/A 325 0 R +/A 328 0 R >> endobj 21 0 obj << -/Title ($1N1528) -/Parent 16 0 R +/Title ($1N54) +/Parent 17 0 R /Prev 20 0 R /Next 22 0 R -/A 327 0 R +/A 330 0 R >> endobj 22 0 obj << -/Title ($1N1550) -/Parent 16 0 R +/Title ($1N1528) +/Parent 17 0 R /Prev 21 0 R /Next 23 0 R -/A 329 0 R +/A 332 0 R >> endobj 23 0 obj << -/Title ($1N1574) -/Parent 16 0 R +/Title ($1N1550) +/Parent 17 0 R /Prev 22 0 R /Next 24 0 R -/A 331 0 R +/A 334 0 R >> endobj 24 0 obj << -/Title ($1N1582) -/Parent 16 0 R +/Title ($1N1574) +/Parent 17 0 R /Prev 23 0 R /Next 25 0 R -/A 333 0 R +/A 336 0 R >> endobj 25 0 obj << -/Title ($1N1616) -/Parent 16 0 R +/Title ($1N1582) +/Parent 17 0 R /Prev 24 0 R /Next 26 0 R -/A 335 0 R +/A 338 0 R >> endobj 26 0 obj << -/Title ($1N1618) -/Parent 16 0 R +/Title ($1N1616) +/Parent 17 0 R /Prev 25 0 R /Next 27 0 R -/A 337 0 R +/A 340 0 R >> endobj 27 0 obj << -/Title ($1N1732) -/Parent 16 0 R +/Title ($1N1618) +/Parent 17 0 R /Prev 26 0 R /Next 28 0 R -/A 339 0 R +/A 342 0 R >> endobj 28 0 obj << -/Title ($1N1742) -/Parent 16 0 R +/Title ($1N1732) +/Parent 17 0 R /Prev 27 0 R /Next 29 0 R -/A 341 0 R +/A 344 0 R >> endobj 29 0 obj << -/Title ($1N1776) -/Parent 16 0 R +/Title ($1N1742) +/Parent 17 0 R /Prev 28 0 R /Next 30 0 R -/A 343 0 R +/A 346 0 R >> endobj 30 0 obj << -/Title ($1N1810) -/Parent 16 0 R +/Title ($1N1776) +/Parent 17 0 R /Prev 29 0 R /Next 31 0 R -/A 345 0 R +/A 348 0 R >> endobj 31 0 obj << -/Title ($1N1860) -/Parent 16 0 R +/Title ($1N1810) +/Parent 17 0 R /Prev 30 0 R /Next 32 0 R -/A 347 0 R +/A 350 0 R >> endobj 32 0 obj << -/Title ($1N1874) -/Parent 16 0 R +/Title ($1N1860) +/Parent 17 0 R /Prev 31 0 R /Next 33 0 R -/A 349 0 R +/A 352 0 R >> endobj 33 0 obj << -/Title ($1N5406) -/Parent 16 0 R +/Title ($1N1874) +/Parent 17 0 R /Prev 32 0 R /Next 34 0 R -/A 351 0 R +/A 354 0 R >> endobj 34 0 obj << -/Title ($1N5445) -/Parent 16 0 R +/Title ($1N5406) +/Parent 17 0 R /Prev 33 0 R -/A 353 0 R +/Next 35 0 R +/A 356 0 R +>> +endobj + +35 0 obj +<< +/Title ($1N5445) +/Parent 17 0 R +/Prev 34 0 R +/Next 36 0 R +/A 358 0 R >> endobj 36 0 obj << -/Title ($1N23) -/Parent 35 0 R -/Next 37 0 R -/A 355 0 R ->> -endobj - -37 0 obj -<< -/Title ($1N31) -/Parent 35 0 R -/Prev 36 0 R -/Next 38 0 R -/A 357 0 R +/Title ($1N10678) +/Parent 17 0 R +/Prev 35 0 R +/A 360 0 R >> endobj 38 0 obj << -/Title ($1N1570) -/Parent 35 0 R -/Prev 37 0 R -/A 359 0 R +/Title ($1N23) +/Parent 37 0 R +/Next 39 0 R +/A 362 0 R +>> +endobj + +39 0 obj +<< +/Title ($1N31) +/Parent 37 0 R +/Prev 38 0 R +/Next 40 0 R +/A 364 0 R >> endobj 40 0 obj << -/Title ($1N1) -/Parent 39 0 R -/A 361 0 R +/Title ($1N1570) +/Parent 37 0 R +/Prev 39 0 R +/A 366 0 R >> endobj 42 0 obj << -/Title ($1N25) +/Title ($1N1) /Parent 41 0 R -/A 363 0 R +/A 368 0 R >> endobj 44 0 obj << -/Title ($1N62) +/Title ($1N25) /Parent 43 0 R -/A 365 0 R +/A 370 0 R >> endobj 46 0 obj << -/Title ($1N15) +/Title ($1N62) /Parent 45 0 R -/Next 47 0 R -/A 367 0 R ->> -endobj - -47 0 obj -<< -/Title ($1N44) -/Parent 45 0 R -/Prev 46 0 R -/Next 48 0 R -/A 369 0 R +/A 372 0 R >> endobj 48 0 obj << -/Title ($1N1852) -/Parent 45 0 R -/Prev 47 0 R +/Title ($1N15) +/Parent 47 0 R /Next 49 0 R -/A 371 0 R +/A 374 0 R >> endobj 49 0 obj << -/Title ($1N1856) -/Parent 45 0 R +/Title ($1N44) +/Parent 47 0 R /Prev 48 0 R -/A 373 0 R +/Next 50 0 R +/A 376 0 R +>> +endobj + +50 0 obj +<< +/Title ($1N1852) +/Parent 47 0 R +/Prev 49 0 R +/Next 51 0 R +/A 378 0 R >> endobj 51 0 obj << -/Title ($1N1494) -/Parent 50 0 R -/Next 52 0 R -/A 375 0 R ->> -endobj - -52 0 obj -<< -/Title ($1N1508) -/Parent 50 0 R -/Prev 51 0 R -/Next 53 0 R -/A 377 0 R +/Title ($1N1856) +/Parent 47 0 R +/Prev 50 0 R +/A 380 0 R >> endobj 53 0 obj << -/Title ($1N1578) -/Parent 50 0 R -/Prev 52 0 R +/Title ($1N1494) +/Parent 52 0 R /Next 54 0 R -/A 379 0 R +/A 382 0 R >> endobj 54 0 obj << -/Title ($1N1846) -/Parent 50 0 R +/Title ($1N1508) +/Parent 52 0 R /Prev 53 0 R /Next 55 0 R -/A 381 0 R +/A 384 0 R >> endobj 55 0 obj << -/Title ($1N1848) -/Parent 50 0 R +/Title ($1N1578) +/Parent 52 0 R /Prev 54 0 R /Next 56 0 R -/A 383 0 R +/A 386 0 R >> endobj 56 0 obj << -/Title ($1N1854) -/Parent 50 0 R +/Title ($1N1846) +/Parent 52 0 R /Prev 55 0 R -/A 385 0 R +/Next 57 0 R +/A 388 0 R +>> +endobj + +57 0 obj +<< +/Title ($1N1848) +/Parent 52 0 R +/Prev 56 0 R +/Next 58 0 R +/A 390 0 R >> endobj 58 0 obj << -/Title ($1N12) -/Parent 57 0 R -/Next 59 0 R -/A 387 0 R ->> -endobj - -59 0 obj -<< -/Title ($1N47) -/Parent 57 0 R -/Prev 58 0 R -/Next 60 0 R -/A 389 0 R +/Title ($1N1854) +/Parent 52 0 R +/Prev 57 0 R +/A 392 0 R >> endobj 60 0 obj << -/Title ($1N1540) -/Parent 57 0 R -/Prev 59 0 R +/Title ($1N12) +/Parent 59 0 R /Next 61 0 R -/A 391 0 R +/A 394 0 R >> endobj 61 0 obj << -/Title ($1N1568) -/Parent 57 0 R +/Title ($1N47) +/Parent 59 0 R /Prev 60 0 R /Next 62 0 R -/A 393 0 R +/A 396 0 R >> endobj 62 0 obj << -/Title ($1N1600) -/Parent 57 0 R +/Title ($1N1540) +/Parent 59 0 R /Prev 61 0 R /Next 63 0 R -/A 395 0 R +/A 398 0 R >> endobj 63 0 obj << -/Title ($1N1636) -/Parent 57 0 R +/Title ($1N1568) +/Parent 59 0 R /Prev 62 0 R /Next 64 0 R -/A 397 0 R +/A 400 0 R >> endobj 64 0 obj << -/Title ($1N1660) -/Parent 57 0 R +/Title ($1N1600) +/Parent 59 0 R /Prev 63 0 R /Next 65 0 R -/A 399 0 R +/A 402 0 R >> endobj 65 0 obj << -/Title ($1N1696) -/Parent 57 0 R +/Title ($1N1636) +/Parent 59 0 R /Prev 64 0 R /Next 66 0 R -/A 401 0 R +/A 404 0 R >> endobj 66 0 obj << -/Title ($1N1710) -/Parent 57 0 R +/Title ($1N1660) +/Parent 59 0 R /Prev 65 0 R /Next 67 0 R -/A 403 0 R +/A 406 0 R >> endobj 67 0 obj << -/Title ($1N1736) -/Parent 57 0 R +/Title ($1N1696) +/Parent 59 0 R /Prev 66 0 R /Next 68 0 R -/A 405 0 R +/A 408 0 R >> endobj 68 0 obj << -/Title ($1N1760) -/Parent 57 0 R +/Title ($1N1710) +/Parent 59 0 R /Prev 67 0 R /Next 69 0 R -/A 407 0 R +/A 410 0 R >> endobj 69 0 obj << -/Title ($1N1778) -/Parent 57 0 R +/Title ($1N1736) +/Parent 59 0 R /Prev 68 0 R /Next 70 0 R -/A 409 0 R +/A 412 0 R >> endobj 70 0 obj << -/Title ($1N1814) -/Parent 57 0 R +/Title ($1N1760) +/Parent 59 0 R /Prev 69 0 R /Next 71 0 R -/A 411 0 R +/A 414 0 R >> endobj 71 0 obj << -/Title ($1N1836) -/Parent 57 0 R +/Title ($1N1778) +/Parent 59 0 R /Prev 70 0 R /Next 72 0 R -/A 413 0 R +/A 416 0 R >> endobj 72 0 obj << -/Title ($1N1870) -/Parent 57 0 R +/Title ($1N1814) +/Parent 59 0 R /Prev 71 0 R -/A 415 0 R +/Next 73 0 R +/A 418 0 R +>> +endobj + +73 0 obj +<< +/Title ($1N1836) +/Parent 59 0 R +/Prev 72 0 R +/Next 74 0 R +/A 420 0 R >> endobj 74 0 obj << -/Title ($1N10) -/Parent 73 0 R -/Next 75 0 R -/A 417 0 R ->> -endobj - -75 0 obj -<< -/Title ($1N49) -/Parent 73 0 R -/Prev 74 0 R -/Next 76 0 R -/A 419 0 R +/Title ($1N1870) +/Parent 59 0 R +/Prev 73 0 R +/A 422 0 R >> endobj 76 0 obj << -/Title ($1N1542) -/Parent 73 0 R -/Prev 75 0 R +/Title ($1N10) +/Parent 75 0 R /Next 77 0 R -/A 421 0 R +/A 424 0 R >> endobj 77 0 obj << -/Title ($1N1566) -/Parent 73 0 R +/Title ($1N49) +/Parent 75 0 R /Prev 76 0 R /Next 78 0 R -/A 423 0 R +/A 426 0 R >> endobj 78 0 obj << -/Title ($1N1602) -/Parent 73 0 R +/Title ($1N1542) +/Parent 75 0 R /Prev 77 0 R /Next 79 0 R -/A 425 0 R +/A 428 0 R >> endobj 79 0 obj << -/Title ($1N1626) -/Parent 73 0 R +/Title ($1N1566) +/Parent 75 0 R /Prev 78 0 R /Next 80 0 R -/A 427 0 R +/A 430 0 R >> endobj 80 0 obj << -/Title ($1N1670) -/Parent 73 0 R +/Title ($1N1602) +/Parent 75 0 R /Prev 79 0 R /Next 81 0 R -/A 429 0 R +/A 432 0 R >> endobj 81 0 obj << -/Title ($1N1686) -/Parent 73 0 R +/Title ($1N1626) +/Parent 75 0 R /Prev 80 0 R /Next 82 0 R -/A 431 0 R +/A 434 0 R >> endobj 82 0 obj << -/Title ($1N1718) -/Parent 73 0 R +/Title ($1N1670) +/Parent 75 0 R /Prev 81 0 R /Next 83 0 R -/A 433 0 R +/A 436 0 R >> endobj 83 0 obj << -/Title ($1N1728) -/Parent 73 0 R +/Title ($1N1686) +/Parent 75 0 R /Prev 82 0 R /Next 84 0 R -/A 435 0 R +/A 438 0 R >> endobj 84 0 obj << -/Title ($1N1752) -/Parent 73 0 R +/Title ($1N1718) +/Parent 75 0 R /Prev 83 0 R /Next 85 0 R -/A 437 0 R +/A 440 0 R >> endobj 85 0 obj << -/Title ($1N1788) -/Parent 73 0 R +/Title ($1N1728) +/Parent 75 0 R /Prev 84 0 R /Next 86 0 R -/A 439 0 R +/A 442 0 R >> endobj 86 0 obj << -/Title ($1N1808) -/Parent 73 0 R +/Title ($1N1752) +/Parent 75 0 R /Prev 85 0 R /Next 87 0 R -/A 441 0 R +/A 444 0 R >> endobj 87 0 obj << -/Title ($1N1828) -/Parent 73 0 R +/Title ($1N1788) +/Parent 75 0 R /Prev 86 0 R /Next 88 0 R -/A 443 0 R +/A 446 0 R >> endobj 88 0 obj << -/Title ($1N1876) -/Parent 73 0 R +/Title ($1N1808) +/Parent 75 0 R /Prev 87 0 R -/A 445 0 R +/Next 89 0 R +/A 448 0 R +>> +endobj + +89 0 obj +<< +/Title ($1N1828) +/Parent 75 0 R +/Prev 88 0 R +/Next 90 0 R +/A 450 0 R >> endobj 90 0 obj << -/Title ($1N1624) -/Parent 89 0 R -/Next 91 0 R -/A 447 0 R ->> -endobj - -91 0 obj -<< -/Title ($1N1672) -/Parent 89 0 R -/Prev 90 0 R -/Next 92 0 R -/A 449 0 R +/Title ($1N1876) +/Parent 75 0 R +/Prev 89 0 R +/A 452 0 R >> endobj 92 0 obj << -/Title ($1N1684) -/Parent 89 0 R -/Prev 91 0 R +/Title ($1N1624) +/Parent 91 0 R /Next 93 0 R -/A 451 0 R +/A 454 0 R >> endobj 93 0 obj << -/Title ($1N1698) -/Parent 89 0 R +/Title ($1N1672) +/Parent 91 0 R /Prev 92 0 R /Next 94 0 R -/A 453 0 R +/A 456 0 R >> endobj 94 0 obj << -/Title ($1N1700) -/Parent 89 0 R +/Title ($1N1684) +/Parent 91 0 R /Prev 93 0 R /Next 95 0 R -/A 455 0 R +/A 458 0 R >> endobj 95 0 obj << -/Title ($1N1702) -/Parent 89 0 R +/Title ($1N1698) +/Parent 91 0 R /Prev 94 0 R /Next 96 0 R -/A 457 0 R +/A 460 0 R >> endobj 96 0 obj << -/Title ($1N1744) -/Parent 89 0 R +/Title ($1N1700) +/Parent 91 0 R /Prev 95 0 R /Next 97 0 R -/A 459 0 R +/A 462 0 R >> endobj 97 0 obj << -/Title ($1N1750) -/Parent 89 0 R +/Title ($1N1702) +/Parent 91 0 R /Prev 96 0 R /Next 98 0 R -/A 461 0 R +/A 464 0 R >> endobj 98 0 obj << -/Title ($1N1820) -/Parent 89 0 R +/Title ($1N1744) +/Parent 91 0 R /Prev 97 0 R /Next 99 0 R -/A 463 0 R +/A 466 0 R >> endobj 99 0 obj << -/Title ($1N1822) -/Parent 89 0 R +/Title ($1N1750) +/Parent 91 0 R /Prev 98 0 R /Next 100 0 R -/A 465 0 R +/A 468 0 R >> endobj 100 0 obj << -/Title ($1N1862) -/Parent 89 0 R +/Title ($1N1820) +/Parent 91 0 R /Prev 99 0 R /Next 101 0 R -/A 467 0 R +/A 470 0 R >> endobj 101 0 obj << -/Title ($1N1864) -/Parent 89 0 R +/Title ($1N1822) +/Parent 91 0 R /Prev 100 0 R -/A 469 0 R +/Next 102 0 R +/A 472 0 R +>> +endobj + +102 0 obj +<< +/Title ($1N1862) +/Parent 91 0 R +/Prev 101 0 R +/Next 103 0 R +/A 474 0 R >> endobj 103 0 obj << -/Title ($1N1678) -/Parent 102 0 R -/A 471 0 R +/Title ($1N1864) +/Parent 91 0 R +/Prev 102 0 R +/A 476 0 R >> endobj 105 0 obj << -/Title ($1N1500) +/Title ($1N1678) /Parent 104 0 R -/Next 106 0 R -/A 473 0 R ->> -endobj - -106 0 obj -<< -/Title ($1N1514) -/Parent 104 0 R -/Prev 105 0 R -/Next 107 0 R -/A 475 0 R +/A 478 0 R >> endobj 107 0 obj << -/Title ($1N5294) -/Parent 104 0 R -/Prev 106 0 R +/Title ($1N1500) +/Parent 106 0 R /Next 108 0 R -/A 477 0 R +/A 480 0 R >> endobj 108 0 obj << -/Title ($1N5448) -/Parent 104 0 R +/Title ($1N1514) +/Parent 106 0 R /Prev 107 0 R -/A 479 0 R +/Next 109 0 R +/A 482 0 R +>> +endobj + +109 0 obj +<< +/Title ($1N5294) +/Parent 106 0 R +/Prev 108 0 R +/Next 110 0 R +/A 484 0 R >> endobj 110 0 obj << -/Title ($1N1506) -/Parent 109 0 R -/Next 111 0 R -/A 481 0 R ->> -endobj - -111 0 obj -<< -/Title ($1N5342) -/Parent 109 0 R -/Prev 110 0 R -/Next 112 0 R -/A 483 0 R +/Title ($1N5448) +/Parent 106 0 R +/Prev 109 0 R +/A 486 0 R >> endobj 112 0 obj << -/Title ($1N5457) -/Parent 109 0 R -/Prev 111 0 R -/A 485 0 R +/Title ($1N1506) +/Parent 111 0 R +/Next 113 0 R +/A 488 0 R +>> +endobj + +113 0 obj +<< +/Title ($1N5342) +/Parent 111 0 R +/Prev 112 0 R +/Next 114 0 R +/A 490 0 R >> endobj 114 0 obj << -/Title ($1N1504) -/Parent 113 0 R -/Next 115 0 R -/A 487 0 R ->> -endobj - -115 0 obj -<< -/Title ($1N5326) -/Parent 113 0 R -/Prev 114 0 R -/Next 116 0 R -/A 489 0 R +/Title ($1N5457) +/Parent 111 0 R +/Prev 113 0 R +/A 492 0 R >> endobj 116 0 obj << -/Title ($1N5454) -/Parent 113 0 R -/Prev 115 0 R -/A 491 0 R +/Title ($1N1504) +/Parent 115 0 R +/Next 117 0 R +/A 494 0 R +>> +endobj + +117 0 obj +<< +/Title ($1N5326) +/Parent 115 0 R +/Prev 116 0 R +/Next 118 0 R +/A 496 0 R >> endobj 118 0 obj << -/Title ($1N1502) -/Parent 117 0 R -/Next 119 0 R -/A 493 0 R ->> -endobj - -119 0 obj -<< -/Title ($1N5310) -/Parent 117 0 R -/Prev 118 0 R -/Next 120 0 R -/A 495 0 R +/Title ($1N5454) +/Parent 115 0 R +/Prev 117 0 R +/A 498 0 R >> endobj 120 0 obj << -/Title ($1N5451) -/Parent 117 0 R -/Prev 119 0 R -/A 497 0 R +/Title ($1N1502) +/Parent 119 0 R +/Next 121 0 R +/A 500 0 R +>> +endobj + +121 0 obj +<< +/Title ($1N5310) +/Parent 119 0 R +/Prev 120 0 R +/Next 122 0 R +/A 502 0 R >> endobj 122 0 obj << -/Title ($1N2) -/Parent 121 0 R -/Next 123 0 R -/A 499 0 R ->> -endobj - -123 0 obj -<< -/Title ($1N6) -/Parent 121 0 R -/Prev 122 0 R -/Next 124 0 R -/A 501 0 R +/Title ($1N5451) +/Parent 119 0 R +/Prev 121 0 R +/A 504 0 R >> endobj 124 0 obj << -/Title ($1N22) -/Parent 121 0 R -/Prev 123 0 R +/Title ($1N2) +/Parent 123 0 R /Next 125 0 R -/A 503 0 R +/A 506 0 R >> endobj 125 0 obj << -/Title ($1N24) -/Parent 121 0 R +/Title ($1N6) +/Parent 123 0 R /Prev 124 0 R /Next 126 0 R -/A 505 0 R +/A 508 0 R >> endobj 126 0 obj << -/Title ($1N26) -/Parent 121 0 R +/Title ($1N22) +/Parent 123 0 R /Prev 125 0 R /Next 127 0 R -/A 507 0 R +/A 510 0 R >> endobj 127 0 obj << -/Title ($1N27) -/Parent 121 0 R +/Title ($1N24) +/Parent 123 0 R /Prev 126 0 R /Next 128 0 R -/A 509 0 R +/A 512 0 R >> endobj 128 0 obj << -/Title ($1N28) -/Parent 121 0 R +/Title ($1N26) +/Parent 123 0 R /Prev 127 0 R /Next 129 0 R -/A 511 0 R +/A 514 0 R >> endobj 129 0 obj << -/Title ($1N30) -/Parent 121 0 R +/Title ($1N27) +/Parent 123 0 R /Prev 128 0 R /Next 130 0 R -/A 513 0 R +/A 516 0 R >> endobj 130 0 obj << -/Title ($1N32) -/Parent 121 0 R +/Title ($1N28) +/Parent 123 0 R /Prev 129 0 R /Next 131 0 R -/A 515 0 R +/A 518 0 R >> endobj 131 0 obj << -/Title ($1N36) -/Parent 121 0 R +/Title ($1N30) +/Parent 123 0 R /Prev 130 0 R /Next 132 0 R -/A 517 0 R +/A 520 0 R >> endobj 132 0 obj << -/Title ($1N37) -/Parent 121 0 R +/Title ($1N32) +/Parent 123 0 R /Prev 131 0 R /Next 133 0 R -/A 519 0 R +/A 522 0 R >> endobj 133 0 obj << -/Title ($1N53) -/Parent 121 0 R +/Title ($1N36) +/Parent 123 0 R /Prev 132 0 R /Next 134 0 R -/A 521 0 R +/A 524 0 R >> endobj 134 0 obj << -/Title ($1N58) -/Parent 121 0 R +/Title ($1N37) +/Parent 123 0 R /Prev 133 0 R /Next 135 0 R -/A 523 0 R +/A 526 0 R >> endobj 135 0 obj << -/Title ($1N61) -/Parent 121 0 R +/Title ($1N53) +/Parent 123 0 R /Prev 134 0 R /Next 136 0 R -/A 525 0 R +/A 528 0 R >> endobj 136 0 obj << -/Title ($1N63) -/Parent 121 0 R +/Title ($1N58) +/Parent 123 0 R /Prev 135 0 R /Next 137 0 R -/A 527 0 R +/A 530 0 R >> endobj 137 0 obj << -/Title ($1N1516) -/Parent 121 0 R +/Title ($1N61) +/Parent 123 0 R /Prev 136 0 R /Next 138 0 R -/A 529 0 R +/A 532 0 R >> endobj 138 0 obj << -/Title ($1N1518) -/Parent 121 0 R +/Title ($1N63) +/Parent 123 0 R /Prev 137 0 R /Next 139 0 R -/A 531 0 R +/A 534 0 R >> endobj 139 0 obj << -/Title ($1N1524) -/Parent 121 0 R +/Title ($1N1516) +/Parent 123 0 R /Prev 138 0 R /Next 140 0 R -/A 533 0 R +/A 536 0 R >> endobj 140 0 obj << -/Title ($1N1526) -/Parent 121 0 R +/Title ($1N1518) +/Parent 123 0 R /Prev 139 0 R /Next 141 0 R -/A 535 0 R +/A 538 0 R >> endobj 141 0 obj << -/Title ($1N1530) -/Parent 121 0 R +/Title ($1N1524) +/Parent 123 0 R /Prev 140 0 R /Next 142 0 R -/A 537 0 R +/A 540 0 R >> endobj 142 0 obj << -/Title ($1N1532) -/Parent 121 0 R +/Title ($1N1526) +/Parent 123 0 R /Prev 141 0 R /Next 143 0 R -/A 539 0 R +/A 542 0 R >> endobj 143 0 obj << -/Title ($1N1534) -/Parent 121 0 R +/Title ($1N1530) +/Parent 123 0 R /Prev 142 0 R /Next 144 0 R -/A 541 0 R +/A 544 0 R >> endobj 144 0 obj << -/Title ($1N1552) -/Parent 121 0 R +/Title ($1N1532) +/Parent 123 0 R /Prev 143 0 R /Next 145 0 R -/A 543 0 R +/A 546 0 R >> endobj 145 0 obj << -/Title ($1N1554) -/Parent 121 0 R +/Title ($1N1534) +/Parent 123 0 R /Prev 144 0 R /Next 146 0 R -/A 545 0 R +/A 548 0 R >> endobj 146 0 obj << -/Title ($1N1572) -/Parent 121 0 R +/Title ($1N1552) +/Parent 123 0 R /Prev 145 0 R /Next 147 0 R -/A 547 0 R +/A 550 0 R >> endobj 147 0 obj << -/Title ($1N1576) -/Parent 121 0 R +/Title ($1N1554) +/Parent 123 0 R /Prev 146 0 R /Next 148 0 R -/A 549 0 R +/A 552 0 R >> endobj 148 0 obj << -/Title ($1N1580) -/Parent 121 0 R +/Title ($1N1572) +/Parent 123 0 R /Prev 147 0 R /Next 149 0 R -/A 551 0 R +/A 554 0 R >> endobj 149 0 obj << -/Title ($1N1584) -/Parent 121 0 R +/Title ($1N1576) +/Parent 123 0 R /Prev 148 0 R /Next 150 0 R -/A 553 0 R +/A 556 0 R >> endobj 150 0 obj << -/Title ($1N1586) -/Parent 121 0 R +/Title ($1N1580) +/Parent 123 0 R /Prev 149 0 R /Next 151 0 R -/A 555 0 R +/A 558 0 R >> endobj 151 0 obj << -/Title ($1N1588) -/Parent 121 0 R +/Title ($1N1584) +/Parent 123 0 R /Prev 150 0 R /Next 152 0 R -/A 557 0 R +/A 560 0 R >> endobj 152 0 obj << -/Title ($1N1590) -/Parent 121 0 R +/Title ($1N1586) +/Parent 123 0 R /Prev 151 0 R /Next 153 0 R -/A 559 0 R +/A 562 0 R >> endobj 153 0 obj << -/Title ($1N1592) -/Parent 121 0 R +/Title ($1N1588) +/Parent 123 0 R /Prev 152 0 R /Next 154 0 R -/A 561 0 R +/A 564 0 R >> endobj 154 0 obj << -/Title ($1N1594) -/Parent 121 0 R +/Title ($1N1590) +/Parent 123 0 R /Prev 153 0 R /Next 155 0 R -/A 563 0 R +/A 566 0 R >> endobj 155 0 obj << -/Title ($1N1596) -/Parent 121 0 R +/Title ($1N1592) +/Parent 123 0 R /Prev 154 0 R /Next 156 0 R -/A 565 0 R +/A 568 0 R >> endobj 156 0 obj << -/Title ($1N1598) -/Parent 121 0 R +/Title ($1N1594) +/Parent 123 0 R /Prev 155 0 R /Next 157 0 R -/A 567 0 R +/A 570 0 R >> endobj 157 0 obj << -/Title ($1N1614) -/Parent 121 0 R +/Title ($1N1596) +/Parent 123 0 R /Prev 156 0 R /Next 158 0 R -/A 569 0 R +/A 572 0 R >> endobj 158 0 obj << -/Title ($1N1638) -/Parent 121 0 R +/Title ($1N1598) +/Parent 123 0 R /Prev 157 0 R /Next 159 0 R -/A 571 0 R +/A 574 0 R >> endobj 159 0 obj << -/Title ($1N1640) -/Parent 121 0 R +/Title ($1N1614) +/Parent 123 0 R /Prev 158 0 R /Next 160 0 R -/A 573 0 R +/A 576 0 R >> endobj 160 0 obj << -/Title ($1N1642) -/Parent 121 0 R +/Title ($1N1638) +/Parent 123 0 R /Prev 159 0 R /Next 161 0 R -/A 575 0 R +/A 578 0 R >> endobj 161 0 obj << -/Title ($1N1644) -/Parent 121 0 R +/Title ($1N1640) +/Parent 123 0 R /Prev 160 0 R /Next 162 0 R -/A 577 0 R +/A 580 0 R >> endobj 162 0 obj << -/Title ($1N1646) -/Parent 121 0 R +/Title ($1N1642) +/Parent 123 0 R /Prev 161 0 R /Next 163 0 R -/A 579 0 R +/A 582 0 R >> endobj 163 0 obj << -/Title ($1N1648) -/Parent 121 0 R +/Title ($1N1644) +/Parent 123 0 R /Prev 162 0 R /Next 164 0 R -/A 581 0 R +/A 584 0 R >> endobj 164 0 obj << -/Title ($1N1650) -/Parent 121 0 R +/Title ($1N1646) +/Parent 123 0 R /Prev 163 0 R /Next 165 0 R -/A 583 0 R +/A 586 0 R >> endobj 165 0 obj << -/Title ($1N1652) -/Parent 121 0 R +/Title ($1N1648) +/Parent 123 0 R /Prev 164 0 R /Next 166 0 R -/A 585 0 R +/A 588 0 R >> endobj 166 0 obj << -/Title ($1N1656) -/Parent 121 0 R +/Title ($1N1650) +/Parent 123 0 R /Prev 165 0 R /Next 167 0 R -/A 587 0 R +/A 590 0 R >> endobj 167 0 obj << -/Title ($1N1658) -/Parent 121 0 R +/Title ($1N1652) +/Parent 123 0 R /Prev 166 0 R /Next 168 0 R -/A 589 0 R +/A 592 0 R >> endobj 168 0 obj << -/Title ($1N1708) -/Parent 121 0 R +/Title ($1N1656) +/Parent 123 0 R /Prev 167 0 R /Next 169 0 R -/A 591 0 R +/A 594 0 R >> endobj 169 0 obj << -/Title ($1N1730) -/Parent 121 0 R +/Title ($1N1658) +/Parent 123 0 R /Prev 168 0 R /Next 170 0 R -/A 593 0 R +/A 596 0 R >> endobj 170 0 obj << -/Title ($1N1734) -/Parent 121 0 R +/Title ($1N1708) +/Parent 123 0 R /Prev 169 0 R /Next 171 0 R -/A 595 0 R +/A 598 0 R >> endobj 171 0 obj << -/Title ($1N1740) -/Parent 121 0 R +/Title ($1N1730) +/Parent 123 0 R /Prev 170 0 R /Next 172 0 R -/A 597 0 R +/A 600 0 R >> endobj 172 0 obj << -/Title ($1N1762) -/Parent 121 0 R +/Title ($1N1734) +/Parent 123 0 R /Prev 171 0 R /Next 173 0 R -/A 599 0 R +/A 602 0 R >> endobj 173 0 obj << -/Title ($1N1764) -/Parent 121 0 R +/Title ($1N1740) +/Parent 123 0 R /Prev 172 0 R /Next 174 0 R -/A 601 0 R +/A 604 0 R >> endobj 174 0 obj << -/Title ($1N1766) -/Parent 121 0 R +/Title ($1N1762) +/Parent 123 0 R /Prev 173 0 R /Next 175 0 R -/A 603 0 R +/A 606 0 R >> endobj 175 0 obj << -/Title ($1N1768) -/Parent 121 0 R +/Title ($1N1764) +/Parent 123 0 R /Prev 174 0 R /Next 176 0 R -/A 605 0 R +/A 608 0 R >> endobj 176 0 obj << -/Title ($1N1770) -/Parent 121 0 R +/Title ($1N1766) +/Parent 123 0 R /Prev 175 0 R /Next 177 0 R -/A 607 0 R +/A 610 0 R >> endobj 177 0 obj << -/Title ($1N1774) -/Parent 121 0 R +/Title ($1N1768) +/Parent 123 0 R /Prev 176 0 R /Next 178 0 R -/A 609 0 R +/A 612 0 R >> endobj 178 0 obj << -/Title ($1N1790) -/Parent 121 0 R +/Title ($1N1770) +/Parent 123 0 R /Prev 177 0 R /Next 179 0 R -/A 611 0 R +/A 614 0 R >> endobj 179 0 obj << -/Title ($1N1792) -/Parent 121 0 R +/Title ($1N1774) +/Parent 123 0 R /Prev 178 0 R /Next 180 0 R -/A 613 0 R +/A 616 0 R >> endobj 180 0 obj << -/Title ($1N1796) -/Parent 121 0 R +/Title ($1N1790) +/Parent 123 0 R /Prev 179 0 R /Next 181 0 R -/A 615 0 R +/A 618 0 R >> endobj 181 0 obj << -/Title ($1N1798) -/Parent 121 0 R +/Title ($1N1792) +/Parent 123 0 R /Prev 180 0 R /Next 182 0 R -/A 617 0 R +/A 620 0 R >> endobj 182 0 obj << -/Title ($1N1800) -/Parent 121 0 R +/Title ($1N1796) +/Parent 123 0 R /Prev 181 0 R /Next 183 0 R -/A 619 0 R +/A 622 0 R >> endobj 183 0 obj << -/Title ($1N1824) -/Parent 121 0 R +/Title ($1N1798) +/Parent 123 0 R /Prev 182 0 R /Next 184 0 R -/A 621 0 R +/A 624 0 R >> endobj 184 0 obj << -/Title ($1N1826) -/Parent 121 0 R +/Title ($1N1800) +/Parent 123 0 R /Prev 183 0 R /Next 185 0 R -/A 623 0 R +/A 626 0 R >> endobj 185 0 obj << -/Title ($1N1838) -/Parent 121 0 R +/Title ($1N1824) +/Parent 123 0 R /Prev 184 0 R /Next 186 0 R -/A 625 0 R +/A 628 0 R >> endobj 186 0 obj << -/Title ($1N1840) -/Parent 121 0 R +/Title ($1N1826) +/Parent 123 0 R /Prev 185 0 R /Next 187 0 R -/A 627 0 R +/A 630 0 R >> endobj 187 0 obj << -/Title ($1N1842) -/Parent 121 0 R +/Title ($1N1838) +/Parent 123 0 R /Prev 186 0 R /Next 188 0 R -/A 629 0 R +/A 632 0 R >> endobj 188 0 obj << -/Title ($1N1844) -/Parent 121 0 R +/Title ($1N1840) +/Parent 123 0 R /Prev 187 0 R /Next 189 0 R -/A 631 0 R +/A 634 0 R >> endobj 189 0 obj << -/Title ($1N1850) -/Parent 121 0 R +/Title ($1N1842) +/Parent 123 0 R /Prev 188 0 R /Next 190 0 R -/A 633 0 R +/A 636 0 R >> endobj 190 0 obj << -/Title ($1N1858) -/Parent 121 0 R +/Title ($1N1844) +/Parent 123 0 R /Prev 189 0 R /Next 191 0 R -/A 635 0 R +/A 638 0 R >> endobj 191 0 obj << -/Title ($1N1866) -/Parent 121 0 R +/Title ($1N1850) +/Parent 123 0 R /Prev 190 0 R /Next 192 0 R -/A 637 0 R +/A 640 0 R >> endobj 192 0 obj << -/Title ($1N1884) -/Parent 121 0 R +/Title ($1N1858) +/Parent 123 0 R /Prev 191 0 R /Next 193 0 R -/A 639 0 R +/A 642 0 R >> endobj 193 0 obj << -/Title ($1N1886) -/Parent 121 0 R +/Title ($1N1866) +/Parent 123 0 R /Prev 192 0 R /Next 194 0 R -/A 641 0 R +/A 644 0 R >> endobj 194 0 obj << -/Title ($1N5390) -/Parent 121 0 R +/Title ($1N1884) +/Parent 123 0 R /Prev 193 0 R /Next 195 0 R -/A 643 0 R +/A 646 0 R >> endobj 195 0 obj << -/Title ($1N5442) -/Parent 121 0 R +/Title ($1N1886) +/Parent 123 0 R /Prev 194 0 R -/A 645 0 R +/Next 196 0 R +/A 648 0 R +>> +endobj + +196 0 obj +<< +/Title ($1N5390) +/Parent 123 0 R +/Prev 195 0 R +/Next 197 0 R +/A 650 0 R >> endobj 197 0 obj << -/Title ($1N19) -/Parent 196 0 R +/Title ($1N5442) +/Parent 123 0 R +/Prev 196 0 R /Next 198 0 R -/A 647 0 R +/A 652 0 R >> endobj 198 0 obj << -/Title ($1N40) -/Parent 196 0 R +/Title ($1N10681) +/Parent 123 0 R /Prev 197 0 R -/A 649 0 R +/A 654 0 R >> endobj 200 0 obj << -/Title ($1N21) +/Title ($1N19) /Parent 199 0 R /Next 201 0 R -/A 651 0 R +/A 656 0 R >> endobj 201 0 obj << -/Title ($1N38) +/Title ($1N40) /Parent 199 0 R /Prev 200 0 R -/A 653 0 R +/A 658 0 R >> endobj @@ -31457,1890 +32212,1941 @@ endobj /Title ($1N20) /Parent 202 0 R /Next 204 0 R -/A 655 0 R +/A 660 0 R >> endobj 204 0 obj << -/Title ($1N39) +/Title ($1N8820) /Parent 202 0 R /Prev 203 0 R -/A 657 0 R +/Next 205 0 R +/A 662 0 R >> endobj -206 0 obj +205 0 obj << -/Title ($1N11) -/Parent 205 0 R -/Next 207 0 R -/A 659 0 R +/Title ($1N10687) +/Parent 202 0 R +/Prev 204 0 R +/A 664 0 R >> endobj 207 0 obj << -/Title ($1N33) -/Parent 205 0 R -/Prev 206 0 R +/Title ($1N21) +/Parent 206 0 R /Next 208 0 R -/A 661 0 R +/A 666 0 R >> endobj 208 0 obj << -/Title ($1N48) -/Parent 205 0 R +/Title ($1N8817) +/Parent 206 0 R /Prev 207 0 R /Next 209 0 R -/A 663 0 R +/A 668 0 R >> endobj 209 0 obj << -/Title ($1N1538) -/Parent 205 0 R +/Title ($1N10684) +/Parent 206 0 R /Prev 208 0 R -/Next 210 0 R -/A 665 0 R ->> -endobj - -210 0 obj -<< -/Title ($1N1556) -/Parent 205 0 R -/Prev 209 0 R -/Next 211 0 R -/A 667 0 R +/A 670 0 R >> endobj 211 0 obj << -/Title ($1N1610) -/Parent 205 0 R -/Prev 210 0 R +/Title ($1N11) +/Parent 210 0 R /Next 212 0 R -/A 669 0 R +/A 672 0 R >> endobj 212 0 obj << -/Title ($1N1622) -/Parent 205 0 R +/Title ($1N33) +/Parent 210 0 R /Prev 211 0 R /Next 213 0 R -/A 671 0 R +/A 674 0 R >> endobj 213 0 obj << -/Title ($1N1674) -/Parent 205 0 R +/Title ($1N48) +/Parent 210 0 R /Prev 212 0 R /Next 214 0 R -/A 673 0 R +/A 676 0 R >> endobj 214 0 obj << -/Title ($1N1682) -/Parent 205 0 R +/Title ($1N1538) +/Parent 210 0 R /Prev 213 0 R /Next 215 0 R -/A 675 0 R +/A 678 0 R >> endobj 215 0 obj << -/Title ($1N1706) -/Parent 205 0 R +/Title ($1N1556) +/Parent 210 0 R /Prev 214 0 R /Next 216 0 R -/A 677 0 R +/A 680 0 R >> endobj 216 0 obj << -/Title ($1N1738) -/Parent 205 0 R +/Title ($1N1610) +/Parent 210 0 R /Prev 215 0 R /Next 217 0 R -/A 679 0 R +/A 682 0 R >> endobj 217 0 obj << -/Title ($1N1748) -/Parent 205 0 R +/Title ($1N1622) +/Parent 210 0 R /Prev 216 0 R /Next 218 0 R -/A 681 0 R +/A 684 0 R >> endobj 218 0 obj << -/Title ($1N1772) -/Parent 205 0 R +/Title ($1N1674) +/Parent 210 0 R /Prev 217 0 R /Next 219 0 R -/A 683 0 R +/A 686 0 R >> endobj 219 0 obj << -/Title ($1N1816) -/Parent 205 0 R +/Title ($1N1682) +/Parent 210 0 R /Prev 218 0 R /Next 220 0 R -/A 685 0 R +/A 688 0 R >> endobj 220 0 obj << -/Title ($1N1868) -/Parent 205 0 R +/Title ($1N1706) +/Parent 210 0 R /Prev 219 0 R -/A 687 0 R +/Next 221 0 R +/A 690 0 R +>> +endobj + +221 0 obj +<< +/Title ($1N1738) +/Parent 210 0 R +/Prev 220 0 R +/Next 222 0 R +/A 692 0 R >> endobj 222 0 obj << -/Title ($1N1818) -/Parent 221 0 R -/A 689 0 R +/Title ($1N1748) +/Parent 210 0 R +/Prev 221 0 R +/Next 223 0 R +/A 694 0 R +>> +endobj + +223 0 obj +<< +/Title ($1N1772) +/Parent 210 0 R +/Prev 222 0 R +/Next 224 0 R +/A 696 0 R >> endobj 224 0 obj << -/Title ($1N7) -/Parent 223 0 R +/Title ($1N1816) +/Parent 210 0 R +/Prev 223 0 R /Next 225 0 R -/A 691 0 R +/A 698 0 R >> endobj 225 0 obj << -/Title ($1N52) -/Parent 223 0 R +/Title ($1N1868) +/Parent 210 0 R /Prev 224 0 R -/Next 226 0 R -/A 693 0 R ->> -endobj - -226 0 obj -<< -/Title ($1N1548) -/Parent 223 0 R -/Prev 225 0 R -/Next 227 0 R -/A 695 0 R +/A 700 0 R >> endobj 227 0 obj << -/Title ($1N1560) -/Parent 223 0 R -/Prev 226 0 R -/Next 228 0 R -/A 697 0 R ->> -endobj - -228 0 obj -<< -/Title ($1N1608) -/Parent 223 0 R -/Prev 227 0 R -/Next 229 0 R -/A 699 0 R +/Title ($1N1818) +/Parent 226 0 R +/A 702 0 R >> endobj 229 0 obj << -/Title ($1N1630) -/Parent 223 0 R -/Prev 228 0 R +/Title ($1N7) +/Parent 228 0 R /Next 230 0 R -/A 701 0 R +/A 704 0 R >> endobj 230 0 obj << -/Title ($1N1666) -/Parent 223 0 R +/Title ($1N52) +/Parent 228 0 R /Prev 229 0 R /Next 231 0 R -/A 703 0 R +/A 706 0 R >> endobj 231 0 obj << -/Title ($1N1690) -/Parent 223 0 R +/Title ($1N1548) +/Parent 228 0 R /Prev 230 0 R /Next 232 0 R -/A 705 0 R +/A 708 0 R >> endobj 232 0 obj << -/Title ($1N1714) -/Parent 223 0 R +/Title ($1N1560) +/Parent 228 0 R /Prev 231 0 R /Next 233 0 R -/A 707 0 R +/A 710 0 R >> endobj 233 0 obj << -/Title ($1N1720) -/Parent 223 0 R +/Title ($1N1608) +/Parent 228 0 R /Prev 232 0 R /Next 234 0 R -/A 709 0 R +/A 712 0 R >> endobj 234 0 obj << -/Title ($1N1756) -/Parent 223 0 R +/Title ($1N1630) +/Parent 228 0 R /Prev 233 0 R /Next 235 0 R -/A 711 0 R +/A 714 0 R >> endobj 235 0 obj << -/Title ($1N1782) -/Parent 223 0 R +/Title ($1N1666) +/Parent 228 0 R /Prev 234 0 R /Next 236 0 R -/A 713 0 R +/A 716 0 R >> endobj 236 0 obj << -/Title ($1N1804) -/Parent 223 0 R +/Title ($1N1690) +/Parent 228 0 R /Prev 235 0 R /Next 237 0 R -/A 715 0 R +/A 718 0 R >> endobj 237 0 obj << -/Title ($1N1834) -/Parent 223 0 R +/Title ($1N1714) +/Parent 228 0 R /Prev 236 0 R /Next 238 0 R -/A 717 0 R +/A 720 0 R >> endobj 238 0 obj << -/Title ($1N1880) -/Parent 223 0 R +/Title ($1N1720) +/Parent 228 0 R /Prev 237 0 R -/A 719 0 R +/Next 239 0 R +/A 722 0 R +>> +endobj + +239 0 obj +<< +/Title ($1N1756) +/Parent 228 0 R +/Prev 238 0 R +/Next 240 0 R +/A 724 0 R >> endobj 240 0 obj << -/Title ($1N8) -/Parent 239 0 R +/Title ($1N1782) +/Parent 228 0 R +/Prev 239 0 R /Next 241 0 R -/A 721 0 R +/A 726 0 R >> endobj 241 0 obj << -/Title ($1N51) -/Parent 239 0 R +/Title ($1N1804) +/Parent 228 0 R /Prev 240 0 R /Next 242 0 R -/A 723 0 R +/A 728 0 R >> endobj 242 0 obj << -/Title ($1N1546) -/Parent 239 0 R +/Title ($1N1834) +/Parent 228 0 R /Prev 241 0 R /Next 243 0 R -/A 725 0 R +/A 730 0 R >> endobj 243 0 obj << -/Title ($1N1562) -/Parent 239 0 R +/Title ($1N1880) +/Parent 228 0 R /Prev 242 0 R -/Next 244 0 R -/A 727 0 R ->> -endobj - -244 0 obj -<< -/Title ($1N1606) -/Parent 239 0 R -/Prev 243 0 R -/Next 245 0 R -/A 729 0 R +/A 732 0 R >> endobj 245 0 obj << -/Title ($1N1628) -/Parent 239 0 R -/Prev 244 0 R +/Title ($1N8) +/Parent 244 0 R /Next 246 0 R -/A 731 0 R +/A 734 0 R >> endobj 246 0 obj << -/Title ($1N1668) -/Parent 239 0 R +/Title ($1N51) +/Parent 244 0 R /Prev 245 0 R /Next 247 0 R -/A 733 0 R +/A 736 0 R >> endobj 247 0 obj << -/Title ($1N1688) -/Parent 239 0 R +/Title ($1N1546) +/Parent 244 0 R /Prev 246 0 R /Next 248 0 R -/A 735 0 R +/A 738 0 R >> endobj 248 0 obj << -/Title ($1N1716) -/Parent 239 0 R +/Title ($1N1562) +/Parent 244 0 R /Prev 247 0 R /Next 249 0 R -/A 737 0 R +/A 740 0 R >> endobj 249 0 obj << -/Title ($1N1722) -/Parent 239 0 R +/Title ($1N1606) +/Parent 244 0 R /Prev 248 0 R /Next 250 0 R -/A 739 0 R +/A 742 0 R >> endobj 250 0 obj << -/Title ($1N1754) -/Parent 239 0 R +/Title ($1N1628) +/Parent 244 0 R /Prev 249 0 R /Next 251 0 R -/A 741 0 R +/A 744 0 R >> endobj 251 0 obj << -/Title ($1N1784) -/Parent 239 0 R +/Title ($1N1668) +/Parent 244 0 R /Prev 250 0 R /Next 252 0 R -/A 743 0 R +/A 746 0 R >> endobj 252 0 obj << -/Title ($1N1802) -/Parent 239 0 R +/Title ($1N1688) +/Parent 244 0 R /Prev 251 0 R /Next 253 0 R -/A 745 0 R +/A 748 0 R >> endobj 253 0 obj << -/Title ($1N1832) -/Parent 239 0 R +/Title ($1N1716) +/Parent 244 0 R /Prev 252 0 R /Next 254 0 R -/A 747 0 R +/A 750 0 R >> endobj 254 0 obj << -/Title ($1N1882) -/Parent 239 0 R +/Title ($1N1722) +/Parent 244 0 R /Prev 253 0 R /Next 255 0 R -/A 749 0 R +/A 752 0 R >> endobj 255 0 obj << -/Title ($1N5374) -/Parent 239 0 R +/Title ($1N1754) +/Parent 244 0 R /Prev 254 0 R /Next 256 0 R -/A 751 0 R +/A 754 0 R >> endobj 256 0 obj << -/Title ($1N5481) -/Parent 239 0 R +/Title ($1N1784) +/Parent 244 0 R /Prev 255 0 R -/A 753 0 R +/Next 257 0 R +/A 756 0 R +>> +endobj + +257 0 obj +<< +/Title ($1N1802) +/Parent 244 0 R +/Prev 256 0 R +/Next 258 0 R +/A 758 0 R >> endobj 258 0 obj << -/Title ($1N13) -/Parent 257 0 R +/Title ($1N1832) +/Parent 244 0 R +/Prev 257 0 R /Next 259 0 R -/A 755 0 R +/A 760 0 R >> endobj 259 0 obj << -/Title ($1N34) -/Parent 257 0 R +/Title ($1N1882) +/Parent 244 0 R /Prev 258 0 R /Next 260 0 R -/A 757 0 R +/A 762 0 R >> endobj 260 0 obj << -/Title ($1N46) -/Parent 257 0 R +/Title ($1N5374) +/Parent 244 0 R /Prev 259 0 R /Next 261 0 R -/A 759 0 R +/A 764 0 R >> endobj 261 0 obj << -/Title ($1N1536) -/Parent 257 0 R +/Title ($1N5481) +/Parent 244 0 R /Prev 260 0 R -/Next 262 0 R -/A 761 0 R ->> -endobj - -262 0 obj -<< -/Title ($1N1558) -/Parent 257 0 R -/Prev 261 0 R -/Next 263 0 R -/A 763 0 R +/A 766 0 R >> endobj 263 0 obj << -/Title ($1N1612) -/Parent 257 0 R -/Prev 262 0 R +/Title ($1N13) +/Parent 262 0 R /Next 264 0 R -/A 765 0 R +/A 768 0 R >> endobj 264 0 obj << -/Title ($1N1620) -/Parent 257 0 R +/Title ($1N34) +/Parent 262 0 R /Prev 263 0 R /Next 265 0 R -/A 767 0 R +/A 770 0 R >> endobj 265 0 obj << -/Title ($1N1676) -/Parent 257 0 R +/Title ($1N46) +/Parent 262 0 R /Prev 264 0 R /Next 266 0 R -/A 769 0 R +/A 772 0 R >> endobj 266 0 obj << -/Title ($1N1680) -/Parent 257 0 R +/Title ($1N1536) +/Parent 262 0 R /Prev 265 0 R /Next 267 0 R -/A 771 0 R +/A 774 0 R >> endobj 267 0 obj << -/Title ($1N1704) -/Parent 257 0 R +/Title ($1N1558) +/Parent 262 0 R /Prev 266 0 R /Next 268 0 R -/A 773 0 R +/A 776 0 R >> endobj 268 0 obj << -/Title ($1N1726) -/Parent 257 0 R +/Title ($1N1612) +/Parent 262 0 R /Prev 267 0 R /Next 269 0 R -/A 775 0 R +/A 778 0 R >> endobj 269 0 obj << -/Title ($1N1746) -/Parent 257 0 R +/Title ($1N1620) +/Parent 262 0 R /Prev 268 0 R /Next 270 0 R -/A 777 0 R +/A 780 0 R >> endobj 270 0 obj << -/Title ($1N1780) -/Parent 257 0 R +/Title ($1N1676) +/Parent 262 0 R /Prev 269 0 R /Next 271 0 R -/A 779 0 R +/A 782 0 R >> endobj 271 0 obj << -/Title ($1N1812) -/Parent 257 0 R +/Title ($1N1680) +/Parent 262 0 R /Prev 270 0 R /Next 272 0 R -/A 781 0 R +/A 784 0 R >> endobj 272 0 obj << -/Title ($1N1872) -/Parent 257 0 R +/Title ($1N1704) +/Parent 262 0 R /Prev 271 0 R -/A 783 0 R +/Next 273 0 R +/A 786 0 R +>> +endobj + +273 0 obj +<< +/Title ($1N1726) +/Parent 262 0 R +/Prev 272 0 R +/Next 274 0 R +/A 788 0 R >> endobj 274 0 obj << -/Title ($1N14) -/Parent 273 0 R +/Title ($1N1746) +/Parent 262 0 R +/Prev 273 0 R /Next 275 0 R -/A 785 0 R +/A 790 0 R >> endobj 275 0 obj << -/Title ($1N45) -/Parent 273 0 R +/Title ($1N1780) +/Parent 262 0 R /Prev 274 0 R /Next 276 0 R -/A 787 0 R +/A 792 0 R >> endobj 276 0 obj << -/Title ($1N1520) -/Parent 273 0 R +/Title ($1N1812) +/Parent 262 0 R /Prev 275 0 R -/A 789 0 R +/Next 277 0 R +/A 794 0 R >> endobj -278 0 obj +277 0 obj << -/Title ($1N4) -/Parent 277 0 R -/Next 279 0 R -/A 791 0 R +/Title ($1N1872) +/Parent 262 0 R +/Prev 276 0 R +/A 796 0 R >> endobj 279 0 obj << -/Title ($1N56) -/Parent 277 0 R -/Prev 278 0 R +/Title ($1N14) +/Parent 278 0 R /Next 280 0 R -/A 793 0 R +/A 798 0 R >> endobj 280 0 obj << -/Title ($1N57) -/Parent 277 0 R +/Title ($1N45) +/Parent 278 0 R /Prev 279 0 R /Next 281 0 R -/A 795 0 R +/A 800 0 R >> endobj 281 0 obj << -/Title ($1N1634) -/Parent 277 0 R +/Title ($1N1520) +/Parent 278 0 R /Prev 280 0 R -/Next 282 0 R -/A 797 0 R ->> -endobj - -282 0 obj -<< -/Title ($1N1662) -/Parent 277 0 R -/Prev 281 0 R -/Next 283 0 R -/A 799 0 R +/A 802 0 R >> endobj 283 0 obj << -/Title ($1N1694) -/Parent 277 0 R -/Prev 282 0 R +/Title ($1N4) +/Parent 282 0 R /Next 284 0 R -/A 801 0 R +/A 804 0 R >> endobj 284 0 obj << -/Title ($1N1794) -/Parent 277 0 R +/Title ($1N56) +/Parent 282 0 R /Prev 283 0 R -/A 803 0 R +/Next 285 0 R +/A 806 0 R +>> +endobj + +285 0 obj +<< +/Title ($1N57) +/Parent 282 0 R +/Prev 284 0 R +/Next 286 0 R +/A 808 0 R >> endobj 286 0 obj << -/Title ($1N9) -/Parent 285 0 R +/Title ($1N1634) +/Parent 282 0 R +/Prev 285 0 R /Next 287 0 R -/A 805 0 R +/A 810 0 R >> endobj 287 0 obj << -/Title ($1N50) -/Parent 285 0 R +/Title ($1N1662) +/Parent 282 0 R /Prev 286 0 R /Next 288 0 R -/A 807 0 R +/A 812 0 R >> endobj 288 0 obj << -/Title ($1N1544) -/Parent 285 0 R +/Title ($1N1694) +/Parent 282 0 R /Prev 287 0 R /Next 289 0 R -/A 809 0 R +/A 814 0 R >> endobj 289 0 obj << -/Title ($1N1564) -/Parent 285 0 R +/Title ($1N1794) +/Parent 282 0 R /Prev 288 0 R -/Next 290 0 R -/A 811 0 R ->> -endobj - -290 0 obj -<< -/Title ($1N1604) -/Parent 285 0 R -/Prev 289 0 R -/Next 291 0 R -/A 813 0 R +/A 816 0 R >> endobj 291 0 obj << -/Title ($1N1632) -/Parent 285 0 R -/Prev 290 0 R +/Title ($1N9) +/Parent 290 0 R /Next 292 0 R -/A 815 0 R +/A 818 0 R >> endobj 292 0 obj << -/Title ($1N1664) -/Parent 285 0 R +/Title ($1N50) +/Parent 290 0 R /Prev 291 0 R /Next 293 0 R -/A 817 0 R +/A 820 0 R >> endobj 293 0 obj << -/Title ($1N1692) -/Parent 285 0 R +/Title ($1N1544) +/Parent 290 0 R /Prev 292 0 R /Next 294 0 R -/A 819 0 R +/A 822 0 R >> endobj 294 0 obj << -/Title ($1N1712) -/Parent 285 0 R +/Title ($1N1564) +/Parent 290 0 R /Prev 293 0 R /Next 295 0 R -/A 821 0 R +/A 824 0 R >> endobj 295 0 obj << -/Title ($1N1724) -/Parent 285 0 R +/Title ($1N1604) +/Parent 290 0 R /Prev 294 0 R /Next 296 0 R -/A 823 0 R +/A 826 0 R >> endobj 296 0 obj << -/Title ($1N1758) -/Parent 285 0 R +/Title ($1N1632) +/Parent 290 0 R /Prev 295 0 R /Next 297 0 R -/A 825 0 R +/A 828 0 R >> endobj 297 0 obj << -/Title ($1N1786) -/Parent 285 0 R +/Title ($1N1664) +/Parent 290 0 R /Prev 296 0 R /Next 298 0 R -/A 827 0 R +/A 830 0 R >> endobj 298 0 obj << -/Title ($1N1806) -/Parent 285 0 R +/Title ($1N1692) +/Parent 290 0 R /Prev 297 0 R /Next 299 0 R -/A 829 0 R +/A 832 0 R >> endobj 299 0 obj << -/Title ($1N1830) -/Parent 285 0 R +/Title ($1N1712) +/Parent 290 0 R /Prev 298 0 R /Next 300 0 R -/A 831 0 R +/A 834 0 R >> endobj 300 0 obj << -/Title ($1N1878) -/Parent 285 0 R +/Title ($1N1724) +/Parent 290 0 R /Prev 299 0 R /Next 301 0 R -/A 833 0 R +/A 836 0 R >> endobj 301 0 obj << -/Title ($1N5358) -/Parent 285 0 R +/Title ($1N1758) +/Parent 290 0 R /Prev 300 0 R /Next 302 0 R -/A 835 0 R +/A 838 0 R >> endobj 302 0 obj << -/Title ($1N5478) -/Parent 285 0 R +/Title ($1N1786) +/Parent 290 0 R /Prev 301 0 R -/A 837 0 R +/Next 303 0 R +/A 840 0 R +>> +endobj + +303 0 obj +<< +/Title ($1N1806) +/Parent 290 0 R +/Prev 302 0 R +/Next 304 0 R +/A 842 0 R >> endobj 304 0 obj << -/Title ($1N17) -/Parent 303 0 R +/Title ($1N1830) +/Parent 290 0 R +/Prev 303 0 R /Next 305 0 R -/A 839 0 R +/A 844 0 R >> endobj 305 0 obj << -/Title ($1N42) -/Parent 303 0 R +/Title ($1N1878) +/Parent 290 0 R /Prev 304 0 R -/A 841 0 R +/Next 306 0 R +/A 846 0 R +>> +endobj + +306 0 obj +<< +/Title ($1N5358) +/Parent 290 0 R +/Prev 305 0 R +/Next 307 0 R +/A 848 0 R >> endobj 307 0 obj << -/Title ($1N16) -/Parent 306 0 R -/Next 308 0 R -/A 843 0 R +/Title ($1N5478) +/Parent 290 0 R +/Prev 306 0 R +/A 850 0 R >> endobj -308 0 obj +309 0 obj << -/Title ($1N43) -/Parent 306 0 R -/Prev 307 0 R -/A 845 0 R +/Title ($1N17) +/Parent 308 0 R +/Next 310 0 R +/A 852 0 R >> endobj 310 0 obj << -/Title ($1N1498) -/Parent 309 0 R -/Next 311 0 R -/A 847 0 R +/Title ($1N42) +/Parent 308 0 R +/Prev 309 0 R +/A 854 0 R >> endobj -311 0 obj +312 0 obj << -/Title ($1N1512) -/Parent 309 0 R -/Prev 310 0 R -/A 849 0 R +/Title ($1N16) +/Parent 311 0 R +/Next 313 0 R +/A 856 0 R >> endobj 313 0 obj << -/Title ($1N1496) -/Parent 312 0 R -/Next 314 0 R -/A 851 0 R +/Title ($1N43) +/Parent 311 0 R +/Prev 312 0 R +/A 858 0 R >> endobj -314 0 obj +315 0 obj << -/Title ($1N1510) -/Parent 312 0 R -/Prev 313 0 R -/A 853 0 R +/Title ($1N1498) +/Parent 314 0 R +/Next 316 0 R +/A 860 0 R >> endobj 316 0 obj << -/Title ($1N18) -/Parent 315 0 R -/Next 317 0 R -/A 855 0 R ->> -endobj - -317 0 obj -<< -/Title ($1N41) -/Parent 315 0 R -/Prev 316 0 R -/Next 318 0 R -/A 857 0 R +/Title ($1N1512) +/Parent 314 0 R +/Prev 315 0 R +/A 862 0 R >> endobj 318 0 obj << -/Title ($1N1522) -/Parent 315 0 R -/Prev 317 0 R -/A 859 0 R +/Title ($1N1496) +/Parent 317 0 R +/Next 319 0 R +/A 864 0 R >> endobj -861 0 obj +319 0 obj +<< +/Title ($1N1510) +/Parent 317 0 R +/Prev 318 0 R +/A 866 0 R +>> +endobj + +321 0 obj +<< +/Title ($1N18) +/Parent 320 0 R +/Next 322 0 R +/A 868 0 R +>> +endobj + +322 0 obj +<< +/Title ($1N41) +/Parent 320 0 R +/Prev 321 0 R +/Next 323 0 R +/A 870 0 R +>> +endobj + +323 0 obj +<< +/Title ($1N1522) +/Parent 320 0 R +/Prev 322 0 R +/A 872 0 R +>> +endobj + +874 0 obj << /Producer (jsPDF 0.0.0) -/CreationDate (D:20251108000128-00'00') +/CreationDate (D:20251204143819-00'00') >> endobj -862 0 obj +875 0 obj << /Type /Catalog /Pages 1 0 R /OpenAction [3 0 R /FitH null] /PageLayout /OneColumn -/Outlines 12 0 R +/Outlines 13 0 R >> endobj xref -0 863 +0 876 0000000000 65535 f -0000334529 00000 n -0000396345 00000 n +0000339911 00000 n +0000469171 00000 n 0000000015 00000 n 0000000125 00000 n -0000334586 00000 n -0000334751 00000 n -0000334930 00000 n -0000335046 00000 n -0000335215 00000 n -0000336259 00000 n -0000393129 00000 n -0000527015 00000 n -0000527093 00000 n -0000527300 00000 n -0000527196 00000 n -0000527411 00000 n -0000531215 00000 n -0000531292 00000 n -0000531383 00000 n -0000531474 00000 n -0000531565 00000 n -0000531658 00000 n -0000531751 00000 n -0000531844 00000 n -0000531937 00000 n +0000339968 00000 n +0000340133 00000 n +0000340312 00000 n +0000340428 00000 n +0000340597 00000 n +0000341641 00000 n +0000398511 00000 n +0000401727 00000 n +0000601787 00000 n +0000601865 00000 n +0000602072 00000 n +0000601968 00000 n +0000602183 00000 n +0000605987 00000 n +0000606064 00000 n +0000606155 00000 n +0000606246 00000 n +0000606337 00000 n +0000606430 00000 n +0000606523 00000 n +0000606616 00000 n +0000606709 00000 n +0000606802 00000 n +0000606895 00000 n +0000606988 00000 n +0000607081 00000 n +0000607174 00000 n +0000607267 00000 n +0000607360 00000 n +0000607453 00000 n +0000607546 00000 n +0000607639 00000 n +0000602285 00000 n +0000607720 00000 n +0000607798 00000 n +0000607889 00000 n +0000602399 00000 n +0000607969 00000 n +0000602514 00000 n +0000608033 00000 n +0000602630 00000 n +0000608098 00000 n +0000602746 00000 n +0000608163 00000 n +0000608241 00000 n +0000608332 00000 n +0000608425 00000 n +0000602860 00000 n +0000608505 00000 n +0000608585 00000 n +0000608678 00000 n +0000608771 00000 n +0000608864 00000 n +0000608957 00000 n +0000602975 00000 n +0000609037 00000 n +0000609115 00000 n +0000609206 00000 n +0000609299 00000 n +0000609392 00000 n +0000609485 00000 n +0000609578 00000 n +0000609671 00000 n +0000609764 00000 n +0000609857 00000 n +0000609950 00000 n +0000610043 00000 n +0000610136 00000 n +0000610229 00000 n +0000610322 00000 n +0000603091 00000 n +0000610402 00000 n +0000610480 00000 n +0000610571 00000 n +0000610664 00000 n +0000610757 00000 n +0000610850 00000 n +0000610943 00000 n +0000611036 00000 n +0000611129 00000 n +0000611222 00000 n +0000611315 00000 n +0000611408 00000 n +0000611501 00000 n +0000611594 00000 n +0000611687 00000 n +0000603205 00000 n +0000611767 00000 n +0000611847 00000 n +0000611940 00000 n +0000612033 00000 n +0000612126 00000 n +0000612219 00000 n +0000612312 00000 n +0000612405 00000 n +0000612499 00000 n +0000612594 00000 n +0000612690 00000 n +0000612786 00000 n +0000603323 00000 n +0000612868 00000 n +0000603442 00000 n +0000612937 00000 n +0000613020 00000 n +0000613117 00000 n +0000613214 00000 n +0000603568 00000 n +0000613297 00000 n +0000613380 00000 n +0000613477 00000 n +0000603692 00000 n +0000613560 00000 n +0000613643 00000 n +0000613740 00000 n +0000603817 00000 n +0000613823 00000 n +0000613906 00000 n +0000614003 00000 n +0000603943 00000 n +0000614086 00000 n +0000614166 00000 n +0000614260 00000 n +0000614355 00000 n +0000614450 00000 n +0000614545 00000 n +0000614640 00000 n +0000614735 00000 n +0000614830 00000 n +0000614925 00000 n +0000615020 00000 n +0000615115 00000 n +0000615210 00000 n +0000615305 00000 n +0000615400 00000 n +0000615495 00000 n +0000615592 00000 n +0000615689 00000 n +0000615786 00000 n +0000615883 00000 n +0000615980 00000 n +0000616077 00000 n +0000616174 00000 n +0000616271 00000 n +0000616368 00000 n +0000616465 00000 n +0000616562 00000 n +0000616659 00000 n +0000616756 00000 n +0000616853 00000 n +0000616950 00000 n +0000617047 00000 n +0000617144 00000 n +0000617241 00000 n +0000617338 00000 n +0000617435 00000 n +0000617532 00000 n +0000617629 00000 n +0000617726 00000 n +0000617823 00000 n +0000617920 00000 n +0000618017 00000 n +0000618114 00000 n +0000618211 00000 n +0000618308 00000 n +0000618405 00000 n +0000618502 00000 n +0000618599 00000 n +0000618696 00000 n +0000618793 00000 n +0000618890 00000 n +0000618987 00000 n +0000619084 00000 n +0000619181 00000 n +0000619278 00000 n +0000619375 00000 n +0000619472 00000 n +0000619569 00000 n +0000619666 00000 n +0000619763 00000 n +0000619860 00000 n +0000619957 00000 n +0000620054 00000 n +0000620151 00000 n +0000620248 00000 n +0000620345 00000 n +0000620442 00000 n +0000620539 00000 n +0000620636 00000 n +0000620733 00000 n +0000620830 00000 n +0000620927 00000 n +0000621024 00000 n +0000621121 00000 n +0000621218 00000 n +0000604063 00000 n +0000621302 00000 n +0000621383 00000 n +0000604184 00000 n +0000621464 00000 n +0000621545 00000 n +0000621642 00000 n +0000604305 00000 n +0000621726 00000 n +0000621807 00000 n +0000621904 00000 n +0000604426 00000 n +0000621988 00000 n +0000622069 00000 n +0000622164 00000 n +0000622259 00000 n +0000622356 00000 n +0000622453 00000 n +0000622550 00000 n +0000622647 00000 n +0000622744 00000 n +0000622841 00000 n +0000622938 00000 n +0000623035 00000 n +0000623132 00000 n +0000623229 00000 n +0000623326 00000 n +0000604546 00000 n +0000623409 00000 n +0000604670 00000 n +0000623478 00000 n +0000623558 00000 n +0000623653 00000 n +0000623750 00000 n +0000623847 00000 n +0000623944 00000 n +0000624041 00000 n +0000624138 00000 n +0000624235 00000 n +0000624332 00000 n +0000624429 00000 n +0000624526 00000 n +0000624623 00000 n +0000624720 00000 n +0000624817 00000 n +0000604791 00000 n +0000624900 00000 n +0000624980 00000 n +0000625075 00000 n +0000625172 00000 n +0000625269 00000 n +0000625366 00000 n +0000625463 00000 n +0000625560 00000 n +0000625657 00000 n +0000625754 00000 n +0000625851 00000 n +0000625948 00000 n +0000626045 00000 n +0000626142 00000 n +0000626239 00000 n +0000626336 00000 n +0000626433 00000 n +0000604912 00000 n +0000626516 00000 n +0000626597 00000 n +0000626692 00000 n +0000626787 00000 n +0000626884 00000 n +0000626981 00000 n +0000627078 00000 n +0000627175 00000 n +0000627272 00000 n +0000627369 00000 n +0000627466 00000 n +0000627563 00000 n +0000627660 00000 n +0000627757 00000 n +0000627854 00000 n +0000605033 00000 n +0000627937 00000 n +0000628018 00000 n +0000628113 00000 n +0000605153 00000 n +0000628196 00000 n +0000628276 00000 n +0000628371 00000 n +0000628466 00000 n +0000628563 00000 n +0000628660 00000 n +0000628757 00000 n +0000605273 00000 n +0000628840 00000 n +0000628920 00000 n +0000629015 00000 n +0000629112 00000 n +0000629209 00000 n +0000629306 00000 n +0000629403 00000 n +0000629500 00000 n +0000629597 00000 n +0000629694 00000 n +0000629791 00000 n +0000629888 00000 n +0000629985 00000 n +0000630082 00000 n +0000630179 00000 n +0000630276 00000 n +0000630373 00000 n +0000605393 00000 n +0000630456 00000 n +0000630537 00000 n +0000605512 00000 n +0000630618 00000 n +0000630699 00000 n +0000605631 00000 n +0000630780 00000 n +0000630863 00000 n +0000605756 00000 n +0000630946 00000 n +0000631029 00000 n +0000605881 00000 n +0000631112 00000 n +0000631193 00000 n +0000631288 00000 n +0000469307 00000 n +0000469371 00000 n +0000469817 00000 n +0000469881 00000 n +0000470275 00000 n +0000470339 00000 n +0000470748 00000 n +0000470812 00000 n +0000471245 00000 n +0000471309 00000 n +0000471715 00000 n +0000471779 00000 n +0000472172 00000 n +0000472236 00000 n +0000472668 00000 n +0000472732 00000 n +0000473125 00000 n +0000473189 00000 n +0000473633 00000 n +0000473697 00000 n +0000474141 00000 n +0000474205 00000 n +0000474634 00000 n +0000474698 00000 n +0000475105 00000 n +0000475169 00000 n +0000475564 00000 n +0000475628 00000 n +0000476035 00000 n +0000476099 00000 n +0000476503 00000 n +0000476567 00000 n +0000477007 00000 n +0000477071 00000 n +0000477529 00000 n +0000477593 00000 n +0000478051 00000 n +0000478115 00000 n +0000478555 00000 n +0000478619 00000 n +0000479015 00000 n +0000479079 00000 n +0000479497 00000 n +0000479561 00000 n +0000479956 00000 n +0000480020 00000 n +0000480450 00000 n +0000480514 00000 n +0000480958 00000 n +0000481022 00000 n +0000481453 00000 n +0000481517 00000 n +0000481937 00000 n +0000482001 00000 n +0000482422 00000 n +0000482486 00000 n +0000482894 00000 n +0000482958 00000 n +0000483366 00000 n +0000483430 00000 n +0000483828 00000 n +0000483892 00000 n +0000484288 00000 n +0000484352 00000 n +0000484759 00000 n +0000484823 00000 n +0000485258 00000 n +0000485322 00000 n +0000485732 00000 n +0000485796 00000 n +0000486206 00000 n +0000486270 00000 n +0000486679 00000 n +0000486743 00000 n +0000487154 00000 n +0000487218 00000 n +0000487624 00000 n +0000487688 00000 n +0000488083 00000 n +0000488147 00000 n +0000488562 00000 n +0000488626 00000 n +0000489055 00000 n +0000489119 00000 n +0000489524 00000 n +0000489588 00000 n +0000490017 00000 n +0000490081 00000 n +0000490536 00000 n +0000490600 00000 n +0000491029 00000 n +0000491093 00000 n +0000491510 00000 n +0000491574 00000 n +0000491969 00000 n +0000492033 00000 n +0000492440 00000 n +0000492504 00000 n +0000492897 00000 n +0000492961 00000 n +0000493390 00000 n +0000493454 00000 n +0000493897 00000 n +0000493961 00000 n +0000494406 00000 n +0000494470 00000 n +0000494865 00000 n +0000494929 00000 n +0000495346 00000 n +0000495410 00000 n +0000495827 00000 n +0000495891 00000 n +0000496320 00000 n +0000496384 00000 n +0000496789 00000 n +0000496853 00000 n +0000497271 00000 n +0000497335 00000 n +0000497790 00000 n +0000497854 00000 n +0000498249 00000 n +0000498313 00000 n +0000498730 00000 n +0000498794 00000 n +0000499189 00000 n +0000499253 00000 n +0000499660 00000 n +0000499724 00000 n +0000500119 00000 n +0000500183 00000 n +0000500600 00000 n +0000500664 00000 n +0000501093 00000 n +0000501157 00000 n +0000501623 00000 n +0000501687 00000 n +0000502105 00000 n +0000502169 00000 n +0000502613 00000 n +0000502677 00000 n +0000503084 00000 n +0000503148 00000 n +0000503592 00000 n +0000503656 00000 n +0000504051 00000 n +0000504115 00000 n +0000504522 00000 n +0000504586 00000 n +0000504981 00000 n +0000505045 00000 n +0000505463 00000 n +0000505527 00000 n +0000505954 00000 n +0000506018 00000 n +0000506447 00000 n +0000506511 00000 n +0000506906 00000 n +0000506970 00000 n +0000507377 00000 n +0000507441 00000 n +0000507859 00000 n +0000507923 00000 n +0000508318 00000 n +0000508382 00000 n +0000508814 00000 n +0000508878 00000 n +0000509283 00000 n +0000509347 00000 n +0000509742 00000 n +0000509806 00000 n +0000510238 00000 n +0000510302 00000 n +0000510746 00000 n +0000510810 00000 n +0000511242 00000 n +0000511306 00000 n +0000511775 00000 n +0000511839 00000 n +0000512283 00000 n +0000512347 00000 n +0000512740 00000 n +0000512804 00000 n +0000513234 00000 n +0000513298 00000 n +0000513694 00000 n +0000513758 00000 n +0000514181 00000 n +0000514245 00000 n +0000514642 00000 n +0000514706 00000 n +0000515161 00000 n +0000515225 00000 n +0000515622 00000 n +0000515686 00000 n +0000516132 00000 n +0000516196 00000 n +0000516615 00000 n +0000516679 00000 n +0000517111 00000 n +0000517175 00000 n +0000517605 00000 n +0000517669 00000 n +0000518102 00000 n +0000518166 00000 n +0000518574 00000 n +0000518638 00000 n +0000519048 00000 n +0000519112 00000 n +0000519553 00000 n +0000519617 00000 n +0000520020 00000 n +0000520084 00000 n +0000520504 00000 n +0000520568 00000 n +0000521000 00000 n +0000521064 00000 n +0000521533 00000 n +0000521597 00000 n +0000522028 00000 n +0000522092 00000 n +0000522507 00000 n +0000522571 00000 n +0000522964 00000 n +0000523028 00000 n +0000523457 00000 n +0000523521 00000 n +0000523927 00000 n +0000523991 00000 n +0000524408 00000 n +0000524472 00000 n +0000524887 00000 n +0000524951 00000 n +0000525383 00000 n +0000525447 00000 n +0000525842 00000 n +0000525906 00000 n +0000526347 00000 n +0000526411 00000 n +0000526826 00000 n +0000526890 00000 n +0000527307 00000 n +0000527371 00000 n +0000527788 00000 n +0000527852 00000 n +0000528269 00000 n +0000528333 00000 n +0000528728 00000 n +0000528792 00000 n +0000529187 00000 n +0000529251 00000 n +0000529646 00000 n +0000529710 00000 n +0000530103 00000 n +0000530167 00000 n +0000530574 00000 n +0000530638 00000 n +0000531067 00000 n +0000531131 00000 n +0000531548 00000 n +0000531612 00000 n 0000532030 00000 n -0000532123 00000 n -0000532216 00000 n -0000532309 00000 n -0000532402 00000 n -0000532495 00000 n -0000532588 00000 n -0000532681 00000 n -0000532774 00000 n -0000527513 00000 n -0000532854 00000 n -0000532932 00000 n -0000533023 00000 n -0000527627 00000 n -0000533103 00000 n -0000527742 00000 n -0000533167 00000 n -0000527858 00000 n -0000533232 00000 n -0000527974 00000 n -0000533297 00000 n -0000533375 00000 n -0000533466 00000 n -0000533559 00000 n -0000528088 00000 n -0000533639 00000 n -0000533719 00000 n -0000533812 00000 n -0000533905 00000 n -0000533998 00000 n -0000534091 00000 n -0000528203 00000 n -0000534171 00000 n -0000534249 00000 n -0000534340 00000 n -0000534433 00000 n -0000534526 00000 n -0000534619 00000 n -0000534712 00000 n -0000534805 00000 n -0000534898 00000 n -0000534991 00000 n -0000535084 00000 n -0000535177 00000 n -0000535270 00000 n -0000535363 00000 n -0000535456 00000 n -0000528319 00000 n -0000535536 00000 n -0000535614 00000 n -0000535705 00000 n -0000535798 00000 n -0000535891 00000 n -0000535984 00000 n -0000536077 00000 n -0000536170 00000 n -0000536263 00000 n -0000536356 00000 n -0000536449 00000 n -0000536542 00000 n -0000536635 00000 n -0000536728 00000 n -0000536821 00000 n -0000528433 00000 n -0000536901 00000 n -0000536981 00000 n -0000537074 00000 n -0000537167 00000 n -0000537260 00000 n -0000537353 00000 n -0000537446 00000 n -0000537539 00000 n -0000537632 00000 n -0000537725 00000 n -0000537819 00000 n -0000537914 00000 n -0000528551 00000 n -0000537996 00000 n -0000528670 00000 n -0000538065 00000 n -0000538148 00000 n -0000538245 00000 n -0000538342 00000 n -0000528796 00000 n -0000538425 00000 n -0000538508 00000 n -0000538605 00000 n -0000528920 00000 n -0000538688 00000 n -0000538771 00000 n -0000538868 00000 n -0000529045 00000 n -0000538951 00000 n -0000539034 00000 n -0000539131 00000 n -0000529171 00000 n -0000539214 00000 n -0000539294 00000 n -0000539388 00000 n -0000539483 00000 n -0000539578 00000 n -0000539673 00000 n -0000539768 00000 n -0000539863 00000 n -0000539958 00000 n -0000540053 00000 n -0000540148 00000 n -0000540243 00000 n -0000540338 00000 n -0000540433 00000 n -0000540528 00000 n -0000540623 00000 n -0000540720 00000 n -0000540817 00000 n -0000540914 00000 n -0000541011 00000 n -0000541108 00000 n -0000541205 00000 n -0000541302 00000 n -0000541399 00000 n -0000541496 00000 n -0000541593 00000 n -0000541690 00000 n -0000541787 00000 n -0000541884 00000 n -0000541981 00000 n -0000542078 00000 n -0000542175 00000 n -0000542272 00000 n -0000542369 00000 n -0000542466 00000 n -0000542563 00000 n -0000542660 00000 n -0000542757 00000 n -0000542854 00000 n -0000542951 00000 n -0000543048 00000 n -0000543145 00000 n -0000543242 00000 n -0000543339 00000 n -0000543436 00000 n -0000543533 00000 n -0000543630 00000 n -0000543727 00000 n -0000543824 00000 n -0000543921 00000 n -0000544018 00000 n -0000544115 00000 n -0000544212 00000 n -0000544309 00000 n -0000544406 00000 n -0000544503 00000 n -0000544600 00000 n -0000544697 00000 n -0000544794 00000 n -0000544891 00000 n -0000544988 00000 n -0000545085 00000 n -0000545182 00000 n -0000545279 00000 n -0000545376 00000 n -0000545473 00000 n -0000545570 00000 n -0000545667 00000 n -0000545764 00000 n -0000545861 00000 n -0000545958 00000 n -0000546055 00000 n -0000546152 00000 n -0000546249 00000 n -0000529291 00000 n -0000546332 00000 n -0000546413 00000 n -0000529412 00000 n -0000546494 00000 n -0000546575 00000 n -0000529533 00000 n -0000546656 00000 n -0000546737 00000 n -0000529654 00000 n -0000546818 00000 n -0000546899 00000 n -0000546994 00000 n -0000547089 00000 n -0000547186 00000 n -0000547283 00000 n -0000547380 00000 n -0000547477 00000 n -0000547574 00000 n -0000547671 00000 n -0000547768 00000 n -0000547865 00000 n -0000547962 00000 n -0000548059 00000 n -0000548156 00000 n -0000529774 00000 n -0000548239 00000 n -0000529898 00000 n -0000548308 00000 n -0000548388 00000 n -0000548483 00000 n -0000548580 00000 n -0000548677 00000 n -0000548774 00000 n -0000548871 00000 n -0000548968 00000 n -0000549065 00000 n -0000549162 00000 n -0000549259 00000 n -0000549356 00000 n -0000549453 00000 n -0000549550 00000 n -0000549647 00000 n -0000530019 00000 n -0000549730 00000 n -0000549810 00000 n -0000549905 00000 n -0000550002 00000 n -0000550099 00000 n -0000550196 00000 n -0000550293 00000 n -0000550390 00000 n -0000550487 00000 n -0000550584 00000 n -0000550681 00000 n -0000550778 00000 n -0000550875 00000 n -0000550972 00000 n -0000551069 00000 n -0000551166 00000 n -0000551263 00000 n -0000530140 00000 n -0000551346 00000 n -0000551427 00000 n -0000551522 00000 n -0000551617 00000 n -0000551714 00000 n -0000551811 00000 n -0000551908 00000 n -0000552005 00000 n -0000552102 00000 n -0000552199 00000 n -0000552296 00000 n -0000552393 00000 n -0000552490 00000 n -0000552587 00000 n -0000552684 00000 n -0000530261 00000 n -0000552767 00000 n -0000552848 00000 n -0000552943 00000 n -0000530381 00000 n -0000553026 00000 n -0000553106 00000 n -0000553201 00000 n -0000553296 00000 n -0000553393 00000 n -0000553490 00000 n -0000553587 00000 n -0000530501 00000 n -0000553670 00000 n -0000553750 00000 n -0000553845 00000 n -0000553942 00000 n -0000554039 00000 n -0000554136 00000 n -0000554233 00000 n -0000554330 00000 n -0000554427 00000 n -0000554524 00000 n -0000554621 00000 n -0000554718 00000 n -0000554815 00000 n -0000554912 00000 n -0000555009 00000 n -0000555106 00000 n -0000555203 00000 n -0000530621 00000 n -0000555286 00000 n -0000555367 00000 n -0000530740 00000 n -0000555448 00000 n -0000555529 00000 n -0000530859 00000 n -0000555610 00000 n -0000555693 00000 n -0000530984 00000 n -0000555776 00000 n -0000555859 00000 n -0000531109 00000 n -0000555942 00000 n -0000556023 00000 n -0000556118 00000 n -0000396470 00000 n -0000396534 00000 n -0000396980 00000 n -0000397044 00000 n -0000397438 00000 n -0000397502 00000 n -0000397911 00000 n -0000397975 00000 n -0000398408 00000 n -0000398472 00000 n -0000398878 00000 n -0000398942 00000 n -0000399335 00000 n -0000399399 00000 n -0000399831 00000 n -0000399895 00000 n -0000400288 00000 n -0000400352 00000 n -0000400796 00000 n -0000400860 00000 n -0000401304 00000 n -0000401368 00000 n -0000401797 00000 n -0000401861 00000 n -0000402268 00000 n -0000402332 00000 n -0000402727 00000 n -0000402791 00000 n -0000403198 00000 n -0000403262 00000 n -0000403666 00000 n -0000403730 00000 n -0000404170 00000 n -0000404234 00000 n -0000404692 00000 n -0000404756 00000 n -0000405214 00000 n -0000405278 00000 n -0000405674 00000 n -0000405738 00000 n -0000406156 00000 n -0000406220 00000 n -0000406615 00000 n -0000406679 00000 n -0000407109 00000 n -0000407173 00000 n -0000407617 00000 n -0000407681 00000 n -0000408112 00000 n -0000408176 00000 n -0000408596 00000 n -0000408660 00000 n -0000409081 00000 n -0000409145 00000 n -0000409553 00000 n -0000409617 00000 n -0000410025 00000 n -0000410089 00000 n -0000410487 00000 n -0000410551 00000 n -0000410947 00000 n -0000411011 00000 n -0000411418 00000 n -0000411482 00000 n -0000411917 00000 n -0000411981 00000 n -0000412391 00000 n -0000412455 00000 n -0000412865 00000 n -0000412929 00000 n -0000413338 00000 n -0000413402 00000 n -0000413813 00000 n -0000413877 00000 n -0000414283 00000 n -0000414347 00000 n -0000414742 00000 n -0000414806 00000 n -0000415221 00000 n -0000415285 00000 n -0000415714 00000 n -0000415778 00000 n -0000416183 00000 n -0000416247 00000 n -0000416676 00000 n -0000416740 00000 n -0000417195 00000 n -0000417259 00000 n -0000417688 00000 n -0000417752 00000 n -0000418169 00000 n -0000418233 00000 n -0000418628 00000 n -0000418692 00000 n -0000419099 00000 n -0000419163 00000 n -0000419556 00000 n -0000419620 00000 n -0000420049 00000 n -0000420113 00000 n -0000420556 00000 n -0000420620 00000 n -0000421065 00000 n -0000421129 00000 n -0000421524 00000 n -0000421588 00000 n -0000422005 00000 n -0000422069 00000 n -0000422486 00000 n -0000422550 00000 n -0000422979 00000 n -0000423043 00000 n -0000423448 00000 n -0000423512 00000 n -0000423930 00000 n -0000423994 00000 n -0000424449 00000 n -0000424513 00000 n -0000424908 00000 n -0000424972 00000 n -0000425389 00000 n -0000425453 00000 n -0000425848 00000 n -0000425912 00000 n -0000426319 00000 n -0000426383 00000 n -0000426778 00000 n -0000426842 00000 n -0000427259 00000 n -0000427323 00000 n -0000427752 00000 n -0000427816 00000 n -0000428282 00000 n -0000428346 00000 n -0000428764 00000 n -0000428828 00000 n -0000429272 00000 n -0000429336 00000 n -0000429743 00000 n -0000429807 00000 n -0000430251 00000 n -0000430315 00000 n -0000430710 00000 n -0000430774 00000 n -0000431181 00000 n -0000431245 00000 n -0000431640 00000 n -0000431704 00000 n -0000432122 00000 n -0000432186 00000 n -0000432613 00000 n -0000432677 00000 n -0000433106 00000 n -0000433170 00000 n -0000433565 00000 n -0000433629 00000 n -0000434036 00000 n -0000434100 00000 n -0000434518 00000 n -0000434582 00000 n -0000434977 00000 n -0000435041 00000 n -0000435473 00000 n -0000435537 00000 n -0000435942 00000 n -0000436006 00000 n -0000436401 00000 n -0000436465 00000 n -0000436897 00000 n -0000436961 00000 n -0000437405 00000 n -0000437469 00000 n -0000437901 00000 n -0000437965 00000 n -0000438434 00000 n -0000438498 00000 n -0000438942 00000 n -0000439006 00000 n -0000439399 00000 n -0000439463 00000 n -0000439893 00000 n -0000439957 00000 n -0000440353 00000 n -0000440417 00000 n -0000440840 00000 n -0000440904 00000 n -0000441301 00000 n -0000441365 00000 n -0000441820 00000 n -0000441884 00000 n -0000442281 00000 n -0000442345 00000 n -0000442791 00000 n -0000442855 00000 n -0000443274 00000 n -0000443338 00000 n -0000443770 00000 n -0000443834 00000 n -0000444264 00000 n -0000444328 00000 n -0000444761 00000 n -0000444825 00000 n -0000445233 00000 n -0000445297 00000 n -0000445707 00000 n -0000445771 00000 n -0000446212 00000 n -0000446276 00000 n -0000446679 00000 n -0000446743 00000 n -0000447163 00000 n -0000447227 00000 n -0000447659 00000 n -0000447723 00000 n -0000448192 00000 n -0000448256 00000 n -0000448687 00000 n -0000448751 00000 n -0000449166 00000 n -0000449230 00000 n -0000449623 00000 n -0000449687 00000 n -0000450116 00000 n -0000450180 00000 n -0000450586 00000 n -0000450650 00000 n -0000451067 00000 n -0000451131 00000 n -0000451546 00000 n -0000451610 00000 n -0000452042 00000 n -0000452106 00000 n -0000452501 00000 n -0000452565 00000 n -0000453006 00000 n -0000453070 00000 n -0000453485 00000 n -0000453549 00000 n -0000453966 00000 n -0000454030 00000 n -0000454447 00000 n -0000454511 00000 n -0000454928 00000 n -0000454992 00000 n -0000455387 00000 n -0000455451 00000 n -0000455846 00000 n -0000455910 00000 n -0000456305 00000 n -0000456369 00000 n -0000456762 00000 n -0000456826 00000 n -0000457233 00000 n -0000457297 00000 n -0000457726 00000 n -0000457790 00000 n -0000458207 00000 n -0000458271 00000 n -0000458689 00000 n -0000458753 00000 n -0000459193 00000 n -0000459257 00000 n -0000459712 00000 n -0000459776 00000 n -0000460231 00000 n -0000460295 00000 n -0000460724 00000 n -0000460788 00000 n -0000461204 00000 n -0000461268 00000 n -0000461661 00000 n -0000461725 00000 n -0000462130 00000 n -0000462194 00000 n -0000462626 00000 n -0000462690 00000 n -0000463097 00000 n -0000463161 00000 n -0000463568 00000 n -0000463632 00000 n -0000464024 00000 n -0000464088 00000 n -0000464505 00000 n -0000464569 00000 n -0000465009 00000 n -0000465073 00000 n -0000465480 00000 n -0000465544 00000 n -0000465973 00000 n -0000466037 00000 n -0000466432 00000 n -0000466496 00000 n -0000466891 00000 n -0000466955 00000 n -0000467350 00000 n -0000467414 00000 n -0000467809 00000 n -0000467873 00000 n -0000468268 00000 n -0000468332 00000 n -0000468739 00000 n -0000468803 00000 n -0000469198 00000 n -0000469262 00000 n -0000469657 00000 n -0000469721 00000 n -0000470116 00000 n -0000470180 00000 n -0000470575 00000 n -0000470639 00000 n -0000471032 00000 n -0000471096 00000 n -0000471489 00000 n -0000471553 00000 n -0000472022 00000 n -0000472086 00000 n -0000472530 00000 n -0000472594 00000 n -0000473038 00000 n -0000473102 00000 n -0000473553 00000 n -0000473617 00000 n -0000474036 00000 n -0000474100 00000 n -0000474507 00000 n -0000474571 00000 n -0000474964 00000 n -0000475028 00000 n -0000475458 00000 n -0000475522 00000 n -0000475957 00000 n -0000476021 00000 n -0000476452 00000 n -0000476516 00000 n -0000476928 00000 n -0000476992 00000 n -0000477400 00000 n -0000477464 00000 n -0000477887 00000 n -0000477951 00000 n -0000478370 00000 n -0000478434 00000 n -0000478843 00000 n -0000478907 00000 n -0000479340 00000 n -0000479404 00000 n -0000479815 00000 n -0000479879 00000 n -0000480272 00000 n -0000480336 00000 n -0000480731 00000 n -0000480795 00000 n -0000481190 00000 n -0000481254 00000 n -0000481671 00000 n -0000481735 00000 n -0000482128 00000 n -0000482192 00000 n -0000482599 00000 n -0000482663 00000 n -0000483070 00000 n -0000483134 00000 n -0000483541 00000 n -0000483605 00000 n -0000484045 00000 n -0000484109 00000 n -0000484504 00000 n -0000484568 00000 n -0000484975 00000 n -0000485039 00000 n -0000485490 00000 n -0000485554 00000 n -0000485961 00000 n -0000486025 00000 n -0000486445 00000 n -0000486509 00000 n -0000486931 00000 n -0000486995 00000 n -0000487388 00000 n -0000487452 00000 n -0000487906 00000 n -0000487970 00000 n -0000488387 00000 n -0000488451 00000 n -0000488880 00000 n -0000488944 00000 n -0000489349 00000 n -0000489413 00000 n -0000489820 00000 n -0000489884 00000 n -0000490328 00000 n -0000490392 00000 n -0000490799 00000 n -0000490863 00000 n -0000491280 00000 n -0000491344 00000 n -0000491776 00000 n -0000491840 00000 n -0000492247 00000 n -0000492311 00000 n -0000492706 00000 n -0000492770 00000 n -0000493199 00000 n -0000493263 00000 n -0000493695 00000 n -0000493759 00000 n -0000494193 00000 n -0000494257 00000 n -0000494686 00000 n -0000494750 00000 n -0000495165 00000 n -0000495229 00000 n -0000495683 00000 n -0000495747 00000 n -0000496187 00000 n -0000496251 00000 n -0000496667 00000 n -0000496731 00000 n -0000497126 00000 n -0000497190 00000 n -0000497622 00000 n -0000497686 00000 n -0000498115 00000 n -0000498179 00000 n -0000498608 00000 n -0000498672 00000 n -0000499067 00000 n -0000499131 00000 n -0000499526 00000 n -0000499590 00000 n -0000499985 00000 n -0000500049 00000 n -0000500500 00000 n -0000500564 00000 n -0000500959 00000 n -0000501023 00000 n -0000501455 00000 n -0000501519 00000 n -0000501939 00000 n -0000502003 00000 n -0000502424 00000 n -0000502488 00000 n -0000502910 00000 n -0000502974 00000 n -0000503380 00000 n -0000503444 00000 n -0000503876 00000 n -0000503940 00000 n -0000504335 00000 n -0000504399 00000 n -0000504850 00000 n -0000504914 00000 n -0000505341 00000 n -0000505405 00000 n -0000505837 00000 n -0000505901 00000 n -0000506308 00000 n -0000506372 00000 n -0000506790 00000 n -0000506854 00000 n -0000507283 00000 n -0000507347 00000 n -0000507742 00000 n -0000507806 00000 n -0000508201 00000 n -0000508265 00000 n -0000508705 00000 n -0000508769 00000 n -0000509201 00000 n -0000509265 00000 n -0000509699 00000 n -0000509763 00000 n -0000510153 00000 n -0000510217 00000 n -0000510611 00000 n -0000510675 00000 n -0000511082 00000 n -0000511146 00000 n -0000511556 00000 n -0000511620 00000 n -0000512038 00000 n -0000512102 00000 n -0000512557 00000 n -0000512621 00000 n -0000513087 00000 n -0000513151 00000 n -0000513558 00000 n -0000513622 00000 n -0000514029 00000 n -0000514093 00000 n -0000514502 00000 n -0000514566 00000 n -0000514972 00000 n -0000515036 00000 n -0000515453 00000 n -0000515517 00000 n -0000515934 00000 n -0000515998 00000 n -0000516415 00000 n -0000516479 00000 n -0000516872 00000 n -0000516936 00000 n -0000517343 00000 n -0000517407 00000 n -0000517873 00000 n -0000517937 00000 n -0000518344 00000 n -0000518408 00000 n -0000518848 00000 n -0000518912 00000 n -0000519305 00000 n -0000519369 00000 n -0000519787 00000 n -0000519851 00000 n -0000520283 00000 n -0000520347 00000 n -0000520776 00000 n -0000520840 00000 n -0000521235 00000 n -0000521299 00000 n -0000521731 00000 n -0000521795 00000 n -0000522205 00000 n -0000522269 00000 n -0000522675 00000 n -0000522739 00000 n -0000523151 00000 n -0000523215 00000 n -0000523623 00000 n -0000523687 00000 n -0000524083 00000 n -0000524147 00000 n -0000524554 00000 n -0000524618 00000 n -0000525025 00000 n -0000525089 00000 n -0000525507 00000 n -0000525571 00000 n -0000526017 00000 n -0000526081 00000 n -0000526523 00000 n -0000526587 00000 n -0000556201 00000 n -0000556288 00000 n +0000532094 00000 n +0000532534 00000 n +0000532598 00000 n +0000533053 00000 n +0000533117 00000 n +0000533572 00000 n +0000533636 00000 n +0000534065 00000 n +0000534129 00000 n +0000534545 00000 n +0000534609 00000 n +0000535002 00000 n +0000535066 00000 n +0000535471 00000 n +0000535535 00000 n +0000535967 00000 n +0000536031 00000 n +0000536438 00000 n +0000536502 00000 n +0000536909 00000 n +0000536973 00000 n +0000537365 00000 n +0000537429 00000 n +0000537846 00000 n +0000537910 00000 n +0000538350 00000 n +0000538414 00000 n +0000538821 00000 n +0000538885 00000 n +0000539314 00000 n +0000539378 00000 n +0000539773 00000 n +0000539837 00000 n +0000540232 00000 n +0000540296 00000 n +0000540691 00000 n +0000540755 00000 n +0000541150 00000 n +0000541214 00000 n +0000541609 00000 n +0000541673 00000 n +0000542080 00000 n +0000542144 00000 n +0000542539 00000 n +0000542603 00000 n +0000542998 00000 n +0000543062 00000 n +0000543457 00000 n +0000543521 00000 n +0000543916 00000 n +0000543980 00000 n +0000544373 00000 n +0000544437 00000 n +0000544830 00000 n +0000544894 00000 n +0000545363 00000 n +0000545427 00000 n +0000545871 00000 n +0000545935 00000 n +0000546379 00000 n +0000546443 00000 n +0000546894 00000 n +0000546958 00000 n +0000547377 00000 n +0000547441 00000 n +0000547848 00000 n +0000547912 00000 n +0000548305 00000 n +0000548369 00000 n +0000548799 00000 n +0000548863 00000 n +0000549306 00000 n +0000549370 00000 n +0000549805 00000 n +0000549869 00000 n +0000550300 00000 n +0000550364 00000 n +0000550787 00000 n +0000550851 00000 n +0000551270 00000 n +0000551334 00000 n +0000551726 00000 n +0000551790 00000 n +0000552202 00000 n +0000552266 00000 n +0000552674 00000 n +0000552738 00000 n +0000553142 00000 n +0000553206 00000 n +0000553615 00000 n +0000553679 00000 n +0000554112 00000 n +0000554176 00000 n +0000554587 00000 n +0000554651 00000 n +0000555044 00000 n +0000555108 00000 n +0000555503 00000 n +0000555567 00000 n +0000555962 00000 n +0000556026 00000 n +0000556443 00000 n +0000556507 00000 n +0000556900 00000 n +0000556964 00000 n +0000557371 00000 n +0000557435 00000 n +0000557842 00000 n +0000557906 00000 n +0000558313 00000 n +0000558377 00000 n +0000558817 00000 n +0000558881 00000 n +0000559276 00000 n +0000559340 00000 n +0000559747 00000 n +0000559811 00000 n +0000560262 00000 n +0000560326 00000 n +0000560733 00000 n +0000560797 00000 n +0000561217 00000 n +0000561281 00000 n +0000561703 00000 n +0000561767 00000 n +0000562160 00000 n +0000562224 00000 n +0000562678 00000 n +0000562742 00000 n +0000563159 00000 n +0000563223 00000 n +0000563652 00000 n +0000563716 00000 n +0000564121 00000 n +0000564185 00000 n +0000564592 00000 n +0000564656 00000 n +0000565100 00000 n +0000565164 00000 n +0000565571 00000 n +0000565635 00000 n +0000566052 00000 n +0000566116 00000 n +0000566548 00000 n +0000566612 00000 n +0000567019 00000 n +0000567083 00000 n +0000567478 00000 n +0000567542 00000 n +0000567971 00000 n +0000568035 00000 n +0000568467 00000 n +0000568531 00000 n +0000568965 00000 n +0000569029 00000 n +0000569458 00000 n +0000569522 00000 n +0000569937 00000 n +0000570001 00000 n +0000570455 00000 n +0000570519 00000 n +0000570959 00000 n +0000571023 00000 n +0000571439 00000 n +0000571503 00000 n +0000571898 00000 n +0000571962 00000 n +0000572394 00000 n +0000572458 00000 n +0000572887 00000 n +0000572951 00000 n +0000573380 00000 n +0000573444 00000 n +0000573839 00000 n +0000573903 00000 n +0000574298 00000 n +0000574362 00000 n +0000574757 00000 n +0000574821 00000 n +0000575272 00000 n +0000575336 00000 n +0000575731 00000 n +0000575795 00000 n +0000576227 00000 n +0000576291 00000 n +0000576711 00000 n +0000576775 00000 n +0000577196 00000 n +0000577260 00000 n +0000577682 00000 n +0000577746 00000 n +0000578152 00000 n +0000578216 00000 n +0000578648 00000 n +0000578712 00000 n +0000579107 00000 n +0000579171 00000 n +0000579622 00000 n +0000579686 00000 n +0000580113 00000 n +0000580177 00000 n +0000580609 00000 n +0000580673 00000 n +0000581080 00000 n +0000581144 00000 n +0000581562 00000 n +0000581626 00000 n +0000582055 00000 n +0000582119 00000 n +0000582514 00000 n +0000582578 00000 n +0000582973 00000 n +0000583037 00000 n +0000583477 00000 n +0000583541 00000 n +0000583973 00000 n +0000584037 00000 n +0000584471 00000 n +0000584535 00000 n +0000584925 00000 n +0000584989 00000 n +0000585383 00000 n +0000585447 00000 n +0000585854 00000 n +0000585918 00000 n +0000586328 00000 n +0000586392 00000 n +0000586810 00000 n +0000586874 00000 n +0000587329 00000 n +0000587393 00000 n +0000587859 00000 n +0000587923 00000 n +0000588330 00000 n +0000588394 00000 n +0000588801 00000 n +0000588865 00000 n +0000589274 00000 n +0000589338 00000 n +0000589744 00000 n +0000589808 00000 n +0000590225 00000 n +0000590289 00000 n +0000590706 00000 n +0000590770 00000 n +0000591187 00000 n +0000591251 00000 n +0000591644 00000 n +0000591708 00000 n +0000592115 00000 n +0000592179 00000 n +0000592645 00000 n +0000592709 00000 n +0000593116 00000 n +0000593180 00000 n +0000593620 00000 n +0000593684 00000 n +0000594077 00000 n +0000594141 00000 n +0000594559 00000 n +0000594623 00000 n +0000595055 00000 n +0000595119 00000 n +0000595548 00000 n +0000595612 00000 n +0000596007 00000 n +0000596071 00000 n +0000596503 00000 n +0000596567 00000 n +0000596977 00000 n +0000597041 00000 n +0000597447 00000 n +0000597511 00000 n +0000597923 00000 n +0000597987 00000 n +0000598395 00000 n +0000598459 00000 n +0000598855 00000 n +0000598919 00000 n +0000599326 00000 n +0000599390 00000 n +0000599797 00000 n +0000599861 00000 n +0000600279 00000 n +0000600343 00000 n +0000600789 00000 n +0000600853 00000 n +0000601295 00000 n +0000601359 00000 n +0000631371 00000 n +0000631458 00000 n trailer << -/Size 863 -/Root 862 0 R -/Info 861 0 R -/ID [ ] +/Size 876 +/Root 875 0 R +/Info 874 0 R +/ID [ ] >> startxref -556410 +631580 %%EOF diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md index de76286b2..4ffe625cc 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md @@ -2,9 +2,11 @@ # Notes +News 2025-12-04 - The GPS pin definitions have been changed!!! This has no material effect on current builds, but future builders may wish to review how they are using the wires. + ## General -The pinout is contained in the variant.h file, and a [generic schematic](./Schematic_Pro-Micro_Pinouts.pdf) is located in this directory. +The pinout is contained in the variant.h file, and a [generic schematic](./Schematic_Pro-Micro_Pinouts_2025-12-04.pdf) is located in this directory. This variant is suitable for both TCXO and XTAL types of modules. The old XTAL variant has been removed to reduce confusion. diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h index fee8ee88e..7eeb26e65 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h @@ -30,8 +30,8 @@ NRF52 PRO MICRO PIN ASSIGNMENT | Gnd   |             |   | reset   |             | | | Gnd   |             |   | ext_vcc | *see 0.13   | | | P0.17 | RXEN       |   | P0.31   | BATTERY_PIN | | -| P0.20 | GPS_RX     |   | P0.29   | BUSY         | DIO0 | -| P0.22 | GPS_TX     |   | P0.02   | MISO | MISO | +| P0.20 | GPS_TX     |   | P0.29   | BUSY         | DIO0 | +| P0.22 | GPS_RX     |   | P0.02   | MISO | MISO | | P0.24 | GPS_EN     |   | P1.15   | MOSI         | MOSI | | P1.00 | BUTTON_PIN |   | P1.13   | CS           | CS   | | P0.11 | SCL         |   | P1.11   | SCK         | SCK | @@ -90,16 +90,16 @@ NRF52 PRO MICRO PIN ASSIGNMENT #define BUTTON_PIN (32 + 0) // P1.00 // GPS -#define PIN_GPS_TX (0 + 22) // P0.22 -#define PIN_GPS_RX (0 + 20) // P0.20 +#define PIN_GPS_TX (0 + 20) // P0.20 - This is data from the MCU +#define PIN_GPS_RX (0 + 22) // P0.22 - This is data from the GNSS #define PIN_GPS_EN (0 + 24) // P0.24 #define GPS_UBLOX // define GPS_DEBUG // UART interfaces -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL1_TX PIN_GPS_TX +#define PIN_SERIAL1_RX PIN_GPS_RX #define PIN_SERIAL2_RX (0 + 6) // P0.06 #define PIN_SERIAL2_TX (0 + 8) // P0.08 diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h index 39cbc8f01..143d20459 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h @@ -116,13 +116,13 @@ No longer populated on PCB #define PIN_GPS_PPS (32 + 4) // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 7) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 5) // This is for bits going TOWARDS the GPS #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN // PCF8563 RTC Module #define PCF8563_RTC 0x51 diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index b6082fdc6..03c5aafd2 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -21,8 +21,8 @@ /** Master clock frequency */ #define VARIANT_MCK (64000000ul) -#define USE_LFXO // Board uses 32khz crystal for LF - +//#define USE_LFXO // Board uses 32khz crystal for LF +#define USE_LFRC // Board uses RC for LF /*---------------------------------------------------------------------------- * Headers *----------------------------------------------------------------------------*/ @@ -167,13 +167,13 @@ No longer populated on PCB #define PIN_GPS_PPS (32 + 4) // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 7) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 5) // This is for bits going TOWARDS the GPS #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN // PCF8563 RTC Module #define PCF8563_RTC 0x51 @@ -211,7 +211,8 @@ No longer populated on PCB #define ADC_MULTIPLIER (4.916F) // rf52840 AIN2 = Pin 4 -#define BATTERY_LPCOMP_INPUT NRF_LPCOMP_INPUT_2 +// commented out due to power leakage of 2.9mA in shutdown state see reported issue #8801 +// #define BATTERY_LPCOMP_INPUT NRF_LPCOMP_INPUT_2 //UNSAFE // We have AIN2 with a VBAT divider so AIN2 = VBAT * (100/490) // We have the device going deep sleep under 3.1V, which is AIN2 = 0.63V diff --git a/variants/nrf52840/heltec_mesh_pocket/variant.h b/variants/nrf52840/heltec_mesh_pocket/variant.h index e765dab66..f4f695b34 100644 --- a/variants/nrf52840/heltec_mesh_pocket/variant.h +++ b/variants/nrf52840/heltec_mesh_pocket/variant.h @@ -122,6 +122,12 @@ No longer populated on PCB #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (4.6425F) +#if defined(HELTEC_MESH_POCKET_BATTERY_5000) +#define OCV_ARRAY 4300, 4240, 4120, 4000, 3888, 3800, 3740, 3698, 3655, 3580, 3400 +#elif defined(HELTEC_MESH_POCKET_BATTERY_10000) +#define OCV_ARRAY 4100, 4060, 3960, 3840, 3729, 3625, 3550, 3500, 3420, 3345, 3100 +#endif + #undef HAS_GPS #define HAS_GPS 0 #define HAS_RTC 0 diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 7c43d8ba7..7a8fc579f 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -116,13 +116,13 @@ No longer populated on PCB #define PIN_GPS_PPS (32 + 4) // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 7) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 5) // This is for bits going TOWARDS the GPS #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN /* * SPI Interfaces diff --git a/variants/nrf52840/meshlink/platformio.ini b/variants/nrf52840/meshlink/platformio.ini index 2a4e27fe8..e0f4a2b9b 100644 --- a/variants/nrf52840/meshlink/platformio.ini +++ b/variants/nrf52840/meshlink/platformio.ini @@ -9,6 +9,30 @@ board_level = extra build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/meshlink -D MESHLINK + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshlink> +lib_deps = + ${nrf52840_base.lib_deps} +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds +;upload_protocol = jlink + +[env:meshlink_eink] +extends = nrf52840_base +board = meshlink +board_level = extra +;board_check = true +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/meshlink + -D MESHLINK + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 + -D USE_EINK -D EINK_DISPLAY_MODEL=GxEPD2_213_B74 -D EINK_WIDTH=250 -D EINK_HEIGHT=122 diff --git a/variants/nrf52840/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h index 54df03691..d1dba574f 100644 --- a/variants/nrf52840/meshlink/variant.h +++ b/variants/nrf52840/meshlink/variant.h @@ -121,8 +121,8 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_GPS_PPS (26) // Pulse per second input from the GPS -#define GPS_TX_PIN PIN_SERIAL1_RX // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN PIN_SERIAL1_TX // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN PIN_SERIAL1_TX // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN PIN_SERIAL1_RX // This is for bits going TOWARDS the GPS // #define GPS_THREAD_INTERVAL 50 diff --git a/variants/nrf52840/meshlink_eink/platformio.ini b/variants/nrf52840/meshlink_eink/platformio.ini deleted file mode 100644 index c0c0cb1dd..000000000 --- a/variants/nrf52840/meshlink_eink/platformio.ini +++ /dev/null @@ -1,31 +0,0 @@ -; MeshLink board developed by LoraItalia. NRF52840, eByte E22900M22S (Will also come with other frequencies), 25w MPPT solar charger (5v,12v,18v selectable), support for gps, buzzer, oled or e-ink display, 10 gpios, hardware watchdog -; https://www.loraitalia.it -; firmware for boards with a 250x122 e-ink display -[env:meshlink_eink] -extends = nrf52840_base -board = meshlink -board_level = extra -;board_check = true -build_flags = ${nrf52840_base.build_flags} - -I variants/nrf52840/meshlink_eink - -D MESHLINK - -D EINK_DISPLAY_MODEL=GxEPD2_213_B74 - -D EINK_WIDTH=250 - -D EINK_HEIGHT=122 - -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk - -D EINK_LIMIT_FASTREFRESH=5 ; How many consecutive fast-refreshes are permitted - -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates - -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates - -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated - -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. - -D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear - - -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 -debug_tool = jlink -; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) -; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds -;upload_protocol = jlink \ No newline at end of file diff --git a/variants/nrf52840/meshlink_eink/variant.cpp b/variants/nrf52840/meshlink_eink/variant.cpp deleted file mode 100644 index 81a5097c4..000000000 --- a/variants/nrf52840/meshlink_eink/variant.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#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() -{ - pinMode(PIN_LED1, OUTPUT); - digitalWrite(PIN_LED1, HIGH); // turn off the white led while booting - // otherwise it will stay lit for several seconds (could be annoying) - -#ifdef PIN_WD_EN - pinMode(PIN_WD_EN, OUTPUT); - digitalWrite(PIN_WD_EN, HIGH); // Enable the Watchdog at boot -#endif -} \ No newline at end of file diff --git a/variants/nrf52840/meshlink_eink/variant.h b/variants/nrf52840/meshlink_eink/variant.h deleted file mode 100644 index b605d7082..000000000 --- a/variants/nrf52840/meshlink_eink/variant.h +++ /dev/null @@ -1,153 +0,0 @@ -#ifndef _VARIANT_MESHLINK_ -#define _VARIANT_MESHLINK_ -#ifndef MESHLINK -#define MESHLINK -#endif -/** 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 (2) -#define NUM_ANALOG_OUTPUTS (0) - -#define BUTTON_PIN (-1) // If defined, this will be used for user button presses, -#define BUTTON_NEED_PULLUP - -// LEDs -#define PIN_LED1 (24) // Built in white led for status -#define LED_BLUE PIN_LED1 -#define LED_BUILTIN PIN_LED1 - -#define LED_STATE_ON 0 // State when LED is litted -#define LED_INVERTED 1 - -// Testing USB detection -// #define NRF_APM - -/* - * Analog pins - */ -#define PIN_A1 (3) // P0.03/AIN1 -#define ADC_RESOLUTION 14 - -// Other pins -// #define PIN_AREF (2) -// static const uint8_t AREF = PIN_AREF; - -/* - * Serial interfaces - */ -#define PIN_SERIAL1_RX (32 + 8) -#define PIN_SERIAL1_TX (7) - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 2 - -#define PIN_SPI_MISO (8) -#define PIN_SPI_MOSI (32 + 9) -#define PIN_SPI_SCK (11) - -#define PIN_SPI1_MISO (23) -#define PIN_SPI1_MOSI (21) -#define PIN_SPI1_SCK (19) - -static const uint8_t SS = 12; -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 USE_EINK - -#define PIN_EINK_CS (15) -#define PIN_EINK_BUSY (16) -#define PIN_EINK_DC (14) -#define PIN_EINK_RES (17) -#define PIN_EINK_SCLK (19) -#define PIN_EINK_MOSI (21) // also called SDI - -/* - * Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (1) -#define PIN_WIRE_SCL (27) - -// QSPI Pins -#define PIN_QSPI_SCK 19 -#define PIN_QSPI_CS 22 -#define PIN_QSPI_IO0 21 -#define PIN_QSPI_IO1 23 -#define PIN_QSPI_IO2 32 -#define PIN_QSPI_IO3 20 - -// On-board QSPI Flash -#define EXTERNAL_FLASH_DEVICES W25Q16JVUXIQ -#define EXTERNAL_FLASH_USE_QSPI - -#define USE_SX1262 -#define SX126X_CS (12) -#define SX126X_DIO1 (32 + 1) -#define SX126X_BUSY (32 + 3) -#define SX126X_RESET (6) -// #define SX126X_RXEN (13) -// 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 - -// pin 25 is used to enable or disable the watchdog. This pin has to be disabled when cpu is put to sleep -// otherwise the timer will expire and wd will reboot the cpu -#define PIN_WD_EN (25) - -#define PIN_GPS_PPS (26) // Pulse per second input from the GPS - -#define GPS_TX_PIN PIN_SERIAL1_RX // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN PIN_SERIAL1_TX // This is for bits going TOWARDS the GPS - -// #define GPS_THREAD_INTERVAL 50 - -// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press -#define PIN_GPS_EN (0) -#define GPS_EN_ACTIVE LOW - -#define PIN_BUZZER (31) // P0.31/AIN7 - -// Battery -// The battery sense is hooked to pin A0 (2) -#define BATTERY_PIN (2) -// 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.42 // fine tuning of voltage - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index 55aabe930..8d634ba60 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -64,6 +64,7 @@ extern "C" { #define INPUTDRIVER_ENCODER_UP 26 #define INPUTDRIVER_ENCODER_DOWN 4 #define INPUTDRIVER_ENCODER_BTN 28 +#define UPDOWN_LONG_PRESS_REPEAT_INTERVAL 150 #define CANNED_MESSAGE_MODULE_ENABLE 1 diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index d3e315f8b..96604c400 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -103,12 +103,12 @@ extern "C" { #define LR11X0_DIO_AS_RF_SWITCH // GPS -#define GPS_RX_PIN (0 + 19) // P0.19 -#define GPS_TX_PIN (0 + 20) // P0.20 +#define GPS_RX_PIN (0 + 20) // P0.20 +#define GPS_TX_PIN (0 + 19) // P0.19 #define GPS_EN_GPIO (32 + 1) // P1.01 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN #define PIN_BUZZER (0 + 22) // P0.22 diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index fd51cf9a1..fd837f66e 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -132,13 +132,13 @@ External serial flash W25Q16JV_IQ #define GPS_L76K #define PIN_GPS_STANDBY (0 + 13) // An output to wake GPS, low means allow sleep, high means force wake STANDBY -#define PIN_GPS_TX (0 + 9) // This is for bits going TOWARDS the CPU -#define PIN_GPS_RX (0 + 10) // This is for bits going TOWARDS the GPS +#define PIN_GPS_TX (0 + 10) // This is for bits going TOWARDS the CPU +#define PIN_GPS_RX (0 + 9) // This is for bits going TOWARDS the GPS // #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL1_TX PIN_GPS_TX +#define PIN_SERIAL1_RX PIN_GPS_RX // PCF8563 RTC Module #define PCF8563_RTC 0x51 diff --git a/arch/nrf52/nrf52.ini b/variants/nrf52840/nrf52.ini similarity index 85% rename from arch/nrf52/nrf52.ini rename to variants/nrf52840/nrf52.ini index e60d47ce7..5da1aebb5 100644 --- a/arch/nrf52/nrf52.ini +++ b/variants/nrf52840/nrf52.ini @@ -11,9 +11,13 @@ platform_packages = ; Don't renovate toolchain-gccarmnoneeabi platformio/toolchain-gccarmnoneeabi@~1.90301.0 -build_type = debug +extra_scripts = + ${env.extra_scripts} + extra_scripts/nrf52_extra.py + +build_type = release build_flags = - -include arch/nrf52/cpp_overrides/lfs_util.h + -include variants/nrf52840/cpp_overrides/lfs_util.h ${arduino_base.build_flags} -DSERIAL_BUFFER_SIZE=1024 -Wno-unused-variable @@ -21,6 +25,17 @@ build_flags = -DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818 -DMESHTASTIC_EXCLUDE_AUDIO=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -Os +build_unflags = + -Ofast + -Og + -ggdb3 + -ggdb2 + -g3 + -g2 + -g + -g1 + -g0 build_src_filter = ${arduino_base.build_src_filter} - - - - - - - - - - - diff --git a/arch/nrf52/nrf52832.ini b/variants/nrf52840/nrf52832.ini similarity index 100% rename from arch/nrf52/nrf52832.ini rename to variants/nrf52840/nrf52832.ini diff --git a/arch/nrf52/nrf52840.ini b/variants/nrf52840/nrf52840.ini similarity index 100% rename from arch/nrf52/nrf52840.ini rename to variants/nrf52840/nrf52840.ini diff --git a/variants/nrf52840/rak4631/platformio.ini b/variants/nrf52840/rak4631/platformio.ini index c95d477f9..0ef661af8 100644 --- a/variants/nrf52840/rak4631/platformio.ini +++ b/variants/nrf52840/rak4631/platformio.ini @@ -8,29 +8,18 @@ build_type = release build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/rak4631 -D RAK_4631 - -DEINK_DISPLAY_MODEL=GxEPD2_213_BN - -DEINK_WIDTH=250 - -DEINK_HEIGHT=122 + -DMESHTASTIC_USE_EINK_UI=0 -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 - -Os - -Wl,-Map=$BUILD_DIR/output.map -build_unflags = - -Ofast - -Og - -ggdb3 - -ggdb2 - -g3 - -g2 - -g - -g1 - -g0 build_src_filter = ${nrf52_base.build_src_filter} \ +<../variants/nrf52840/rak4631> \ + \ + \ - + + + \ + - \ + - \ + - lib_deps = ${nrf52840_base.lib_deps} ${nrf52_networking_base.lib_deps} diff --git a/variants/nrf52840/rak_wismeshtag/variant.h b/variants/nrf52840/rak_wismeshtag/variant.h index eba910dc1..159cabf07 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.h +++ b/variants/nrf52840/rak_wismeshtag/variant.h @@ -230,6 +230,7 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 +#define OCV_ARRAY 4240, 4112, 4029, 3970, 3906, 3846, 3824, 3802, 3776, 3650, 3072 #define RAK_4631 1 diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index f961ddf6e..a7b9290a5 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -300,6 +300,7 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define SPI_FREQUENCY 50000000 #define TFT_SPI_PORT SPI1 #define ST7789_CS WB_SPI_CS // Adds compatibility with the rest of the checking for a ST7789 TFT. +#define USE_TFTDISPLAY 1 #define SCREEN_ROTATE #define SCREEN_TRANSITION_FRAMERATE 5 diff --git a/variants/nrf52840/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h index 30d5c5888..7b7738547 100644 --- a/variants/nrf52840/seeed_solar_node/variant.h +++ b/variants/nrf52840/seeed_solar_node/variant.h @@ -110,18 +110,19 @@ static const uint8_t SCL = PIN_WIRE_SCL; #define ADC_MULTIPLIER 3.3 #define BATTERY_PIN PIN_VBAT // PIN_A7 #define AREF_VOLTAGE 3.3 +#define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // GPS L76KB // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #define GPS_L76K #ifdef GPS_L76K -#define PIN_GPS_RX D6 // 44 -#define PIN_GPS_TX D7 // 43 +#define PIN_GPS_TX D6 // 44 +#define PIN_GPS_RX D7 // 43 #define HAS_GPS 1 #define GPS_BAUDRATE 9600 #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL1_TX PIN_GPS_TX +#define PIN_SERIAL1_RX PIN_GPS_RX #define PIN_GPS_STANDBY D0 #define GPS_EN D18 // P1.05 #endif diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index f33d200b1..09fefc7f2 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -122,6 +122,8 @@ static const uint8_t SCL = PIN_WIRE_SCL; #define ADC_MULTIPLIER 2.0 #define BATTERY_PIN PIN_VBAT // PIN_A7 #define AREF_VOLTAGE 3.6 +#define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300 + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // GPS L76KB // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index a65500612..fb112a302 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -147,12 +147,12 @@ static const uint8_t SCK = PIN_SPI_SCK; */ // GPS L76K #ifdef GPS_L76K -#define PIN_GPS_RX D6 -#define PIN_GPS_TX D7 +#define PIN_GPS_TX D6 +#define PIN_GPS_RX D7 #define HAS_GPS 1 #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL1_TX PIN_GPS_TX +#define PIN_SERIAL1_RX PIN_GPS_RX #define PIN_GPS_STANDBY D0 #else #define PIN_SERIAL1_RX (-1) diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 4f3a53ebf..9a0cd0578 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -181,14 +181,14 @@ External serial flash WP25R1635FZUIL0 #define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake // Seems to be missing on this new board -// #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 9) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 8) // This is for bits going TOWARDS the GPS +#define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +#define GPS_TX_PIN (32 + 8) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 9) // This is for bits going TOWARDS the GPS #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN // PCF8563 RTC Module #define PCF8563_RTC 0x51 @@ -203,8 +203,6 @@ External serial flash WP25R1635FZUIL0 #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -#define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h index 403552ec0..ff63a4155 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.h +++ b/variants/nrf52840/tracker-t1000-e/variant.h @@ -22,7 +22,9 @@ /** Master clock frequency */ #define VARIANT_MCK (64000000ul) -#define USE_LFXO // Board uses 32khz crystal for LF +//#define USE_LFXO // Board uses 32khz crystal for LF + +#define USE_LFRC /*---------------------------------------------------------------------------- * Headers @@ -142,6 +144,8 @@ extern "C" { #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define OCV_ARRAY 4190, 4042, 3957, 3885, 3820, 3776, 3746, 3725, 3696, 3644, 3100 + // Buzzer #define BUZZER_EN_PIN (32 + 5) // P1.05, always high #define PIN_BUZZER (0 + 25) // P0.25, pwm output diff --git a/variants/nrf52840/wio-sdk-wm1110/platformio.ini b/variants/nrf52840/wio-sdk-wm1110/platformio.ini index 028129783..7c11ef6f6 100644 --- a/variants/nrf52840/wio-sdk-wm1110/platformio.ini +++ b/variants/nrf52840/wio-sdk-wm1110/platformio.ini @@ -4,11 +4,22 @@ extends = nrf52840_base board = wio-sdk-wm1110 extra_scripts = - ${env.extra_scripts} + ${nrf52840_base.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_unflags = + -Ofast + -Og + -ggdb3 + -ggdb2 + -g3 + -g2 + -g + -g1 + -g0 + -DUSBCON + -DUSE_TINYUSB build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/wio-sdk-wm1110 -Isrc/platform/nrf52/softdevice diff --git a/arch/rp2xx0/rp2040.ini b/variants/rp2040/rp2040.ini similarity index 100% rename from arch/rp2xx0/rp2040.ini rename to variants/rp2040/rp2040.ini diff --git a/arch/rp2xx0/rp2350.ini b/variants/rp2350/rp2350.ini similarity index 100% rename from arch/rp2xx0/rp2350.ini rename to variants/rp2350/rp2350.ini diff --git a/arch/stm32/stm32.ini b/variants/stm32/stm32.ini similarity index 98% rename from arch/stm32/stm32.ini rename to variants/stm32/stm32.ini index 1a9fd10ce..547b0502e 100644 --- a/arch/stm32/stm32.ini +++ b/variants/stm32/stm32.ini @@ -8,7 +8,7 @@ platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip extra_scripts = ${env.extra_scripts} - post:extra_scripts/extra_stm32.py + extra_scripts/stm32_extra.py build_type = release diff --git a/version.properties b/version.properties index 05d8a493f..8e40687e9 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 16 \ No newline at end of file +build = 17