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/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/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..8841ad1dc --- /dev/null +++ b/extra_scripts/esp32_extra.py @@ -0,0 +1,80 @@ +#!/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"))) +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 bf828b813..218b75443 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! 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 d30280d8b..b4ab57053 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 // ----------------------------------------------------------------------------- @@ -371,6 +394,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/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/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 96% rename from arch/esp32/esp32.ini rename to variants/esp32/esp32.ini index 08a547ca6..5171bc45c 100644 --- a/arch/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -2,10 +2,16 @@ [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 +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/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 e46f2356d..e4a6c0397 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -151,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 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&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„ Âa€0@˜ L&„== endstream endobj +12 0 obj +<< +/Type /XObject +/Subtype /Image +/Width 645 +/Height 455 +/ColorSpace /DeviceRGB +/BitsPerComponent 8 +/Length 67274 +/Filter /DCTDecode +>> +stream +ÿØÿàJFIFHHÿá¤ExifMM* +† Œ–ž(1¦2®BC‡iÂAppleiPhone 8HH11.2.62018:03:08 17:52:25‚š<‚�Dˆ"ˆ'd�0221�L�`‘’ +t’|’ +„’ +Œ’’ ’ +”’œ’|°¤’‘884’’884 0100 … Ç¢£¤¤¤¤¤2T¤3t¤4"z 2018:03:08 17:52:252018:03:08 17:52:25 oùÿÿö×o�dßç©2Apple iOSMM  .ìh  ¥ ¢  +‚ +š        ª bplist00O%&*07:33-0)+"ü ##&()ïøÿ ! + äìõó÷ýøø÷ÝãçæÝ�+NO‹àìñûüÐÜÚ×Â_�D@^´áææ÷ùM·Îζ?ntHqèÖÙãèð s¼®@ñY]²ÇÍ×àß4Šb j¦ Œ½ÄÌÑÑ M<!º#żÁÇÈÉ 4Ú% —ͱ·»½¾"'Mƒ¯­­²¸½!"/<Cs¡¯°µº#Ew«2()‰®±±'" N¤°p bplist00ÔUflagsUvalueYtimescaleUepoch£|¤3”;šÊ'-/8= ?ÿÿþ¯`MÍmeÿÿÝy!ÐÓw€q825s�d�d  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™ÕÑê‚ +descüecprtd#wtptˆrXYZœgXYZ°bXYZÄrTRCØ chadø,bTRCØ gTRCØ desc Display P3textCopyright Apple Inc., 2017XYZ óQÌXYZ ƒß=¿ÿÿÿ»XYZ J¿±7 +¹XYZ (8 ȹparaffò§ YÐ +[sf32 BÞÿÿó&“ý�ÿÿû¢ÿÿý£ÜÀnÿÀÇ…"ÿÄ + ÿĵ}!1AQa"q2�‘¡#B±ÁRÑð$3br‚ +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ + ÿĵw!1AQaq"2�B‘¡±Á #3RðbrÑ +$4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÛC ÿÛC  ÿÝ)ÿÚ ?þ·c¯Ø·À?ü jfÓ×í²"HLƒ,¤ŒŸ¥}ý ��¢ˆà‰Tc°o^(Xÿüß…Ilz±2“ÕŒWe»ÆsRù~Ô˜¥uò3 ‘çÒîzTø­8FiÆ›eE®¥cŽÃÆa�½E^òÿÏù5 Âç•✨³E$UÉ +™Jƒó©¼³Œ@™¨öÎBÒøà +pL–�‹œ‘C¢gq Ñj ƒ¸ÅXwì֡ѹQ›E]‹Iå¯ùÿõÕ‚ƒµ7Ë?çÿ×Qì~Õ‘¤k¸ŸëVj5LÔ•¥:™Tl)ihª•O;TE8RÑYÂØ®­&sKEoÈ™7 +(¢Ÿ"0)»O­>ŠÍ­KR]HAjªñàŽi÷×öºt~mÃüWÇ?¿lï„ÿ +wA®êQE"g)‘»�A\®”¤ívP„¥ª>¸uvásR‚À‚IÈæ¿!£ÿ‚±|ŠìFÚŒ^I#œøÏ=+íoƒÿ¶'Á/ŒÒ-Ÿ„µ«iî[þY‡Ÿ×"º>«4®Ñ×*Rz#ê„rXóS”Ën&’ÞHæ]é‚=EIC‰åÔm1ËØ-MP§Þ©ª¡Ì®ÃQES¤‚ì(¢Š=’ ±0}ih¢§Ø¥°s1¥rsJsÚ–ŠËêè9˜Ò=iÔQZBŠìÌhPîô¸¥¢©Ñ�`æb}hæ–Š=’[Äù©h¤$(Éè* Ũe¸†õ¬êkæÏÚ ö¢øcðÂóëÞ/Ôà·xÔ²Æî0œ§ð¯å?ö¬ÿ‚ôøŸWñÖ�ð^»‡æPß0ùI 2+|>]R²º=*=^‡ö¯|EðŸ‡`3j×IÁ<‘Ð~5ävÿµ'ÂYnc·MBÒp£räŸ~køñí/ÿ#ý¨ï è×wÖV�¡!(Ï~µ¯oðþ +e,züz…Ü�·Ÿ™ÉÚº§’B?•ÏF–_¬ÿBÍâ—‚|C2ÛØ]£9ã÷ú×ö’�°ÈûWùøøöŠý¼gÝE¢ÕäÔ.Ê€P‘ƒõ¯ØÙƒþ +ÿ¬Þêv:GÅ8M�èP“G—¼ç·®q<ªJîâ«–+{¬þ¥ðwgµ:¼;á?Æï üQÒ!Ôô{ˆåYTPç­{‚²°ÊœŠÊðjÒpvc²i(¢®R¹‘ v4|ØÍMEH¿yÙ©Ê$#žj@ƒ½sÞ ñV‰á›SuªÎ‘*÷f¦�.cZps÷b�òª:šË¸Õ,lŽé¥}s_•ÿ´§ü'à�ÁËÙtDÕ –uFÌhÁ›#ŽƒÞ¿¼ÿuøÁñ$ÉiðFž52ùÒ�Ã9ùpvÓÉêTÕh{xl¦M^Gõ™}ñÂö„÷Q‚§^c®~Ò ´2ë}{~XËêöæ¿“� jðPoŽ—æx.o£20r"Ê€9Ïô¯K¸ÿ‚x~ÖïT×®/^sþ·{ã‘ߨäû +sÊ©Çã™ßõ +1ø™ý-Û~Öß îå�lïc3¿˜„í]m—íà[¢ª÷1©sÆ]Æ¿—øv?í¤¤rÛKxfFɾïÐç½yÄÙoöÉøn[TÒ/¯!Ž,àJäœW4rú2ûdKIìÏì§Nø‰á}MÕ­¥7|Œsï]¬7–Ó°¸9÷¯à§Ã_¶�í[ð7ÄÚø’æêê8!� é_ªÿ³ßü‡E×õK-⾕:íGiÎçøóÓ¤òZëXjrUÁ%±ýD:å2ëPÇQ_/üý£¼#ñcOK­ú+ˆß’Êà�Ï5õeeMñœ�­s¸Éi#Š­' ÄÚzLÚ­F¬F~aTäš1Um±]ƒ“RRÛÎ3KPíЊ’¸QIÍ5>v.vÿÐþú)é×4ÁRGÞ¾‘ÑËf=º‚§a‘Šˆ©Ö“µb íšŠIã‰KÈÁ@õ¬ÝEXÔ[Ñ(®[Pñ^�¦Çæ]\"÷倪x÷ÃS¨e»NÚ³XúODÎøeX™Gž4Û^‡qLóùÿõVU¾¯auþ¦`Ù÷­pÁ†VºaQKcŽ¥)EÚJÃ|ÁþýUX¨¶ו™64rqR„šhCœš¨NÍC]Á]’Raih¦10* +±Q|€íb3éUÜ™_ Ñɧymþýtíƒ9úS§Üq¿R/-¿Ïÿ®�-¿Ïÿ®¥¢³TÐȼ¶ÿ?þº–Š*’°QLÙ;Š‚C…ÝéVŒb¹ßjcFÐ.õR»¼ˆ™ñþè&¹«"é«É#ñ þ +…û~Ið?ø@¼*âmVë ÄyyïÇjþA¾/~ÓŸ¼q㻫¯]ÝMw,üÄ»„`Àöâ¿I|}âcûUÁIn<â76ñÜnù�kúW;û|ø á—ìãûFiZ™l�cwåµÄ²(cž=:W«G–’äk[\údÔ’,x áw…5oYË«Ê!º¹€3±l2î³^¬ÂËý”|ogã Þ\Iažh*ø=x#Ûñ¯±¾9üHø£ü?Ò¢ð ì3jRP“É#¨ÇoJúÛIJ鸉ûKãr/9ã¶óVVUÈþÉàÔ}g•ÅÔVLÊë3öOþ ½ûfYþÕ? +¢½Õ×W´TYQñó cw¾kô׿�ø ß‹¼A¥|y¼ðD·j`O1zîàí_Ù}sU¢£RQ<œÎ*I �NÈôÿ?�%¹Qæ{F.G§ùüér¾”Ú(²hÇe}(ÊúSh¢È=£å£å¤¢‹ öŒwËGËïM¢‹ öŒwËïIòÒQG*´å£å¤¢—"h;hõ €)QN~jeq‡«ã¿Û7ö¢ð÷ìÁð®ëƤè—CœnlqŠúîîæ;;Y.¦8XÔ±>¿Šÿø,ïí5¬|lø×eðKÛî¡I*‘¶J»6TzмÎwžÈôòì?3ç{#ñ¿öêý¼>,þÔž-¾ñ­<–öi#¥¼�Ý=Hì~€Áÿ`ü|½¶ñÏŒÈxˆRCC ÷Î+Ìÿh?ø&v«ð�à·ÄýuâY!g–%?¼@FBªã–ú×ÿÌÿ‚…ë¼Eià«ÖštO´Hçn�æîÕïb*ª´Ùt=YNM{¬þç¾þÆ_¾ÚÁ…£Û«"`•Q†¯q_…>ÿ�y4¸c€q�Ê¿>ÿfÿø)GÁ�Ž—0èÚ®’ÝÆB2“´ƒéïõ¯³~4|vðï¯Kâ­NdH!PÍ&~\øú”eÍï-N.Y^×3¢„!†§w¹ùWû)ÿÁ|cñOøN~.Ë9IÝY„äïaÔýâN+÷«áüWö}ø[n«—Çhqž�ëôVxmâXmÔ"(À��¥U¾¿±Óâ2ݰP9¯3Ž«7y3Ë–>sz‡†þx3ÂùZŒP`cåP+«0Z€F£·Ý‹Œ¼9!Σ>¦¶¡¾´¾�Ì´mãÔWŸ*’}L}î£ÖÒÙÇÌŠ\·Š¼á}gN—ûZÊ@S˨?Ò»zâ¿'?à¨_·•�ì­ðâãLðó¤ÚÅÌeU ÊO·^;ñUF“›²F¸hÎu9bͯˆŸ±·ÀoŠâãM|Ë!Éd†¯ÇÚŸþ ªÛ}£[ðLQ–Øv<`ü›{`z×±ÿÁ"j?ˆß´¯Šµkývbßfuf�œõÅI馨\[y71«‡ääf»iã*áçdÏFS�'ï;ŸÂÁOŠ?ÿbo®•â‘-L23 +çÒ¿­ÿØÿö´ð·ÇŸ Ú\ÛN>Ñ"èÄ|¬8'×—þܰD޾5x2ïXÐí"ƒT³‰ÝPe€+ÇZþi¿f¯�¿?cßÚ~ø„„·–}ƒÌ%@¿„c¯­zõyqtùÒ´‘ßÉ E7mÏî`èjMŠËÍy§Â¿ØøÏÂZÅŒ«"Ï +È9ê+Ó¼±þýuó‡ËV§É.VUÙÎJ_, +³å­ZÿŸÿ]\è«´ŸÿUGùÿõU¯-Ïÿ®�-Ïÿ®€çGÿÑþúŠ•ëN�½4±$J@Ät¯ˆæI�M“ÓI BÎqÉ®_Ö¡Ñl^òc…E'šÎ¾6ãÍ#\>Uf©ÁjÌÿø»Mðž˜÷×ò*äúWæOÆÛ£LÒm3Ae’AÆòp¼SöÑý¬m£†}Æð"&C�{z +ü)ñßÅMgÅW�6˜Ì–àrq’ø¯æî?ñ:tjÊŽZŸéÇÑ£è{ à Ï;ƒ³Õ'·Ìýñ÷í¡ãK›™†¥ª  å##ÕãVß·µÓ ~cŒ îÿëWÄ�|?ñ¦½ Õ`Ó.®"Øeó6œçéÒ¸ï"(›j¦ÃÓi6AÁ¯Ã±¦×õTÑ´[�QÎ1³~B¿™OŠßð[;~Òÿð¬,×í� ’:¶Ò¾{Wô�ñ ¹ðmú³|¢$û`×ùlþÒš”ÒÿÁI¯m-Ý£t׃g„*Û�þ|b½Œž”+96¶:°#(´Ñþ¥? üV¾7ð>™â”C½·Yv“’ õ®ÝA'5àÿ³:y?¼0å·4–1;v'Å�Ú7á?Á�*çRñƱoiötgewáFN}+Ç�^gÊ‘ÁS)T´Qï4WãFµÿ�ý–4«û{D×-eWùŽçiéÉÀ'Ú¾°øû~|øó2ÚxcW‰f�3 ²‚î¼ �=kz”*E^Q7ž]V*ö>碹ÍcÄ6Z6“&¯;æ8×~G§­~uÝÁP¿g­+Ä÷^×/ŽͬÂ#¼€‰ÆÔÖ)Éü +æT°u'ð£ôÞŠóŸüIð§ÄAã�éÆu-¸�6ã¨>˜¯€~5ÁU?g¯‚¾2okw±=Ä{K“sÇÀ u#$ãµ]J¤¹b… %IIÅ-OÔ^õÎø¯E#ðÕîˆX§Ú¡x²;nR3^sðGã.…ñ·Á�øÓÃŒ^Òàn�±€Ê@ Œõö`Gݬd•Ú3³§=zçíªêú‡ì¥ÿ1Õ|IâØL1 §òšPGîË r}E{/Æ_ Ú~ߟ¶—¦è'̰˜¡‘�û§‘Þ¿z?à§ßðLý#ö—µ¹ø�àè#�\�›hÄ„Ä8d=ÉÀÈï_�ÿ²çˆ¼kÿúøÓs«øïA–úRâ6Že;�(áóŒ|½V2W�ÅcéiËž<Ñ>°ý¹ÿà–S|)ðÖ•ã_ ©h­B%ÀLḎêz×Ñvßµ€<)ûÿ¸Õ$1ßÅbÖ�o·’Ä›'Þ¸Ÿ�ðX|GðÓxJÇKžSp +²õmç#�Î@>Õùïðoöqø«ûRøêI$†ê-y2ÊXÎã�¡�E*KÙÅâ�ÚØ•wß{LÿÁ> ø�þ:k^;¼‚AgyÈý%±Ôõ9¯ìR¾Hý’?fŸ~Ï·Ñ´{T·�áE”¨åˆ¯Ò¾·®YbY¹4|þeWž¥ÂŠ(¦yáEPEPEPEPEPEPŽ¢–^ÿJmž )+£H>‡ŠþÐ^*�Áß 5�rW äÛ;Op+øRý‘¢?´§üÆmG^l?k TóÈpükû4ÿ‚„^\XþÎZĶÙÜ"~rGê+ø½ÿ‚BMkkÿ’ç?h¹¸ÜTtR­Éüýk¯ M,<ä}N\¿pì@ßðZÔ�ÁŸ²ÕÂèÀÇå!¯Pv㿱¯Áø&üßÀ¿´_ÃýWÆ>3ØÏ�»¾ñb»·uÏS_¼ÿð]õQû6Ý(%GæHV;€gÔ +þC?aoÚ³ö¯ðì—øc \ŸÆ¼/þ +Gû|_øEáyaigm=#GnìXaNLu5ÓMÓ©8Â÷’* + è~¾ÿÁ?l_ +i ßJñ=ÊCy摉´uëÞ¿©/ü~øuã-‹£êPM!8!1×8¯ó¤ÿ‚R~Ë^<øó®Oa£Ý\ØÛÆþ[¶âª’�ÎOsÀ¯ÓÏ�Z·Æ_ø'wÄÝ"}SS–öÆòæ4Ê3|ã#;¹Åe‹Ë)N¬£ïU„e&™ýšüNðö�ñÁšmàÞ²Âû6õ'b¿“?ë²þÏ?·TÞ‰…½¤·xž ý{×ôïû&|B²øµðƒIñ‚¿7êÍœz×òMûxx‘_þ +C¦Ûøv@ +ßæ1ò2`døÓӒö”çØéÀ´¯k¾Õ± C¨FþbHŠU½r3]}y�ÁÛIl¾évó6÷X#ÜùÚ3ú×§W-�žÅ«T�WÀ¿ðRoŒÏð[öYñ¿jOÚ'‹ÈŒ>ÿ¾ú¯Ä_ø.f…â?~Ê7ºO†ƒ¸Ùd@§€# ×UyÅy�9U5*Êçá·üCá‡ÇÚCSø¯â«/´EÑxš\¾Ìuå½súWöñioo§ÚGgl»c�B¨ô¿–ïø7ÓÂMáÿHuBVûÍ>zÆ~QŽÙ$þ5ýE$=ëLƧ5vvféÊV%¼»[;/¢)jþ\?n¿ø*ï�o>97ìùðA=¼¢Þâd>i`ÁÅGŸuÙ¼=ð·W¿€VÒS¹x+µIÏ׊ÿ;[�‰šö…ûeêZí¬]jR_´å°ÌF~¸=k\¯ ÊN] +Ê©.W#úÔuïÚÓÁþ:×oçX¢ýëÙùW¹ÿ +ýÿ‚{þÙ¶4³§Þ] åY61>ª0xëœ×å·ÄÏ~Öþ �éSÇoy©¸�ÏOjËÿ‚RþÏ_þ xÿUÔ¼`Ù¶I7IèH=ýjeF2ƒszž„éÅÁ¹ÒÇí ñçÁ¿³ÿÂýOâ/Н#¶Š  @i$…@z“_ǿĆŸà¡:î³ñ�Wߘ�'’¼Œ 9ÞsÔžäWoÿ ý¢þ5øßÄöŸ tè™t�:dB�«;�K ú�×µvmß +ü$ý—áµÒ¦7‹fb%£ÇU ·çœ +륆p¤�=ÙL:Š÷Ogÿ‚%øj…ÿ¼Kà™$/´ao “�é_Õ%¼D[¨¯à÷þ ‡ûnÚøoö€¼¸ñ$²$÷÷!·*w*Žçéœ×÷Gà?¯‹<9e­ªm[«xæÐ8¯'2ÁÊ�VåÔáÌ9•ΨÂ'¶x_ø��'ÿð\oƒºg€üc§|V¶‹Ë&,ïL.2Äsœ÷Ç5ýd¨ÚM4ÿðqŸˆ¬t/ƒºE´£^Æè +õáÇ$u#$Ư.“UcÔY-fªòkÁ%>6Oñ?à�—ß4¶±Xó�@ïŸQÓû:WóÝÿ!‚÷þæÞëP §Wˆ°ÁØŠÛ¸"¿¡ÐW!Z¬¬a›ÛÚh-QYHQEÿÒþú(¢—iÆká%Î�§�šøcöŸľðUÅ�«bYQ�'¿}Ç3ŒçÒ¿�ÿø)oÄ‹Ë]RãM,Á`%#�Xúšüÿ�3…ÀN¯dGý¸CÅT0Õã_ÈüiøÃñ÷ÇšõÁº“0$�òÔç½pÞWZí…¬0�%&Mã8ÊgœšÊŠÍ…œä³cêO&­Ú�NÇPö2òþeúŽ‚¿‚gRx¿mQß[ŸôQ<†•±àp¾î–_qýmü#øðåþØßõý/Á:?i)|_¡/�5iLÆ%Sã–Rz®OQÅ~üø âÜ�è€bØF8ÉížµöÀ [Ä?>9éº â˜f†tŽE=ÕÎ0?:ý‹Ã F+V3›÷YüŸô¬É²~&Ëñ}+´ðÿíK¤_ç|Úp[î�ùé_Û™5vð\ýOñ.ŒÁ£öËÂÿðEøÃÂþ"‰n#”Úä€v!m¤†EêsŸ¥~Hø�âWÆïø'oÇ�øD'¹žÖ;YA`åÀ�ç’;ŒzÿGo„r#xNÔ­ULol›Jò¿ttÿÕü>ÁÇQxnËã¶�wh‘o»”PíÆn+,³*ópš1ÂÏÚ]3ú¶ý�¿iWö•ý“añÆ· ’èÚì•”�›ä$÷ã­žÏí4úxÿ‚Œêz„³Qu•VÏÞ â¿®ø eíó~Èú†‘­;48�ãÞIJ� gµ!ß¶£=×ü\¶Ó8ä]\'¸:ïÉkrº±FX*v”�ôdð¯Åëo‚ÿ±‡ü{zq¦…›œãæØ[$ût¯àûö¡ý´¾0~Þ?®>xcTŸìz�Ђ1HeÞ9ÈÀëž+ú°ý®oµM'þ 7o÷M$Ñé³îéóÇ€£Ú¿�ÿø%O‰|áÿÚÃÃWÞ2‰Ö[ÉcË(ÎYò9ÈÏOZçʽÚ*-õ0Œ]›GïÁ?ø7âûâ»m[ÄW²Ã©µ¹džVÆóü.s�ǵ~Fþ׳¯í#ÿ´ø•¦êR^ÜM¦Êà­Ärü\§£šÿHÏϦϠYO¤•6¯ŠôØTcü¼ÁËx=¾鉨ª5ÚÈ¡Rž*²œÆ¥JªÕ1eةʧ,�Òø%wí{§~ÚŸ³Å½Î·sö�NŠd��ì…p2\z×ágü—þ å㿆šµßíðѦ0+›‡Šß/9àÛ­u¿ðl\ZÕ½·ˆ®.a‡!‰é†é_Ñ?üOÄÞÐÿf�v<¦¶|y݈SÈô§†«*éS†ÌÙÔ�:Ö�üLüÿ‚âø»áÁ{߆òA3ëVјvÈçkÈF€yÈã�nµà_ðOoÙsã�ííûJøÉ¯.ì.ï„·,YʦöÜAç�Ú¿)>(˦êÿuíGÃÎZ½’H_88Žxí_Ýü›ñá¤ÿ G‡Zá[žÙ]ƒáY˜˜äãŠúlÆôh¹Q[îÎÜF‘rKSúCø!ð«ÃŸ>iÞðÜb+kV1Ôd�ƒ×ô¯`ò×­AÞ£oUªùçÆÖ›”®Èe…&]®3_)übýŽþüfQ/‰lƒN¹Ä�ð{_ƾ±a‘PŒçŠNšnýBž*tþ~[i?ðIïÙÏL¼ûPÓâ`p0[ßæÎy¯¼þ|ø}ð«J�Hð†Ÿ¬QŒ£Ÿÿ]zÎõ¥`Õº î:¸ÙÏâ¸ý)´£¨§=ER�ÑÄåq”QEH‚Š( Š( Š( Š( Š( Š( '¬8¡>õI´“A¤SæŸÚÏÁpøãàŽµ¥>âÍ +2IÇ¿��ÙÙuïÙ—þ +Höz¹h ú˜H݆ +:çœÿF�wN�UÒfÓä)×ñÁÿNý‘¼Cð·ã?´^‹iç-¬¢l ù�SŸòk·4å*ê}NI4âà϶ÿ൞$oþÉi°�IqÁc�Ü0Âöõ¯�?àÞŸØïÃZ�‡.¾"xîÉf¹.|¢Ë�œƒëœ +÷¯ÙŸÆíÃðºËÂ%œNÅV ã‘�`ÃÔOZýêý–f¯þÏ^�Að¬!ÅøË;�Ò¼üLÝ*n‡PÅUPƒƒÜúFð—‡UP~EÁdüáÙ?f�SRª×2#"L•Êã�©¯ÙÆá·c5â?>h¿¼,þÖãà ÚU†G=ë‹ (ÓšŸcÈÃb-Q9çWû~ØšçìuãûmZ'6WyŒ¥OÉ"÷ǯ­t?¶Ÿü7\ýº~2øÃƒCmm{„Ú1'†õ¯ë#Ç?ðE¯…Ö›4ÖV¶Íy)f  é“Þ¿ž¿Ú“þ ¿oû|D¶øµ|²í³¹BBýοÈ{WÕSÌ0ò“”¼ÏWÚÆwq?­ØÍ"ø'ûØêž#;^ÞÊK–fë…L¯á_ÊÇìÅዟÛ#þ +3¨xæÊ6—J²¼ÞιÚÅd 3ìJçßþÐðVKÏ|‹à_èškÛ›qhÊ„®Ð˳!†3þïë_¯?ðEضÇá/Ãåø�®ØˆµIDŒí�Û¸ç1^uHÊŒ>ì¸þê ³÷ß@±‡MÑ­¬`]‹j¸÷VÅÀ§ljòéÁ¤|Ô�ÝÆ×Æß¶ßÃI¾$ü¾Òíáó›Ë`G¶8ýkì°5KTÓmõ;),n×tr 05£º\ˡф­ìê)ÈOì#ãÝköføûwà VG³µžós£œ©V<}:WõiáOYx†Â;»I’U ¥Nx"¿à ß±u톨>(|>„Çs$ùY\àç/x;Çz=½þ‘{ñÌ ‡R9íY?g¿‡¿íäå¤RI/ü´* Ú¹°˜§M¿Äã¥_Ù' -ÃÁNþ|<øc†ô=K»ï$ RPvã `’ÙÏJØÿ‚høÏãí­êž-×tƒ§h’»•óI !Œzã8ϵ~ŠCÿÑø6¯£ªé±Ì! ¯‚:u¯¸<ðÓÁÿ t„Ñ<%e¤:"�Ò»%^ƒ„#c:˜Úq�¢�–ðeüš†•nˆòÄ€2Mnøßâ/„¾éÇSñMäV�¨Ë4ŒIÍMLMJ²¼µ5ÄÕ•yZ�>­ªÙhz|š–¢â8ã$×ð—ÿfý¦ïmoÚ‚?„_ϦØOöH˜¹fvI|(ÀÃ0uãÞ¿@¿à¦_ðWFñ•å×ÀÏÙíÚånìí4ù¤ô,1Æ=;Õ/ø$·ü/Å3x¦Úâbšàù¨.{`�džÜ}Aâ½¼5áéºÕ·{#ÓÂPT å=ÏÝø'ÏÀ;�‚ß|;¡ê°§†ÊÙ€ d(fwÜM~€•OO´K 8ìãÔ:qZ^ZÿŸÿ]y¡ÌÜ�ž«[šWdTT¾ZÿŸÿ]ZÿŸÿ]_ÕŒîˆy£æ©¼±Ûüþ´ž_ùÿ&—ÕÇxŸÿÓþú#4¹'­)\ Š@2q_;IêT½ÿ�W#®+ùyÿ‚“J­â­RÙ�Ï ‹!åŽ{ãÒ¿¨«„2@È:‘_ÎüáÞ¡aâcâ"Æ8ïÔà(Îý£•>˜Ækò¢ÿ²ª(£ûwè+šÐÃñz�Wg(éêš•ÏĘü³uÉõ­|ª¦zóYv÷*ɳn1ÅO‰ÿÕ�“_Á5ZR?ßu¬5Pãk·!AïúVn¦&n"`Ka°8ã=*ÌW‘Ebbeɤt·½Hâ™¶`ä{ò8®œ¶‰äf4ä°óvèT±…¿Ù¿cí6o,àÙÊÙÏ^?¥1Ÿš ˆšçšwvì AÉ5ý?~ÅÖŸhý“të 3æ*ÚÈÀ{0'üÂ|]‚K‰zõœÀ¬¦éò¬=Oµ~ïÆô\ð8t¿­þyý±�‡q«+75ÿ¥Hýbÿ‚W[C{ux¬å”‚Ät#� yoÇ«‹Ë/ÚöHa_Þ¨£\åÀÍ} ÿ£ð®¬±Þê÷VÅ"»PCt +S·â büEøk«ø³öäfÓ—tQÞFŽÁ‡?‡Zû\—/oIÛª?;Îø§5Îj9§búõ>˜ÿ‚†ÏrŸ±½Á�™UFÑœ“¶¿…•Ò|aΈ°jOLÞ,±üøÁ8 �NÕþ–¿~èþ¿€5˜ÄqL‹Êã*WŠüå·ÿ‚3|¹e›Y)pùÉw�æL zWô–G‰tpÊœ‘þff9�*µ¥;Ú÷?7~ÿÁhü!ðOà¬>ñTÍ,öKˆX)w\/!»u¯æ�öŠø§ãßø(í/‰4»K™mMÁŽÓ –U-Õ¾µýjü]ÿ‚ øÅš»Â3äVmàβ#ç0¹œWÑß²�üçáŸìù¬A¬KkÒŃ€>RqÏQšíž"�4åJ:³Š†&”qgGÿíý›µ‚±Ý®‹oh"½’Ƀ; m¼’>µüþÔÿü[«~ß7𥽫�í¼3(%NÙ9$œWú¡ÚøjÚÇE�E³P�Æ›˜Æ+òKÅ_ðH/…~#ø¹'ÅW�¤ŸÎhZ5Á9Ï^µž_ŠT›Z²pØØEI¶z)ømñãöµø}–[Ý#–*œ\祟ÇO�dŸŽ—6íguo—æDÂ7VB[8éÁ?ZÿTx/MðW…ìü-`£ìöqˆã_î�_4~Ð?±ÁÚn/|U¤ÃöùÓi¸DØŽ…³•?ˆ5 s¡{Æéœø|t> =çSöoÿ‚ûø{Á?¬4?,—Zd+, ¼ì@Àôí_�ß¶·í[ñƒþ +ƒñ‚Ë@ðî—q�øŽ6Žxàtý1_ÿÁþÉ®¶©osÖÏÞ·RO?~Æ´„žû<î�ÝÚ5Îk}ÝÚÿI¿ x[Fðž…‡tXDV°®ÕQÇò¯•|Oû +üñWŽ[â¨³‰7.6†9Û‚+¾–sQEÂj鎖iß1[}㟅š7Šï¢h%¼¶�Þ6ä«mäf½g{VN…¢ØøoI‹FÓP$0�ª£€jW�¡ó¸š‰Í¸Ž,O)óM¢©;\±Aâ EO['qÊWšuS$BëQ”#¥KE&®z*r Ô$Ö³”,QEQEQEQEQE*œÔ»Ö¡¢‚”¬K¼ŽÕòçíKðÿüs£êÖë1XÛh#¹kéêyÛ"”~sO™¦¤·GN*sRGðwã¯|dý„~7]x�À°M.–ÎÄÇåLgø�ò¯ÜØËþ +ÿðãÇöká߈3µ†£k¶7Ž@BúpN3ÏzýtøÁû/ü0ø·¥\[kZ|fiAÃ…7ãÛÚ¿Ÿ/Ú‡þ·¬Íyuâo†w&Êiÿçßä#8Î?*è�jU•««>çÕF­LuÜþ‹|9ñÿΈáé—ñÉÀ?+×ñ®âøb壹L0ÎIw¯àÛÄ_¿oß�:ŒÚ%†¡{<ÿ(�ΓÃ^0ÿ‚’]^-‚ÜßÎ’ ªÅßåÏ8¬žU¯ˆä©•E;£û‰ñŸíð³À¶÷7Zö­1ÛÆdbÎÀ÷&¿�ïø,üÂ?µݧÁß„Ðë˜ä*­|¹3Àf dãµqÚ'ìAûs~Ь¶^5Ôõ³Ë"—Y‚ž{û +ý±ý�¿à�ß¾-ž³ã製¼€‰¼Ö\±sÛ-Ú¯ìè>g«&2¥A]“ßðJÏø$¯‹uëý;âwŸ|©D®ï”çkrþÉ<#á}7Â: ¾�¤D±Cn�UW§/‡|/¢xZÂ=7E�`Š%Ú€8Jèƒ�ÐW-YJ¬¹æy˜Üo´~D€`b–¢.HÅ2¬óܡ栢š´0üKá­7ÅDÚN ¡ÒU+Èõ¯ççöäÿ‚aÝx�Qo|>´�.˜ä4'nÓô¯è“q5ÑCu†u <Ôò8¾joS» ˜Î“џŧƒüûV~ÇÞ ]_ŠâM:Ýà À¯µ~¹| ÿ‚³ø&þÙ4ÏG%žÜ.é�÷ÜOJýYø¡û5|<ø¡e-¶µl Ê¥ +2¯Ë‹ÿðHo 둼þ˜[‚Iuë‘øw­¹èTV¯3èichUVš>þðí½ðcÅRí5›)7`|“)çÒ½ËMøåðëS`°jåŽÕýâüÇÐs_͈ÿà’<1q%ç…oç�iÊàž +͸ýƒÿlÝ.Ö5Òuyü¡Œ0ÆGüŒ°¸hü9±ZÐþž/¾2øLQ&¡{ *r2ò(çñãò¯#ñ?í¥ð !×uËx<±“—^Ÿ�4Þ$ÿ‚~Ûþ(¸_k7· +˜*C²�ÿ¥Rпà�Ÿ´7‹ —>-¼ºs#&w`HúdÖßQõwPÒ– £“?Hÿi¯ø.Á�‡¶ZOíªr±†äw'¥~øËã·íñÿ ÖeÐtwžËGº¸ +Qw.îœã ‡û#ðGþqá-&ê ß|Â6]Êß6{“Í~àü"ý˜>|Ó×Oð~— AT.í£9’lÒ…zt´¢®üÍkchQV¥¹øUÿÿÿ‚+i»‡ÇÿÒµ3É`'¶ãž~•ýxsÃZ?…ô¸t}‚UQU +08­ØãŽ#Œ`˜©ð+–Q•IsÔwgÏbq“ªï&7bÓ袵JÇ(QEÀ(¢ŠÿÔþû›…Å5zy8¤Zù¸Å=ÈäÂó_ ~Ü_¡ø�ðÖ{›BòÜÊ«ÜaÚ¾î•w +ÎÔ4ë]RÍìîÔ2º•?C_;ÄLqXz”d´húîâÚù&kG2û84ÿÍÂ/įj_ uÆÒµ4*åþa‘½I=Àèx®BCž¡‡#ë_¾·ïì}5ÝÿįG%ÐÈ™¢NXŒýÐ1“Œ WሴWB¾µDÐ<+©ÍžÜwÁ•òü\’�ºÑ�~6e¼S”Ó¯Jªr·¼º¦SB"Ù¹&«ÜF³À‹)#Ë;‰ŸJt²Ka]ÝÈ$pj2ºþó; ã®+â#VjI-ÏèƤlžŒþ…àš¿´·‡¯|ÿ +ÿ^™-fŠ0ˆ70«µ�'6œ{׫|\ýŽþøïÅ2øð…Y§Ä’m*A#×òõ¯æãÃ'ñ‚µHµ½é­|¶9�­žç•ïÑü{øáâÆ•§ê“Kmyòm�›©ààc?Žk÷žâ¸UÃC ^’?ϯ>Œ˜Œ}_>ÈñÞÂ5u’üÏé‡öy½øMà«!àß\B.¾în +ßü?ð7Âvž8oˆ¯7²n!�mÃøÝÿüýž>%Üx®jóÉ q“!,[¹#>£#ñ¯è2Ú²Dz¨¿¦87/•j*u#kl—>6Ê–WœVÃåø—QËã}ØèÀCбQçÔµúEr«3ùêu +(¢µ"ì*7­IE&®ez\šs.9ÊÅ«Øìû +7æ›E+‡3Xž´™4”SL..IëR EEJ "œXù™)PNhØ´ ƒÒ–¶$nÅ£bÓ¨ …šuPEPEPAâŠ(6@EGS?ݨk9  +(¢³@ÉÅ?Ëÿ?äÒ'Þ©h4Œt")�šeLÿv¡ ™«QA!EPNSƒšm& ÖÐÜ)I0ô4ôûÕ5_³RZš*­}«|/ð>»p×:¶› ÎòŠÎ³ø1ðÒÂUšÓJ…NFuü«Ô¨©�­�~°û™Vº&‘`¡--ãŒE¯gåéSà´˜Z·Iº¤ p)J3S`”SPFmܯEXªõ2�„QE@Q@HéO ž™E•‡•„žT {kVmÞZ–õÀ©”eª]¢Ÿ²M{VRV äļ²*p#Q…Pùö©±ëÍ-8QHWЋ#Ò™šŸhÎhÂÑ(XJì� Î*ZL +Z’Ò +(¢�…Q@Q@ÿÕþû\ñN7;‰Z}|ÄUõE¸¶Æ°ÈÅ3Ë?çÿ×RÑJTî‚1w1u­"ÓW³k+¥ ¬;ŠüÔý¡ÿàŸ~øŸ×Ú[[Â×+òäœòé_¨L¹éQùdŒb¾?>áŠÔãZ>ÿ‚|E͸~¿Ö2ÊÎð~§ò™ñ þ ©ñZÍä‹Hs:@ÄlKþÍypý�>4FVÌés†Qœ²`�®z×õîúUŒÎ^HT“Ô‘M:6œ9ò�jü毂ù|Ÿ7-�ë<³éëÅXzJœ’•�åoÀÿðNŸ‰š½¤1ëÖr¯–Ü…ç¸=ÇÒ¿T>Á:ô­½÷‰– Ú¡FïÆ¿Wãµ…�íS¯Êv×Ñd>`0³U9v?0ãߥ—g”� Ôä‹ÞÇ5áOè¾ÓLÑ XQFÐ9®¦Š+õ:T8(ÁY̸¬EJÓu*;·»#ÿ–•%Fß“RS8Š( Š( ¢eÇ"¥¢“W½æ\Sk¬ER¢Š(É÷ªj…>õM[G` +(¢¨Š( Š( Š( Š( ?JŠ¥~•e0 +(¢ §Z–¢Nµ-ÐØkýÚ†¦»PÐD÷ +(¢‚Š( Š( 'Þ©ªûÕ5m €(¢Š  +(¢€ +(¢€ÀÍW©ÏCPTLŠ(¬€(¢Š(¢Šr}êš¡O½SVÐØŠ(ª¢Š*&m ‚Š(¬Š +(¢€ +(¢€ +(¢€?ÿÖþúÓ­KQ æ¤¯šÃÇÝ:Š(­ùQ\uÕ¤EV 5¾í:�ŒŒRÖÔÖ†s�Š(«±<Œ(¢Š,+0¢�ŸjLû,Ä:Š@sKHŠ( ?JŠ¥~•ð+)­A!(§ìjM�K‘�•�¢�±¨ØÔ¹XY‚}ꚣU äÔ•´°Y…QUfaEQfaER°¬QEQE1úTU+ô¨«)îETôëRÔIÖ¥ Ú »PÔÏ÷jžáEP@QEQE8)#4Ò¸ÒûÕ5BŸz¦­c°‚Š(ª¢Š(¢Š(CPT硨+:€QEfEPEP“ïTÕ +}êš¶†ÀQEPQYÔ6†ÁEVeQ@Q@Q@ÿ×þûäýIQTäôÀ©sšùÌ<’ާ@QEº« +(¢¸«ÊìŠCžÔŒÛk €ê*&`V²äÕô¨NÙnbSÓ—QýjûIì�š+šŸÅ^µžþúÈ¿ãNµñ>�}r¶v—°É+�Ê�Æâp(öˆÓêõ-~VttT‰ä—pÆ{Õ©ˆê*œ×ÖÖ‰¾åÂs\äÞ;ð•»mŸP�»Š×Û¶®Z£)kc¯¢±ô�Eסiô{˜îQN �³ƒ[QwÔÂ¥6˜QEÌÆ?Jju©jd†2JÁBõ$ÔÉ[Vi>„Ô„‘Óšó{Ÿ‹ß,õÒîõ{Xî¢4Š<õÛ麾™¬Aö�.xî#Î7FÁ†~¢¥TW±Õ<<â¯$^ßþÈ£Ìÿ?äR� àR<ˆ¼7zkÔçQc•²qN¦©V—{S«x=TPN9ª2êV0J –UWnŠHÉ©�xÇâe¤ÙzŠ‚ ã�wÄÁ—8È9©Y±ÅRªšº"R°ì�ÖšÄ�‘JFhf +2iJZ]Š×#Üôå,z×�øÇã÷Âßjk¤ø—U†Þvm»Y‡޼עøcÅZŒt˜µÏ ÝGwk(]5Š‘¬°ÒŠ»GEEV­hc4# ŒTl¸©9Í-% ’£r „õ¥òÿÏù5Ìø³Æþð6šÚ¿‹/¢±¶NZI˜*�Ä׉xö´ø#ñ'ÄSxoÂÚÝ­Äñ6Ï–EäçzÖVÔê†R�2ZJªãšu"°u ¼ƒÈ œ Ò^fv°Œ Ï,÷ÿ?­yÅ‹þøC¢{ÇZ„VPçÈÀd׉ü+ý´þ üXÔ×Gðíú4òHѦ×VV+èAëX,BÞÚÁT’æQ>½òÏoóúÒ f‘z†S‘Ô}*@Cq]P´•ÑÅ(ëb0¥ºRìj“*8®Å<#à颇Ä7ðÚ´ÙÚ$p¤�ÔóYNj;šÒù;E]�˜RN*QÀÅ`xwÅ:Šì§ \-ÌãzŒ×A[Ò”ºb•7 ÀbiôQT`Š( Š( Š( = ASž† ¬êQE˜Q@Q@Nµ-D�jZÚQE@QEgPÚQY”QEQEQEÿÐþúH,t¦=ª#œx©OQ_(¶F×Ôòo�tï„?5_ˆ§ú�6”ç§¿?g_ø.ïÂ?ŠŸGÃÙ�:ŤhVñó¸0û¤��FzúWÝ¿ðUÜx[ö8ñf­LIm‰êUŽ=G­šU¶§â»?\x‹Â×1\,�,InÄ8$žF9ü«Z—+œ�¥Ë°tçGÞ[ŸëeáŸh1Ñ¡ñ†n’òÎq˜åŒåXVë¶ÑÇZþR¿à€ÿðPoüIVýž¾%LÍuk•‡Í9‘ˆÏô5ýW㸫Vq¼^ç“�Â:59øqûjÿÁ[aýšþ=iÿ4í:9·È«<®H*XãqŸZý’ø{ã(|wà=/Æp€P¶IÀ· ÷¯àWþ ã_ÁGÆ‘tCG �ÈÈ óü«û…ý�µ¦ñìÅá E‰,ÚlJr1Ê®;WFeIS§Ùêc¨ÓöPqGâWü þ â�Ù{Çíð»À°H·lŸëàŒû ¯ÃÝ/ãÏüÛö½½ŸÅþ¹Ô$ÓQÉkx † ô<~£Šñïø-ω¢ÿ†æ¿·»m±E´ì„éÏ5ýaÁã­o|zø?á_� u/x¡ŠxX¤œf7†¿šßø'ŸíàOÙcöÁñÀ�~øm¸•íã,ÛW;þRsÅ*\ònF²pÆÒrQ´¢W´›†qUฎêî `É î4”“Õ!S{ -ÄPDÓLB¢‚I=ëù ÿ‚ÃÿÁLõ¿‡‘Áðoà]ÞýVñšZ&åsÆî9úWéïüö„Ö?g�Ù‹Xñ~†q9‰‘Hꤎ ¿ðM¹¼%ûLþÕrx‡öˆÕâo2q2µËŒƒ¸ž7qŒS¢ïz­^ݧÊ0±KÚËV{G…ÿc�ø(·Ž¼2Ÿ­µ«ë’£ÏX »1œN+ë?ø'íÿûJ|%ø¹Á¿‰wwÐÝMäÈŽ¤„ÜØß“Ækú¢ðwÆ_ÙÓNÒ¡ð…õí7É‚1ňAck#ÿ²ŸÀÈk:\ÿ»·²p…ñ6xo^•ýBH_Jâq䓇cÌÇQå¨Ñ? _œ¿ðP/Û«À²/Ë›½Fö?íkˆÜ[ÀofÁÀsÖ¿Au{£i¥\]gTNÙú +þ?n�ë´7üOð~»y¿M‚ø[ì,X0ã�î+·AVmËe©Ý”`ÔåÎöEß +|$ý·¿o�wTøá ßÞÇ Nó„ËŽª1‘ž+ô þ ›ÿ ø�û?|F“ö~øüÏbq5À 2¤�Aú×ôßû4|ðwÁß„:g…<;j�Æð#¾Õ’Àþlàà�…ÞøO§ÆØ¥Ž¨ÒFdt ž¸ÇZ0Øåˆ«ìª-Ç£\kJTí¡ýeøw_ÓoG–N sÚ¸ßxïßü=sâ\-µ¥ª˜ã�ØzšìK`â¿?à¼_¼Aà?Ùäé¾�à–nYÔ‘´wý)F2©8Â=MðeR§+Øü¢ý¹l¯‹ß·¯Æé> | +šäxí6á²UNߘ¤×�x×ö]ý¦?`û�Ç–Ó]][$Â_.@í´u$ú{w¯Ñ�ø7{á·‚ü_àÏ|FÖa[½RÒáS2ä�Ü“Ÿ\×ôGûFøÂÞ7øK«éÞ&·Y¡HÁ ¤Ï¥tbq*3tâ´GÐK5=ŠGÇ¿°/íñá¯Ú_Àö¶zŒRZêÖÀA>ò<²è?„“»ŸqÖ¾ìø¥ñg +<{ãܤ6¶q4¤“×hè+ø]ýŠþ(ê??à£ò|6Òõ "ÒçÕˆ0ä€rø¯¡ú×î/üƒÇ7~ý› Ð4û·¶ï³*ÛAÏ­pË 8â#BzsÓÁSuÒ—Sò»ö¶ý©~5ÁG¿hHþü †A¥ÛÜÂ[bóŒœzŽMu?a�Úö(}â/ƒõ ‹Û„–T\˜žL ÷Æ}kí�ø7§à΃7à [â­ +ɨÇtJTn9Å@ÿ´w†¼?âOƒºÕ�ˆ-c¹…mÙ€uÎôæ°ÇV”*¸Ceø�ï3䬨Åh|5ÿóý¾4Úþ ñc%¿‰,PE:–;Ñë_«q(ÓJÀ*‚Iöü~Çž5×ôø(Hðå«´C¨6ÝŽP¿_ÝgŠ¡¸¸ð-ݽ±-$¶ÅT޹eë]8š^Êj+®§�œ`ቮ§á?ü£þ ¢~Î:‰ðO¹ ö£2\ˆ ‡¦K`àf¿•ÏÚWþ +iûF|nñ¾·{®µ¸·“ÛÇ�U\òIõ¯éƒÃ_ðGˆ>4øÿ[ñÇÅ9‘ÒêfhÄ‹“‚xkð“þ +ãûøköKÔ¬µ�Zý–}§æê3ÁÇ¡5ê`çG�A+¶va¥îÓèT?ðF¿‰ú¯Ä¯Ù®Â]Váîg¶Q¾fêÛ±€}Å~É×à'üû5Ü¿²Ñ{¡÷äÜoOé_¿uÅìÔg$»žkuSP¢Š+^].y|º\(¢Š’BŠ( Š( ¿Ý¨jI;TuœÀ(¢ŠÌŠ( Š( §Z–¢Nµ-m €(¢Š  +(¢³¨m ‚Š(¬Ê +(¢€ +(¢€ +(¢€?ÿÑþùÁÍX=EAÚ¦'‘_*–ˆÕ•ÿðX™.ìCâømћ̴eàp8cÉü3_Êüëöqð_ÅÏ�³éŸm–ú("ù�Ô¡‰ÚëŽzWõýÿRÒXý†|lT3®™<Š;ü±¶vûã8¯æ{þ ¶‚ Oö‚×d�e¼–Ýýå «ýMk¯ÕfÑõIµ…mYûpüø¯ÿÊý·�Æ/†:{Eá{™Òxgƒ#j1åN8>•ý\þß·‚¿kO†êÐ\Fšµ¼@\¢‘†,8eçó�{·í9û6xö™øoyàÛG1xÈ…ÝAÚ߈èzWñGª|aÕ¿à�ÿµ®·á,\jJƒ ¶FÙ´;:á¯ÊCÞ–õŠk›âA°ÄR´÷G‹ÁX¯îfÿ‚”Ϋ&$r‘¨# ÜGó¯îÛö>´‹Oý˜<ol8M2/Ŷóú×ù¢|wý¥o?jïÛ?Š-o%¸ÔnU'.‡®}Æ}ëý1¿dÈ1û2x6%$‘¦B2}@Áýj³õeN=‰ÆEF’Gðÿ½ íÕâ$d/Ž6ð þyª±çì“ÿño…à¾øqm @ÂGX÷wò@Àäã­zwü™´ÙoÝSJÔ—y"Šàúµý­ÿÁ:ü#¤xgöQð»éëû»a,�<� ã®¦/*4áɹߊÆ:Qº?•Ý7öÿ‚ j÷QÞË},¯•Mó2*¨éžù?JúOà‡ì3ÿð÷Ä7Pñ.©qŒ2£M vl…`p ×õä‘¢}ÑJU[ïkÊ©˜Õ¨µ±åÏ=¨Ó‹JÇá /TÑü–¢Å®#€Xää/?­Ÿ‡üoÆ#ñ7üJÛO´v&Öå#ŒuÚÀç�_è�ÊJž†¿‚ßø-×ìÿãÏ„?µ­ÏÆ+›7�L»‘$¶ºˆPÛSÛ§ë[e)FN=YY=~Gö‰û } þÍ~–èîvÓ¢$Ÿq_G×ä§üWöÀðÇíð MðM½ÂM¨øzlî �=S±¿Jýk¬%US¼CÈÇP’ªÓ3õvUÒ.™ñ´Bäç§Ý5þvµ%Åí¯ü†çVÒó&¦!˜¡à“!Èü«ûÉý«¾;x_àÂGÅÞ%�aW�ãBÄÊœž/©¯àëàÇïþÛ?¶ÍÖ·áÑ(Iõ#xX©*ã´‚= ëe¥vöh÷r+Ó„¦ögúüž[¯„žžà’æÆ “É8AɯT`M|“ñâ,¿²·ì絨Âo¥Ðôè¢Ø3—’8Âà�VïŠü׿àà_é7×vÞg•$uñå@�œ2ÿ#ô®\%)Õ�à�*xT“œ^—>›ÿƒ„<]†?dö·¬Mt¯9ÙŽ1îç_Æ×ì]û/|Eý©|`°|*’[k›}®eˆ�àƒéè_júûþ +oÿ1øÑûmiv~¿Ð—O†ØæD¶8'…è½ÏLšûkþ  ñ7†t¯‹ž!³ñ©ŠÞúâ†ÔtC! �3þÊñô5­UR…mÏb‚•:^gesÿ…ýµþé-â}]½]‘™\™8ÊŒäàñ_UÿÁ2¿à¥ž*ømñÙ¯ö€Õõ­ßìðÉ9ýâȨPO''Ö¿©Iooá›û‰Â²Gm+z`!=+üã|U¬‹ø)ijÙȲ%ƹ‰W§úÓŒŽÜàÑ…¨ëÓ—´èm‚šÄR”f�ôrÖeKÿ \Ïvɰõå ¯ó?ý½¼Šÿà¢W~½Ä–’_Gg©Yœ(ë_é;áÙæŸá�µì’y­-‚ʹ E�wí;sŸðU°žçc©B69= ã#ÝIÈÕ†W8N]ŒrzkšIl�ª?nïø'u¿ìáû7x{ãW‚ôìÜJåcË!›¸Çc_´?ðDÚ÷Zøƒû<]ÙøÚFvÓ”¤l_'*1È<�ð¯ØŸˆß ü'ñköe‡Ã:Þ�os ÒÃÂ’¨`ŒbàŽ8ÆkøT�öª×?a/‰^5øoá÷ºb"#ÈÆ2+úÍO°Ýꦻ3þ +5㟉¿µ¿íÚ<.¤»²¶Ô<”ˆ1+³ÌØ+ÙਿðO½#ösý›<=â-*åQ®<¤Ûû½¼“ž¤WØßð@¯�ý¦~+ø‹ãÿĸîúÂìÏŽ7óæga€9Îkïø96x´¯ÙžÉáÄq²ˆð gæ��jîx‡õÈQ†È˜é^0G)ÿɦ~ëú»`}²QòŒ|¾YÁÏׯJþ¢ÍÄmÐñë_Ë÷ü]§K¦þÌúþ©*�Mò±ø˜ƒùázW³~Õ?ðPÏÚ[À?/¼1à$5­žQ<¸ÌŒX6Ü�§Jâ¯ÏS*qîeˆÀªÕ¤ïc÷[âî¦ÚOÃ]cR�ÂyVÎʼnÆ0Jÿ:+ÍSûSþ +d³,¾dgYFŒ�CM€§5ú�ñŸþ + ÿø¹á‹ÏÛè×°ÉC*˜ü²á�œŠü:øo¡üBðíE¡êŸ­ç°�¯Wt“�¸bÀäŸNF zØ*Nœ'Õìvåô=”e ÏôþðfïøDôÑ'ÞÑgë´Wó[ÿÛ[„š\·Ñ üã'*HÍ~þü +ø½áˆ_ tígK»ŒùV貂Àm(¸'éÇZþj?àâ¿‹ž ñ‡‡´¿…Þ½[­QT0Ž2›�ÇLŽkÄÊêFXˆÛ¡Á–á§õ‡t}=ÿçêS_~ͺŸ�;HV`… ÈR¤óëÈ#Ò¿£L×óûÿùøÄ~ ýš5I|Qiöyï®#š7*AhÈ  Úú×è¯íññ ãG€>\jŸcgÕQ”.=ž"JX‰r½0ÆaÕLK‚v>í2¬`»ôü¦ÿÁÆ?,­¼§xBÉÕ¥¾ã¨ã×ðÿ +ò¯øj_ø*„6гè××’á@=ëƒ×•ù%ûsxoöÏøÙ­7‰>3iײ.K¬mXÚFÔŠõð8nY©JHôpo³�Ô®E_ðmñ˜þÏ:ø—aÝ")^Üþuýü[·û_Ã}f×ó-d¦kùxÿƒt>5x_ÂþÔþ ê·‚ÂáÒ9>S•ÎA'¿Jþ‰¿jï�> ü×|UâíZÞÖ1k" . ÀŽrÍø +ññP—¶—©ÃŽ¡'‰ºGð§ð>ÃìÿðVkQóÚj‡pì~by=«öGþ2½Týžt{¸�·C²_ÝòCqù×ägì áo~ПðQåø�áÛRºt·ìKÄ B¹ÈÆNzWï·ü;à?‹ƒ“ü…<+V‰Ç’Jõõ7üósç~Êml�¹-¥Xþ‡šýú¯çÿþ ìØ?d«�‰´}³,}K‘ýUOã—©Ïœÿ…QZÃcΆÁETʉ”;QYØÌ(¢ŠŽNÕLÿv¡¬æEV`QEQE=:ÔµuÍI[Ç` +(¢˜QYOshlQEAAEPEPEPÿÒþúX`Ó³�´7@hPO>•óZ6~vÿÁV.ï¬aßO§»#ý�ä Ëç*?Þ~5üÄÿÁ¶š†¡§þÑ:åŒGbË«0 mÄýO§¥hþxkã€ïþø¶3-•úluØçÚ¾2ý•?à�_ +¿e?Ýx³Áh»î°;UJ–Îq€9¥í­NT­¹îa±��¹ú"#!G9¯ç·þ -ÿ¤ÐÿjŸÝüqð2´^)°ƒ,«Ïœ€1ôâ¿¡Ç9¥’8®bh¥« yàÖTg*oš�†ÅrJèÿ&Û„^(ð/Å› +ëš|¶º…ž¡˜YJ¶T‘ú÷¯ô÷ýŽ®ÅïìÅàé„o =U‘þò°fÈ?C_3|Zÿ‚XþÏ?þ%ŸŠ� ‚ýä>Å]¤ƒž†¿Cüá3Àþ³ð¦��-¬£ÆSŒªëµ~‡©�ÅS”‹?Ïþ �¡\韷¦»©ÎC£1’&3ƒòœŽ¹9â¿H¿à�¿ð[Ý#à�Á;…Ÿ´§ºšÓýT‰¹QP£±à8ýþÑðMÙÇö’ñ:ø»Çšq{Å9 §÷ˆ=yôé^3¡Áÿc� þÌ–Q/íàuÀàŸÖ»¥Œ¥*j#{UÇPš´���üÃövF"k•6ðûOÞôÇz<ÿÎøiñ ź…´ÏΠ䢬Á8Î:ôæ¾¢³ÿ‚?~Çšp-e¢|í÷™Àl�ÇŠè|ÿ¨ý”üâxî?¯^køòý©¿d¯Ú#þ ¯ñéümð¾Îäi2ÌÒ[Íl‡h>¤qØ×ú `לüIøOàŸ‹Bè~8±ŠþÙxYTZé¥Z¤óø<ÅÆ^þ¨þB,ÿà¶´³ð>_kÞ¼:ƒÚ42]` ÆÝÃŒò:�Züâÿ‚}þÆ?iŸÚÎÃâV«gqmf·âúiäS¸�Ä€¯é_Þ ×ìyû=Þéë¦ÜønÔÆ«·åP§ë�+Ð>| øoð§wü!tv{†>P?žO¶©ÈãÚçrÍáµt£KþÇð:èð‚Eµ „× +›@ü|cøã«¿ø)5׈¢ÓgšàêSÍ�†Ò¬Fp}šÿC2ªF1^3ªüøQ¬x xÒÿFµ}P>ÿ´˜�ÉŸ÷ˆÍ<-IÑ‹ŒUîr`3%J÷ênxJ‰þiRÊ`ÝxŒùWñÿFÿ‚VüVÓ¿hÝOÇ~ŽKËÝ–yqй't8¿»{{x-âÂ0«À‡®xOÃÞ$òƹg×’w'˜¡°×- +uh»Ó븰¹£§'}S?ÿàŽß°æ¥û!ü-»žðºÏ®„’Uq€äm\×Ï?ðp�€üAâïÙÿOþŵ’îDŸQIÊœÇ^Æ¿£ {H­a[x" @è®kÅ> +ðÏŒ¬þÁâkH®âÏÝ‘C:t¹ãQUê*Y�ªûI-Á¯ø §Ãü8øªh^%GˆÏr’$mÐ#e°¶sÍ~ãÞ|"øu©^I¨ê:=´óÊrÒ2Çê{×IáŸx_ÁÖGOðÍ”VP“�‘.Ñšè÷µ8Òr“œ÷g>+0s›”t¹çRü!øm,Mš%¡Và�)kñËþ +'ÿ²ð§Ç]:OxÂ;[Ëh]ÕÓï2ãèGnÆ¿t÷sóR:,èU†AàƒZª|�š†0�9sç¿mãoø(çÀ�çá×…-/Å­¤²gq%ö°’1Èç½} ÿûÿ‚oüvý¬~/Iñgö“k¹"Ža![ÍÀ±@)CÑp8°¯í~ÿág€µ fºŸK¶óî×”D›Î?Ú Öî…ámÂÖßeЭc¶CŒ„g3Z´ðŸ‡"Áo¦N8ö®êóO³Ô¡ò/bYPõV�Ö¬’M%b©¤¬|´«6îaÂ!á¬/ú gÝùÉøëá€üáû�^ÒífŽâ6�-’7 ãƒ^œ„ž)õ§°MÒÅJ.éŸÅ¿íÃÿÙøßû/|B>:ýš$™¾ÝpÍ VŇÊìIÞÁêxðνû/ÁGj?Xøâ”-hBÄÒ;6 xã,Þ•þ„7znŸ�¶Â’ã¦à+6/ ø~Þåníí"Içr®kµW©ÚÈ÷cŸY]­OÍÏø'‡ü¿Àß±ÿ€mÚÄu¹Yv†*qÈ�οC~ÈžµµÒ­WûJH”Êä|ÛÈç'ϵ~‰¶qÅ:ŠQ¥»{ž-z²¨îÈ”rkð;þ Aû(|@ý¥|5a¤ø.‘—©=8úƒŠýöªóÚZÝ \Æ®?Ú¦éÎR�Ñ€Å{s¤~OÁ#ÿg/þÎ_?áñ()#Ì$!—iàÈü•~´ÔqE(#…B¨à0*JQ¦îÜ·dⱬù˜QE¥¬`QE +(¢³šêg5Ô(¢ŠÌÌkýÚ†¬ž*0j&€J(¢²¢Š(¢Š(HûÔ•^¬VðÔŠ(ªjÀQE#EQE³ +(¢‹¤QE+ ´‚Š(¢È—&�ÿÓþú‰ÊŠ‘FŠhS´Š’¾];—%­‚‘†E-!õ®‡n¥´"¨�jP01KS(®S>Vµ#f`qšnö§fäñþ~´ž[Ÿÿ]r´ú &= #šu1~QÍ.õ¥í-¹¢E ò)j®¤29;S2GJ{|Ý( ëY™¸»ŒÉ£8§”½3i©eôI¤ øYᛯøÚþ +EÜòLáG=>µùiãïø-ì•àýSû6ÆâmEU•â,yö y$ñ¯“à²?²Oí}ûOjVÃ9nŸÃQíiíìÂç#– +Fü`õ'u©eÿø!gÁ‹‚‰Å(æ—SÔãYe‰˜©Gá[;ŠáŽNÓÎ**ºqIɶßCé(að°‚•MYûû8~Ùÿ¿i]=_ÁzŒI}‚ZÍäV|êTá†9⾸<×ùà|vÓükÿÛý²€¾ßÝ[ÙµÚ€†Vbªòa@9û¼àwÁ濼¿Ù£Ç·ÿ~øcÆÚ®>Õ}cÍ�ïíÃ~µ¤ ©É'³9óLºâªSÙžæNMx—‹hŸƒ¾ñðÏŠuë[+Â@Ù#€AnƒyúV¿Æ‹>ø3à+ïøºqµ¬lÙ=ÈR@¯óÚý¯ÿm=câ×ísÿ —‡õ;¨ì`¾H¡(Ća&1Œãý*gMÍ6�’2ÀàH¹LÿFËKëmFÙ/läÅ"†V^A�kÁ~=þÒÿ +g_ Íâ_ˆzœVÂ5ÊÄXosì?Üý�õ˜|Mð+ºí©,·z]¼€·Þ9AÉÇsÞ¿Ÿ_ø+ïìûQ~ÕÿlŸÀ3LúQ UC� œ±nœƒXá!+Õz NUfö=£Å¿ðpìõáû‰,ôý>{‰V¸»)P§UËØ½�Ia°õo­�ôu×üK¡ø_J—Z×.ÚÚYÝŽ­~"~Ó¿ð]Ù›àŒ¿áðį®j�0Ƥ`u;™¹ôé_fü{ð—ÄÚ?ö?{/‡·¬º®­¥Åp« +ìáåWÎ7}kðoö!ÿ‚ë–/¼gûJZIT.¡‰Èr{“QþÑ´€Ÿ ïüyâ ¤Nà�Åp=ñ_çwûu~Ýß¿kÿŠÚ†©£=¶�op1+²†ØIÎ;œÒºaMÍsGcƒ/ËÕMf¢GÁÚ;á¯Çý#ûcáíßÚcTãû¾ÄŽ+Û®®íìàk»·  ³üÂÁºšö¡}á/i÷W&årޤœ•?/÷Ê¿Jÿà¯ß%oÙÛáû‚ÓwŒýÝ»Ž ê=¾µú5ÿÛø‚Iþkz-Ì¡î ­Ã€IÆ4ýzþ5ÛV Rçg£W.å£ÍÔþ›*9¥H#ie8U“èZb¹n~ÁF?l='ö;ø }ã{„^Î�¼dñ¸ñ“ú×%)s4�3EÎj ¯ÁJ>i¿´m·À»F‰ÝåX%ÜÀ8v€¾¼WéÄ7 $nÌ7Wù¦üøó«üOýº¬>#ëlUõ ^Ù£$œƒçF¸EÒ¿ÒO~�–~ü+ƒ×ªðkT³}8\×éíÿbðÇÏ�w?u{‰^+©c•¢’_•p +;gñ¯¸¼Oû | +øsðRð�‡ô 8ã²ÓÝRS–È^[8ÎiP´¥fŽœ<¨)r-O7ýƒ?à¡Þ +ý¨4tÑuk¸­µ¥÷Nvn!FTg©úñâ_ƒ¾xNóÆ~3½ŽÎÆÊ6–Gv…¯ó¥ý˜>'øßá—ü”ø'Á—²&’ÚÒ®Ö9\ç•ý'ÿÁs"øbÓÅ~ºŽîÖî5uxÛ#‘Èü+âïø(?í£mûü.>(�i¼”â=ØÏ~€õé_˜ßð@�‹~'ñgì�Q”ÜG¦¬`î%ŒgËËžîyâ¿?฿·&¡ñ‹ã!øYrÒCo¡!vɰ犃XPÃ9VöWØâ¥€‡¶}‘ôN›ÿ®ý«~5x‰tÿ‡fk‰¢0¨™Œr2çúWê—ìuûYþÞ^?ø™k¦üNÒZ=!�L¡\©<žz`s_�?ðDo�?²ß¿´½Á¿G‘d¸EÛ eB Ÿ×¿5ý¹ü8×>øÿÃö¾/ðÙ糸\Ç$J£…8íèj1•y*{5™¦2º‚qQ=*ÎF–Ù$n¤Uš@µêR^ê>c•QZ ¢¢rAâ—$ŠÏœî˜eAP²’j1j«Š/‘š)ª»WêjWÜiQO™QEŒ¤›ÐŠ(©º¢Š)I]%p¢Š+&ŒÜ +(¢ŽPäaHFF)i?, + ‘"›SÍ.zÊÉ�Ù² +*|-!E4rvaìÙ X¦…QN«‚°œlQE\÷$(¢Š“u°QE (¢Š(¢Š(¢ŠdÿÕþû“îÓ©€…Qš<Åÿ?þªùe¢5¾¢°'ŠñÏ�| ðÀ×~:ñÝäV–¶¨Ïó°]Ä àwü�¯]¹œA JzOå_Ãü#ö×ñßÄ?�Wÿ³ÿ‡.Ú-6Tq×!㞆£Ø*Žìö2̽Öm½‘òí1ãý[þ +/ûußx«á4kWºŠ(B®á¶6n;t¯ïcöZð§ðÃà†¼­|·6viæ ç†â3íŸÃ¥,ðA/ØSÅO‹ã_‰mZÒÏ’„?¼^ç'Ôô¯ëkâÄ áo�¯üaâ +[C“�…†&·=D–Èïͪó¸Ò�üéÿÁÀ¶Gƒ¼3ð½¾è·ÿñ:½BíDä¡9Åßõ¨üm¡ÛÚìye¼„º¸És¸÷ºýiý¨>;|!ý£?mMsQñD‚â͈]›åaÉzb¿0|u/…m¾:Û'ƒ¡òm ¼ŽE•Nîúx¯BÊ0q:¡MA(£ýA?cyŸ³‚ˆRÓ!\sú×Ò,‰'ÎÊ éšøkþ ×ñW¿e ¿†®¢š[+4†tFÃ(Æâ½@5ö¾©©[i:lÚ…ÛŽ.Äû +ó•HòYî|Ö"Ú³ãŸÛ_ö¹øqû+ü%Õ5Ï]ª]Ém"Ã`3) +Øú×ðWà�#Æß¶¯íĺ�‚mšW½¿K—tRt®“ùW³Ág?j¿üyý§õ‡ZUÃM§[Ëä"lüêx¯Øø7ãö"ñÏËûÏŽ>µò º„ MËór0zö¯[ÚÇ ‡²ø™ìÒ„hÓ}Ïéãá?„ ð7Ã]Â�.ϰÙCû¶‚ßøñ5Öë:ΓáÍ6m_W™-íáÝØ€+[•øÃÿ£ý¢®þ ~Î76^Ô>Ë}t +²ƒ‚ÈÃW‡J‡6‡•…¢ëUågàwü/þ +'áÚÅð£|ždL‚7•yË“Æ{dþ•ú·ÿý“üað{á+üJñRßZ@è½Æn=ðy¯å3öý•þ#~Øÿl´Æ·š[;‹Å¸¹™Ærªû‰Ï$×úQ|ðŸðÇá¦�àm23Zu´p€F Ø s[ÖÃÞJ»™ÕT©{(žûgþÍ?ðÔß &øc%ÏÙVv $yHÈ$¡¯åoþ +ÿ­øsû"~Ìöþ+ðÿïîÒeO0`GÌÄ‘ê+ûd= ~Á|íc¸ý�n¼ÃŒ9Ûìq×ò®ŠJP©'¡çdõ«�Ÿðlðhí|Y#ÌU¥ü£ÎAÛƒŸÂ¿i¿à«ÿ¯þ9~ɺö‡§ËrÚÛÉ:—ÀÆý~í~'ÿÁ´ºÓµûufhã¶3K1óÜ q_°ðY/‹zßÂ�ØûW»Ð]’KÄhNÑÎ +žý¸®L4ÚÚ=,oûâ±ü~Êÿ´OÅ¿Ùâíſ¨ÞûWŽBZ8s‚À ž9¯Wý´ÿm¿Ú/ö°š âÕœ¶’³ V™àd€Wé_üÍû8x/ã—Ä�oÅž8±KÙ¬g3JgŽ:¯ÞíëŠýÿƒ„?fÿ…þ ø�ü¢ÛéƒH¡ÞÝù�zg6×·,E9W³�Ìï§5íì÷>¢ÿƒ?f�Káìëwã›Û¸nSÄ/½<¡Ñ× +á�~œWè×üö®Ñÿe‡ˆ.Jµìñ˜í_­Ÿðn·í%à/…Þ$ÿ…®^9Ÿ[Qo‰N n1·ñ¶½\{ýË;1æ¤Ñý´jW�é–rßKȉ { +þ'?à°_´çŽ?l/Þüøek#Zi¢”.JŸ(äŸÄŠþŸ?à ?´å—ìßð Vñ|M™ZÝÄMØ’0+ø¬ý™¿l/Ùø£Å~5ñ´[¯/Y•ˆÏ$~f³ÃÁr#ÏÁÒå\ÏsàÙgG½Ñkÿ é:¼ee²¿�eOW ¤ïÛð¯õ+ðÁá}8§ÚÄGâ‚¿ÊãÂ?-4ïÚºÛâćʴMLÎ_þ™™#JÿK_ØÏãÿ…h�:7Šü=r'xáH&ìC(ã�÷q\˜ÈZjLÏ2NiU�Ã,çóÅ=ŸðY¿ø)F›ðáÍ×Â�‡3%Ʊ¨)IJ6 +çŒ ~¹¯Û_Žß´/†¿ µëWkh"µ�«³Ãm89ȯó}øëmñöÌý­µ-'Á2\ê©vÈ’ ²Æ…»vÇZÓ.ÃFu=¥GdŽœ“ µ©>‡×¿ðEÿÙ#Uý«ÿkVøÃâ‰%�,'³È2Pî'*=ëú¬ÿ‚»|"Ð)þÒ>ñÝLJ?g˜þ×$“2Œ¡m�IÚ +ä?n;ÏÚóƾ1€~Òh³[‰cX�-Á'ºœþˆÿàÛß„žÖ,õ¿kÖÑ]\4¦HK c‘�ÙÎx®¯þTð�…“•Ó•.in§ÂÜ#F$^CWË¿µíeðÛöZð-Ç‹üoq‰I†Ü}éô®óá·ÆxÇÂÆ“¨G²h +ìîÚ3×Þ¿—¯ø8cãm­ÅÎ�à�fuŽwhØÇ~Jà{ +ãÃÕ•ZŠ›90yzušÐý4ýšÿàµ>>|A‹À‚5³žæá`Œ ±ºg®sÓ�Ö¿lm/¢½µK¸9Ie>Æ¿ËsöÖ5{oÚ«EÔ4é+HlälPàçI¿Ó³áÿ‰t]sÁúf¡§LŽ“ÛÆF}£5ž*R£W‘lÊÍ0‘‹½4xGí]ûZxö\ðƒøƒÅ¯²FŒ´y需3õUnA>ã¥|½ÿø®Mà%¶‹!îÀô;³�Ò¿•ø'?‰/4¿Ú›Bo8™d’0N~è%{UQ¢ªRç–åÑÀÓtomOôÖ¶¸Žâž"X |íûP~Òžý˜þMãß ȹ®p y?Jö]Ç}áK+˜ú”g×Wá�üÛÄ×6Ÿ³­®‘):ʲ3arr0zvé\xj­ÉEžN„][Kc´ý�ൿ ¿hO‰Ö¿ôÝ=¬äžR‹6ýÊÃ8R2 ë¨#ñ¯òßý’~0EðgöŽÓüU­ÈaŠ9ðØ9ŽHÆkýþþÝß¼mà ?[¸Ôá‡0¦ü¸ì½Fk¯½””¡³=ôöhöÚÛö¡ð¿ì«ðÑüâ5YŸdHçjœrä‘è¿­~`üÿ‚Öü2øëñb?i�ºtŽ?›å�7sü8ÉÍ~kÿÁqà¡ ÏðcÀÞ²ðo�uK&†Ê�ä–<�£©Á¬ÚûáïÇo€z÷ÃÍ&FYurˆÑ‘“žßC_ÎWŸø$Wííà¿éÚ�ߋŮ›Èï™ä$/bF¯ê—á¾�ªxsÁZv‹­Êf¹·…Fõ*1\ÖKÙ»ž^24éMT§+³øYñ×ü»ûQŧêž1ðÓ/ÚW2Ã0ß�L¼WàwÄ„Ÿ¾üH¸ðˆí– cLŸce€rÊq““ëé_ë’è6ìüžþÞßðF‰jÛÿ�¾ ·…´«ùPÈCíp3ÏËþs[}ió?hiC§~cè_ø _À?Œ?¾Íñâé’×Q· Gª‚w ý;bµ?à´ÿðSg_\|øvS_Ô"uܧ €�óÍ~Õ~ÌŸ %øQð[EðmäB«X%Ç*03ÇüùÿÁW?à�??k¯Ú |aøo¨CEåý�ðã¸ÏRÕ†PXŽz»ô¥“lþ{ÿà–~øeñÏö¥“Äÿ´MêÊ&œL‰;ò[?3z�_ßÏÃ_‹_³·ƒôˇ> ×´ò°(Š(¢•3Ó€5ü†ø'þ Æý§4›¦Ô Õ¢²º íq6ÌgÓo5õ§ì÷ÿ:ý¦þüNÓ¼i⿵Ê[ÏŒ«30Ú„:õ b½|Ê®«sSÔô*Ñ£5ïÈþ—?hoÚ Â¿¾_üI×e_³ÚD\1?/N ¯óÄýº?nŸ~Û¿nu ë¹!ðì7_ê÷}à§}:zW÷ŸûY~Êú×ǿٶûá—så]L±í,~WØ9Vö&¿–¯Á¹?gñê5{h&f%åÜB=qò�×ð¯? ^ +îN +Tag©ûKÿ¦±GÂO‚zgˆ|?«iÖzÌÖÈfyåQ.äŸBkö×Áÿ<ãÛf½ðf©o©ÄŸy­Ü8•%>ÿƒ|~8è@[Mâr˜´*'¶¨ÜÃêk÷§þ Ùûø£öBø?†|U©6¡<Ç©bøÏSZRQSæ�S‹0�).u;³ô{wͶ¿¿à½v·—±ž¥¸,'�^+÷ ¿ò?í«û3GûV|»ø[$«¹a–<;àú×­Q^J}Ž<·VŒ¥±üòÁ¶M ¥†¿¦’¦r 2Ê®çÍ~˜ÿÁq¼¨øÛö<Ô4í2ÞI¦ÙLc8*3Î=kÙà�ðO=öÒõM:Îqt÷Ï»Ì n9ú_¢~5ðN�ãï\xgÄ�-ťʕt`Áú׎éËÛ:É_S»�„±>Ñl Ÿð@ÏÚwÀ³_Å­{Bø›xšm¥ìB6’L¨GÏVã�C_TÁu¿nÿƒÿ´K_„Ÿ 58õ7µQ#IC¸çwö¯¾¾ ÿÁ¿ ü]ãýKÅúv®Ú|7’X`]™çæ<ôöÈx_þ ØøM¤ø¶ßZÕ5i. I•ÝX–, çõôæ½/¬ÐU=£Nç¢ñØ~k}Ngþ ×ð7ˆô)×/-$¶Žñ¢ò„ŠW8î+Ú?jø!¯�¿j/�‡Å-kWki¯Ì`ÈN_МŽ+÷áÁŸ|ðœð-šZ[BЧhÆí£5êØZóÞ¤ªºËFÏ2¾m'6á±þzðT?ø$eÏì?áȾ!øzâ9, +ç%–Pý€<†â“þcûø³ãÇë¹6v:D«tÎ8ߎŠO¥nŸ¶Oì�à�Ûጟ|e'�Œ~Y6†À=r:×—~ÃßðOß~ÄÚuÖ—á;¥»[ƒŸ0¦×Æ1ƒ×�¥tÔ©zN/sXf1önïS¹ý¬¿d ~Ô¿›á_ˆä1) ¬ª2Gc�zŠü8ñçüÉð÷Ã>Ö5O j®÷KnÌ#@rÊ€¶x¯ê[qÏÉàŽîÙí¤û®¥OÐŒW=)J <èfSÙŸåO¯þÏÚ¥§íÿL.�/E¶æêlM¢—üãör´ý›ÿg 3CL›‰aŽixÁoCšùüÿÁ!~Ý|s?õ’ȳ›…Èç,Iéí_¯~‘�¥G¥EÊG�Ÿ@1üª±u}ªI+5ñ‘vå?‹ø-7üÆÞ.ø‡sû=ø&éííà×#çaÀSíŽs^;ÿhøÕû0|Ô.ücñªt·½IÃ.7nÏ'8èkú ý¤ÿà‹h¯ˆóüHÖ'–ÖîèþôpôÎGç5å>ÿƒ}?d�Å’I.ZW?6ì2ãýž„UF¬=›=ʘü;§È��¼~ÁHeÿ‹>'²ðOÃýb;‰î`QÆßLù¯‰à»_¼añoöU™<h÷RÂÞaØ2BŽI¯¨~Á*¾~Îþ+µñO�ÒF’Û§�óè +ý!×|-¢ø�B—Ãú´+5´¨Q•€#b¹9\j©ÒèyF”Ô©Á÷üoöððÿìgâ�cÁßÔÅläÆÛˆ6Ï8ÍCÿ³ÿ‚‚øGöÂÖôÏ|¶šòv‚Å,Ùà'sï_Ò·Œ?à‰²ŒüUâ»ý<‰oyŒp�½p0ZÍð·ü'öZðß‹-üP¶1ÈÖÌoL{šëXš^ÓÛr»›ÔÇДùúž1ÿ"ø 㯅¿²£ÍâÔx/o˜KYᇠ+ÙàßµÇü+ö�ý”¿hÙGˆ¬§-´ÛT¾Jõíœöý/xOÁú‚ôX4À–öð EUp|b¾uý¥¿cß„Ÿµ&�ŸñÉe0$æÇ§¡¯>‚q¬êU�Ó2£�¥ÎùÖ‡ãÄŸø.×Á/ü¼±Ò i5KÛf…ã*U·²ààtÅ~~Áß þ%~Ñ¿¶l?ìì&[A¨-Ü÷ äs_Ö´ðFߨþ5D:O˜±ãåu\êH¯³>þÉÿg› -ü ¦Ç’6|£rŽÀWoµJ�(îv,Ò…*mSÝŸEi0É—l’�®"PG¸´vŸZpëK]ôi¥�—r»¸Ü7­/ÍëKEiÈ„SšÙä<5F,Èî+BŠ9>ÆOSKöAÐãüþzŠ9Í¿üæÂI>¶øÊ©Ýž˜ö¯áâ}_Wðõìz†…#Au!ƒ`‚FqÚ¿¹Ïø/³FŸ -cc´K†?NEÚ¬Z¬«le�˜¬j�[žõ͆“QlúêUœ(Å£ëÍ?þ +!ûSiÚXYërD¶;@;Ûæ#Óú¯ÿÌý™<{ÿñcxëã¥Ü—ö²w9` OS_œ ?à”¿µ—ůÛx·ÂzSKkx¾fÂ!}‰ëô¯Üø'7Å]Sþ �¥ÝxOö�²}!$Ã`@b?1�ʹ1˜ßw’›ÕöÜç©�œ•£¹úµñþ !û4ü+ø{ã/émµiø¥¹Gœc¯ü¦ühÿ‚žþÖþ!jžÑõÙRËN˜Ã‘#¡8Ûž˜?OÆ¿r¿k_ø8átßu=3á¶Ë‰oÑ­áPr Œôï_ÅçŒ|M¬øïÄÚŸ‰µÙ°ú”ÒLË׎EFò-uõ4¥Q¥ï+ŸÔgüá7ˆà§šôÞ1øç¨=Ä6�“Ãg’Kz÷ÅO¿à›Ÿ³/ÀÍu|Eá %È –pÔwüHÁ%ÿà§>ý‰þÓ¦ëÑUù6Ž�“_Ô—ì¿ÿ«øIû@øÝ4·óØ!ôcÀ¬«×’º9+Ê{Eè~íÃp †.@}+ùœÿƒ…l[PømmÈaòÐ30ç#Ò¿¥�:þÛT´ŠþÑ·G*†Sìy¯æwþ¾³ðÿÃý:òø.çÔ÷~teòµX۹ǗÉ£?“ØZîÞÃö™Ð­²�2±$�[ 8¯ôîø_hëà½2V}êöÑ‘Œà£¥“wÉW¿þ3Ù|C³C/ÙˆF ' �ô¯ë«à¿ü;á/xNð÷‰ ?g�WÍg$�Ü W~sƒ�Z©Àôk×u´G¶ÿÁÅ2¬,ì|âlJ§¸ÀÀü´ÿÁ;¼Øk "‡fB7wät÷¯ØÚ'â÷Å�ø,/�>ÃðÎÍÓL´DU¿v@öÆr;Š÷ŸØ—þ;ñ/á�Æý3⟌¯ά‰"ðs“YƬ(ÃØÍê“:ãR1‡,™ýp|(“w€ôÕ=|•'ë�_‚ðp³·Â {{t-•ÞÙäa§½BžÐâðþ“�Ï•OÈWÀ_·ßìy¨~Õ^M&Å‘^4 —9>À•ãaêrMN[qö�Üÿ3ý]äÓ⸼vÚ¬ä€z®}j˵øËñ'AT:.½yl¶Ã¢vÛ�aœb¿¨¯Á¸ÿ|I-ž—¬›[iÌ6ÅAà÷’Ó¸;Ÿø6WÇÒIÕÅÿ™+°êÄ… +1‚Qƒô¯V¦eO¹ê:ñî3¾(Õ.xgOŽÏÃÚëÍxòãUé’é_Û¿ü‹ãW�>)|ó<]t÷G/2?ñz�ì;ø'Mÿƒg­nµhÖ¦ÖðÆø%œ#ûÅG'ó¯è³ö!ý‡ü%ûü5‡ÀZój o3 ’{c'�ÛšÖ®.!É ÉÆb¡n×>ê¹¥¤ÈéK]+k'=‚Š(©t�÷)lQEÎ]ÆQEL£$'+QSi �QRäÑGÿ×þú¿‡4Úwð~4ÚøÅ¡¿R@¡–—`ôýhB®Gƾ=ðŸÃÍOxÆõ,lãë#篠dÕ˖ȸA·duø# 1!jµ7yvÑà¹Ú $óÀµùKãø8Óà6…ªÉkáÍ-õUT‡¾÷uàv隨aêµuЧ”×’æHþ“ºž) +Œò:Wæçìÿð_í¿kpt 2m6[x„§ÌèÇ8etÇPOQÛ×íߊ_<ð‡Â^4ñ�Üv¶¶Ê[ç`»�e÷5„¾.I-NyàêF|–Ôôjp÷¯çwÇßð_ï„>ñ„š…¤5í²É䬡·ÝÎW°¯ÒØïþ + ð—ö·µ0xrd¶ÔTmË ü¤‚?—àjy$µ”t7­–Ö„y¤�Ð0ƒ4í£ªWVö°µÍÄ‹',Ìpã_‡·üÃà§ì­ã”ð>‹?öµÔcù +$]à�Êq–Àã�>Õ×BÓV‚1Ã`ªUvŠ?u(§Wóiû9ÿÁÀ_>'øæ xÎÓì·2b9Ú# +�rváœ�À ûWôCàÿø{Ç~µñW…®’òÆñÇ,dAúwëXÔTݤ…ŠÀT¦½äu*0)sšAK^¥:§ŸÜpRy£cRGJ]í[Ç—vᱨØhÞÔ»Ú� ;±¤cŠJRrsIK‘Â�ŒŠZ1Þ¹êÓ-;«!»€¸ðþ§+¦’ïon%$.Áшž ~Y|Vý¡>,|fñø§âûÝÞK#<İ)µ†¨9À×�Á{9sô3�>Y¹;«LÑÜ­òI€N×ÈçpsU~ܲéŸg²Yy‡;À?ã]oƒ|'Ä߈O�,I¨J±´�Î#8Éïì+ûøý‡àŒ?³Ž‘ðM¸ø…¢ÛêwwIæ+Ì€�­ÐŒŒƒZfåJÍnOy ÿä|)þÁø2Þ-º·+$ÒÏ"a‰b@äûb¿©jò?ƒß¾|ðÄ^øwa…”X dw¯]#äF.¤ÝI­Ï7]IݰfcJ…º5IEjé#‹ÚA=͵ªy·2,kÓ,@™¨õ´±°’òC…‰ èzþ&à ðT?ÚÁ_uO +xOZòí¬]šá× ÜHØ¢Ž1ëUC í$ãsÐÃaDÝÏí²+ˆ' uqê¤åR×áŸü£ö©ñ§Çß„“ßøòé®nã›Xî`„.ß~M~Ük—§NÑ®ïÔàà ɟ÷TŸé\5!Ë'Ð+aœ&¢ú™çŽ|!᫤²×µ-epI$U8Lñɱ[€˜{×öƒûe|'ñOÆ€:÷€< ŠþþÙ㉎1–Ãg ƒ_ÈWí)ÿJÕ~þÊz·ÅïmmsL ,Òno˜7@¿NœÕaekKsèr—N+]ÙõÏü£ñ‡Ä)ñ&«á}rþkÉ«I#<…Ç vIÇ^•ýPüRø…¡ü/ð6¡ãOL°ÛÙÂÏ–8ä{×ñ×ÿãŸ/­@ ##+À+È¿¢ø+mõí—ìcâFÓ�35øYû‰Ú<Š——¶(íÎ>fŒ1¯äSþ ;ðƒHý½?k;Éþ+"ê ÎÒ¿šIç=ø¯êÿö­øc¨|ÿ‚eM£,M,Ðiq¼�P‘ŒŸÊ¿„ŸÙ#ö§ñ¯ì ñ®ë]ðéf¼†è† +›†�ö¬²ŠNxiòîe…›äq�ý&ÿÁn`ς߳Á /âGÂX[OÔ`¸O1A�Ñ€P0zú'þ Ûý¡>%|@ð¾±ðëÆ—Ò]ZÚD&·W%‚Œãä'õ¯çÃþ +ÿeøËûkxr/]Ù²[ȰFEê>µýÿÁ½_³¿‹>|4xŒÆ>Õl*Änç“ëKJT°¿½Þåâ£%†|çôÎrÁ¦å©3ÀRRgÄKre9Zu5>í:º.HQEÞ¶7[Q] c¸QEì ¢Š*\\ì(¢ŠÉîHQE€)AÁÍ%¼vIÉÍ%S¢Š+)7sXÅX(¢Š\ì|ˆ(¢Š’‚Š( Š( Š( çkþ ÿw%¿Â dˆÜ±=1Í?¼iÃ�Z'�õ'ÛS†vô(ÙýËÁ} i> Dû† o]§‚ã_À–¼m¦¸.ÙÚ¬Üôç]4}øq?ÐàÏüóör²ð–—cã�F—ÈDýÛr¬ã_¯_³çí!ðçö�ð±ñOÃëµ¹„¤3€x¯òIñ[Ä…RáüÑó ŒHÉþþïàÜMGW€Jš‹•�Y�+NGé\xÜ :pç[†' MË©ý=óÞŠxeèzÓ ¯6j)]3ç’è|ÝûFþÓŸ ¿fo 7Š~#]yH²¢õ uë_ÎíÍÿîøk«ü2Ô¼ðªÊG½½F�fíÏ8ö­ßø8»ÄPÝiš^“a|É%¬Ež$'“×�5üMjºÅ«ih˜4Ó+•‘@*Äg9¯C ƒŒ©)Øú,-(ÆÖÔ¿ñÅߌüUyâO¾ùnK>P`€yúWWð3övø¯ûFx¢ |.µy^r“:¶Ì ç�8¯¼½ŠM2K»bÄHRsŒ×ö{ÿÝü5ð¾§ðöÏf¦e‘THØ,÷Ï­1¯aO™{+§&~Gü?ÿ‚I~Ô?uË�¾.Òf–-$¬ÅcVeUBNï˜c5ýü5ÿ‚åüø_à-/ž4ˆ»qÒï —+íí_Ð?Åm +Ç]ø{ªi·Q+«Û¾Ppvœ{ÿ1ø(GÃ;ÿü~Öl-¢–îÖiv§‹Jþ&¸©GëZT2õV:£ýdÏÚ÷Àßµgƒÿá'ð³"°8*ŒNy úW×¼ž•ü�ÿÁ»þ=мðõ´¿ß‹fgdT˜íàŽ¿NkúŸ>˜üÈõË6ƪr}1š–Õ)¸t<¼vÓ÷‡£cK±«„O‰þ`¥µ‹T.2ʪ"x«º�¼«+¶—ªÚÜ8c¨ÁO¾_Y‰Çõ9ÿ+,xİ𞥳ï}–\c®všÿ1ßø(Uì—´O‰u+rŤ¹d�u²Ùžâ¿ÒËâg�<-¤x U½¾ÔmâAk.ȧ�‡§<×ù”þ×F|zñ­áûyîlü÷"EBCÇœ +ô2‰sÎHú ¢›Pw?¦Ÿø7‡Ä‹sðú]?wï·`§~ WõQ­Ù>©¤\ØGÖh™9ÿhb¿�?ø7©bÒ4íV}AžÐ�«²a·8bwq_ÖÅÇÄoépƒy¨BŠÞ,ãÔŽ•åcãˈ’fY…9ûDâ¶?›Ú¯þ‘­üuø™'‰´›–µ‡{ž«äíÎ:ëšýRÿ‚rþÂ?ðÆŸ cðÕÝë]Ýnc¼�¸RK�׿$×ÙOûGüŽí¬Oˆ¬üå+¼>•é~ñw‡|YkößÝÇuèÈÀƒùR©^¤â©Ïc,F.³†ªÇLÏj˜¿çÿÕLMJõ¯B”ì�qêME àb–»ÔW-lQE?fÆQEÍ€QE2§ š¸Ï,Ÿÿ]ZÿŸÿ]>ŠàžãHÿÑþû_¥5€ +)ïŒsHFTWÍû2¥¹yeÁ¯Îø+ íý†œ¡ë\Ó¥ïÅ®ç~\¯V'ó­ÿßA¨Mñ?T–xÁL—\»òòO§¥cþ>ð†~%xfãÂ^-¶[«+¥*èàƒ×­ ¿ðmìÉÿ þ­ecƒ°Íæ¹'s ƒ{Í[_>(iŸ¾êõ…/m¦Gæ8Q“Œâ¹±ï›$�[8‹uÒŽçÏ`oÙëösñE׊>iIo-ÑÞP¨Â¹êÃÞ¾Vÿ‚Úki±>³o8 Á*À÷ +2ükؾÁM¿g¯�VŽ÷—ñhó,†5Y¤\1Î1ì}«óçþ �ûM|*—öe½ð%–¡ows8 �X—ÀùHlð:ñÅg„ÃÊ5T¤N•EZ.kcòçþ Íñ¯ˆ?á!ñF�~ �2· )ÂäWçÏüÒƒþ +sa;íjŽ\ôû¬1ƒëšûÃþ ŸÑÞïâ߈­ïÏ�·fNI!8#>ƒÓ½|�ÿêðÅÏÃÿø(8×m1ž¤‘yì­·G=k¥A¼lùv±ïPjX‰[±ýÇþÈì»áIvüÍ6';ŽO*9¯à'þ +­‹ÁFåšÉÀ+¨/ÌÙÉË�¦kû)ý‰nŸ€þÈžmWYŽ;»&%’8f;3…íŒwÍÿ·oÄï +|sý¼çñ_�¤6­~» Gp,¤…<¶Œ£Ï̬rà)Ê5§&»ŸÚ7í7ñ[RøiÿÁ‡_ÑvÆeÑbFaòíf� ¯ä�þÑû(h¶í/­j?¡K‹X. Œo‰ÎxäWõ ÿ ðάÿðIÉM²ù¦-6ÀÎB„ä�ê¯ç#þ õý¢ü5ðSãÝÕ‡�î¡¶‹Q2,BraGÌO¡žià*N8Iòos"ýÜš?Kÿà¶ðNÿ�Ÿgøþ%øM‹M’Ù’ÚÁÏ<ú/ô?ü™ñëÅÿ~ê¾ñÀš=5T¨#¯Ò¼³þ +ý´¾ ø»à›ðïÀºÔZ”— ØÚ¸�;xšÆÿƒgl5K›ox�ì�mc2(�Š�²·qúŠºêr§Uërês}Qóî[´S›­6¡ŸÉ“îÓ©«ÂÓ«qQ]‘—FtQ]kc¸QEÄQEQE`÷¢Š)QEºØŠ(¦EV2ÜÚQRPQEQEQEQEüêÿÁ~ä‘~ Çå›`Àúkøzð��ßâ��4ïiĵ�#³;`çó¯îþ‘£ø-lð¦ç1¾?à$gù×ñŸûF&ý¨¼,%‘=ü (•O3’? +æÀ¿rRó>§ èųúóý”¿à‚³»|5²Õ>!@·sÝ[Æçrõ�KfºßÛ"oÁ&> ›ÿ‚ùP¶A e?CÀãÒ¿{>é³ižÓm›Ø÷p1_Îwüiª$_í¬çV1ª«��ws’}€âay§Usꎛ¨ÖçâÅßü¯ö¦Ô”êwš•ÚÇ! Á¸lÙ$~„Ÿð\�ÚÛO•ïaÕÚÖÆuÙòrG±ß“ùWáå�¢&ŽÒÉ#¤úWó'ÿÓ¢?´.·{¤ÂA á¸nœWõÿêMm7Á·X9hB©Çr@'?Ê»sšoØ_¹é×|”›?§‹ë(u I,§ÎÉS�C_Ÿßà™¿²÷Ä]RMXТkù¹›$·\ñþú ss�³Ý\¨ƒsa_�_?೟³�Á_]xè¼÷v®Qœ‚©‘Á÷ãèkÈ…:‘~æçÏ`£Vø?ÿMø ©~ÁúÊë ®†›§N �P‘óc9Àõ¯Âi¿nÚB_.fñ$¨‘ò¸Èçð"¿Z¿nßÚÃÆ_ðToŠ0xà^™qz-™­Š² ÏË´úS^=cÿþþÙZ¾ŸÖ©Ø ù…c °øq^¯¶P²ªõ=èÉ­±ðjþÞ´È�ˉ¤*�Á›$Ÿ©­½ þ +#ûMiׯkmzRTd®ù-ޤ)Áükï(?àÝïÚsË6Ù÷1z3ŒçÒ¿>?jߨSã—ì­©ÉŠâ nÿ:ÊS%ãpOô¢–"œådkõ‡²g³|$ý¸ÿi/�ŸtïƒÞ:Ö�Õž«0WuÜŒ8ÁŽýq_ÙìÉÿ´ý››áåž«ã 3íWòÆ 7™œ’3“ŽZÿ;ï‡Ûá·Ä]#â-¨fû£jôÎNêþÅÿgßø8áw…¾Xø{Å«_[D±3ÝF7pÁ¬±të]{- Tç?€êà©Ób Yê_Ù´¤š’F„í`CaIÅ->2ý¿¿jÛ¹ÛK²ñMß“ ,Ufe'=7û·þ +{ÿX¹ý«uÙm´;X—O„gr˜ÏñŸ­~jz•õíÇÚ-SΞëlp*ޤà�ƽ4’‚SÜÞ�[«=Ϩ'ý¯þ6j­î«¯^Ms.TŸLðp{à×ömÿ ø×ñ+âçÂË˯J'ŠÞEX™X°*GÌ3í_ÏŸìqÿø÷ûQx×âGŠ%þÊ‚íT…s�´÷Q�½�\WöKÿÐÿ‚|è¿°o¨üar×¶íÛŠŸ¼s’@'ë^vcŽ¥?ÝSz˜b1)E¦~žGÞ‰;R!9Å,�ªit>bkBJ(¢½ˆl0¢Š*€(¢Š(¢ŠLŠ(®7LÿÒþûÈ K�ŒP(¯ ¥s^Up¯�?n_�úÏíû7x‡án‚Ê.o↠Q�zúøœ ×­x÷Á>½M;^Õ­,î$û±Í2#À�kƒ+KC£)FJPÝ„ŸðH/ø&—ÄߨÓÅz—о K�µFâ8צ@‚8"¿w|wà}â/ƒõx¢¸°Ô¡0Í ‚§‘ùé,¯-/íÖêÊUš)UІR=ˆàÕ·8®xòI:’Ü׊�IóËsøÌý«?à†´eÇÅ»æø yåh÷r™áÃ2"¶r ÐÙäs_,x£þ?ÿñF±kŒõ�©D>YÙçyŠFHÉÃ�ÛŽ+û¡ñwÄOxÐÞø»S‚ÁÏï\) z¦¹?ü~ø7ã¹b·ð¯ˆ¬îå˜ád‰ôÁïKûN²^G ³*üº£áoø&wüŸÁ¿°·�ÚÆmZöö«Ž ;¹9ê;zTðQø&OÂïÛ‡Á7ÇÄéݨsÕC“ØsÈæ¿T“îÓ&dEÞÇQ5&¹¢ìÎã*)óÇsø§�þ ãý¯ôÍ!tOøäÁl©°F%ØŠ:àz +úCöÿƒu߃¿ÿá;øùq¤Ö÷>h+óÁ™Ï&¿¥Ýcöˆø- êgEÕ +xÇ_ &øUâ5ŸIšßìþQè£nÜ�q_ɧÅø7[âN£ñ«VñÃ�dXiJd´XÑWb“Á,|ÙÉ œó_ÙVk“ñŒ|)á ayâ�BÞÂ68 3„ÉøÛXðm¼˜K{†‰AÉÚA#<àp=kÒ�E7§CÖÂ%=bgŸðm´²| ÔõRF7$$c’Sj†úué_Ó'Ę$»ð­k !ä·uë’1_Í'üB.­~ë¶·½|è„}²›�üXšþ¡î­á½·{YÆQƯ6«ýäŽ<ÎV¬Ÿcü¿à¡:GŠü?ûNxŠÀÚÜMÓHÐRWˆ¯êƒþ Ððv» üšï^µx¤�„±n]¹RäŸ|×ìoŠ¿àž²·�5yõßør9´ŽØ$–98È8澚øiðƒÀ 4H4Ø%•½ºÑTº:1^�\[ÄARå±¶'5„éòõ:?F_º‚€mäGÊy¯óÿ‚Šx[Å?µ[É´ÙäÝ3I¿i` ê2;f¿ÒšX’hÌRŒ«õ¾Iø�ûþΟugÖü_ Å=Ä‹°°x8Î@ôïXÊÊNlÃ+ÇÓ¤š™üÐÁ½>ð®—s¯ë1Ä—¯¶X„¸Þ%Iç¾Gý}[jz4ϲÚxË‚¾˜¯äÛþ +�à+_Øò/|Ù1Má"m»~l ãä“_�?±OüöŠñ/ÇïøGÆ:”—V÷²³±.AÝÆ¦8¬e„ö‰Ö�èbp�«ï&~[PŒcƒ_Íü ð¯Zñ_�´ý_@µ7(dŒ„Yd8ÛÈ#ýxW}oÂÖwÒ�Ìñ!cêH¬Ÿˆ +<ñ.Ùm|UiÒ)Î$]Ý: t¯2 ¦¥ÊÃVTj5#ü•µ?øªñæ\¼PFÆY|�Óªôê0kÌ#‰ö¼–©±±µ¾§¨¯õý£?`�ÙÓ_øi®ê6ž³µÕÙ¶]$*|•þr¶GÃë_„¿u-ÅTÆ“HK'Ü''¦=îRÅ*º.‡¥Jq¨ÛGÅ.u8®áÓlmfºyNШ +3�ʾ¼ø ð‹âTß4øŸÃ—èPM¹^Tl3´~8ë_µÿðAOØÃÀ_"üG°Žò5 Ѥê9!xÐ×õÑñÏö1ø1ðŸRÒt +ÒÎkx‚HP0yÇNs^f78>D8´�™ƒûü}ø9¡|"Ñ´ïaµ o#Œm"¿B|3ã/ xº7†ï¡»Uëå:¶1Áâ¿Íçö³ø¿ñ;àÇÆ OÃÞÖ¦·µ°�“b± +Áã®*þ‘ÿà€¿>!|\ðmþ§ã9ÚyY‰YÈ+•, õ'œ+(`åM{gÔ19te+êM>j{õáÀÅ-zôè³æXQEéEhES¢Š(¢Š(`QEs�ÿÓþü(£­áT^ï25ûGäÿüãþ + ¥þÈ?'Ó|;w +x—PC sÊ»µø!ðÛöÿ‚€þÛ#ãÕïŠ^$¼µFÒÌA��1œ~•äŸð_�s?¶&›gur12lFcŒîÁÂ×õ‰ÿéÒ¢Ò?cßEñÙ‡#Ó=«ÎÅU•q”7gÖÊ+‡Œàµgâ§ìkûf|Sý�>3Ùkö›¼yây<¸d–@Á2xÆNyÍA?~0Ëðóà^«ñ_ÂÐ�A­lÍă%ò20+øåÿ‚°jöVŸðR{S¨H#T¾�ÈüFÜûWöeð†-Æ_<>o­ãžÚâÂca•?(íïYãb’„Öí\Ç2ŒcËY­OàûÅž<ý·ࣿ´%ç‡ã¸¾�/¦Ä0ýVSÀ>þµô�ÄŸØ÷öÑÿ‚oèvßa×'¾ÒmäV¹ŽW#Ë|qž9õ¿²�à¿Á…š­×Ž4-ÃH�Ô´· ¦ä’{WàüWöÊø1¨~Ï—Â� jö·:”À<À8åGEœŸJÊŽ*uk{.[DÓ[Ú>[hϼà—ßðQíöÊð�Ðu WûkM€4ù,Xã‚K7ð¯ÿ‚ÁÁN×öYðªü,øs oêãÉFSó q€E~?ÿÁ³i{«xçÅ2LnÌ ŒqœcŠùWþ G¯j:ÏücM°¸!Ŭ‘‘±´ã®Eo…¦£^pZ¤iOIWÐú�àoüöÎý¤<,Ÿuo\ÚËzíHÀ6Fà:÷¯eý�oO‹²�íß³WÇK£t…ÖÞ&v8P;×ô×ûƒoû2ø>iÀù´Ø�°1Õ}+ø]ÿ‚¦j2è?ðUuÔlÁŽ9nãd=ËzVe,Kwèc +j¤ålyÿþ<øOá' Ÿ‹ºì‹ö{_´¯<·p¯ãFOÚ?ö®ÿ‚¼þÑz�„~_OcáÛIš8 9�V=ØÉõsÔú×î‡üÃIJÅÿ½{¡‘4úD + Œ´@gé_�?ðlµ¿—ã­NÞEÐA!ߎ>cßßÒ·Ã?öIVê��ðØxÒ¥)­ÎÇã·ì«ûJÁ4t«O�ø�õ;UG$=Aϧ¥AðMŸÛsBý±¾&¥l¯þ˜‰ÛÏß7§Jù³þ Â~ÅWò: !;»+óÿþ ³ñ +·…u� +僱 ÑÝTŒñÚ– ÍKë{ØÎ¬}¶Î]êêŠz¨#&�å�óÿë¯zl��LŠ™#ˆÐ¹è9©Nüg.?º•sc_-6Ñ­wf/ðXø(�ö‡âý�~NÂîñ¼‰¤Bv¨c·�;úW†|ÿ‚êßü gñgSñäz¦¤¢àŽ yœ†çŸ¯µ~mÁE®§Oø( Ëç+±¹GS*€¸¯îoà>¹£Þ|Ðu 9ãdLLæ6.#äñÒ¼lÂs§*N×ZŸqˆ¬èÓ�²?—ÏØÛö ñŸìûS\~Ëž7¿:•Œ—R–líÜØÈç¡ý+ûÒïQ°‚þ?»:,ƒ>ŒοÎ×ö ñí¾µÿ5k«k•*oÒ3&qÀ|Wú +ü%½mGᮃzÇq{9õ zu(¨U„–íjyÙý4á�sÑ_­2žýi•é½Ï’ +(¢�QX=À(¢Š@QEtQEQEÎt ¢Š(¢Š(¢Š(¢Š(¢Š)=€þ|ÿྌáže·v8–*;d 0üE_±¬ÿðÖž È[˜ÆÜ`6\qï_Úü +ó¿Àh-íÆæd“o׿æ+ø¸ý�¼I§xkö–ðÖ·«§’-obÆîÃ8ü+— M}^VßSë0ë÷?ÔÇÀ±|)aÿGŒþ%kÎhÚ7á§ìÕà·ñ¯Ä{±kl3´‘Ø{Õ?„Ÿþø¿Àv:¾ŸªÛ„HNç +~U¡5üÄÿÁÀßµŸ‡|SáØþø6ö)ãµ +ìêüïÏÌáÖ¼ì¾ødyTð\õ¯5¡÷7íUÿÀý˜cøC©é Ô&:�õ»ÅåíÃ�àŒdn=3ÍÖP>öüž}«úZøÏûW| ð¿ÂÍk]oÙ\„µ—Ç(ÞÇiàɯ*·ñG•ŽŒªUØù§ã§ü�àÀßOá}NU¼»G QÈIêyà_i~ο´o‚?hßEãL¤8ËÅŸ™ó¯ó3ý³¾(ÉãÿŽSøßJ¸‘­!‘ÌaŽr3À¿¨Oø7ÿö¥ð•Ç�®¼/âËÿ³Fy”ðÜÕ[šÐúïÅð\/‚¿4û�‡~´Úžë5Îì¸Ç +@Ï_¥~rxÇþƒñö¸»ºø�uqª³³ùnv3çØWàOì}­A«~Ôž·œˆ YÓfx!�¯ôÉø£þÞœÊ>Sg8êvŒšíÅÓXi/gÔôëÊ4!x-Ïæëà¯Áëßø$‡>Ùâv7–Qò6žž•ßx§þ £ð÷Å–³xÂÊÃRÕCÙ»o˜�¸ïšú'þ ®-­ÿgËwP¡äi†;Pàþü$üñÍ·ƒ>)i^#×fyb‚TgÝÆ0Çå]‘ÂÓ­Gž¦æô'6�êZÇþ¥ã?Úæîo‰Þ&½‚Î é¼ÆîS"0ÉûªsÉö¯ßïØ3öðoìIðþ? xno>G•ù<àp8 q‘ŸzâdoÛƒö|ñ7Á� + ïÙØÜ­º&É¥DÜp>îq_¡¾ñ¯†ükfoü7wÜ ýøØ2Ÿ¡ó7©)(Mèqãñ¹ZKÝ;001KEõt©Ù#ç.QEjEPEPER`QE`ÿÔþûÇJSE!8¯ +? ÍVçðAÿûÓíbýº"TA"Ço”¤å·™kûý‚’Eý’|ffÓã$~ülÿÁy^7ÿ‚‚Ax¡—ìopGïϦêþÊ¿`ýBÏPý’<uiŒgFÌ3‘\Ý;R§cësþËÿðY2ÜÿÁJ§¶²ÈtXeQŒ’F09ìkû@ý‘í¦·ý›ü ßxi°ç>ë_Ä/ü_wÿ‚’Kq£NHŒJz*í8÷ÉýµþÊÑØ~ͽզƒL‰ËÈ@ +¡z“éŽk t’�&×B³jìÐKs¥ý£~럾ëðõùÓnõ( )8?s=M _ðRoø$Çǟ٧÷_¼G~|I·�-ГåHËc…oN8¯ôÑüI x�]´;ÈnÄxÜbpøÏLãé_Œ?ð^«Ý"ÓöÖùÕ&gP»�UfT#ð*Ã.ªþ²—s‡-­58Òhùgþ Þ¶ø7¢ü ½¶ðÙ‹þ&/-ì€e‚m�·\Wà—üªâ+_ø)Õ½±Rò$égù‹>ö?ý ÿƒeàŽMsÆÖ¸& mâeýà²0$Ó¨¯Ëïø,…ø´ÿ‚š\\FÇu£ªÌDù‚ðsÔä We+}v¬WcÚö*8‡cûéý’ uý™ü'äÓ"  â¿„¿ø*ä‡þDÑêxŽxÊŸC»µu¿±Æ²5¿Ù¯Â—.»vؤdöF+ø"ÿ‚³jbø(õìöN&Qp9 ó‚?*Œ‰|^‡YüiÜþ—¿à¤rYÁ+¾Ó,„¢év‘‚{nŒt¯Í¿ø6uçˆüIy°‚¨8ãjŸækï�ø)½Åßü;«g2Ç—bòL{…‰X¶=ðkóÓþ ~ñU¼^6ñF�4êDÖhËÐwž=‰'©?öz�/õyµÿð]Ù|�ØkV¹f!VM§Œçp¿.?àÚiä×ÞâD01�ñ‚ |6}zWêüföÒØ«Pµº•ÎsµòÅBýßq_–ðlµÌÞwŠ!™—v1Ÿ› +r3õÜ*ë ùpŠÙ͇vÀÉ]Oì>í:šŸv�^ôv>5î@y9¤¢Šç’Ð#¹ü�ÿÁeàš3¾ñìß´gÃ8.5I.t–Ðd(çJü»Óÿj¯ø(ÇÃO„÷^ ²ðÕþ™e B¹a&ý§�ߎدîËÄß> ZxÖ?†>(Õ-ST˜nŽÞr¿6=2iþ>øqð«ÄžÕ%Ô4Û9me·’W‘0v!ù·Ž1^*Æ%ê}= ÂpIN'ù€ü)Ôu/~ÔÚµã d›R¹Õ]eÈpùÉÜ>µþ£ÿḷøSáø®,,`(ñ¶°Ú§‰µ î ™Àvl~^õúgðþ§ûbü}ð=¿�ì, ¬2�èÍÃ2¿NP+èSÿï~ÙÚœ‹j7ÄRòH¨»p;y¨©Š‹vmhW2î~ +6¡h#ò®¦Ú§€Ø#ŸJÍÔ."{o&Õ·‚0Øãvzƒí_¹Ÿ?àߟÚcáÆ…7‰µI¢¸¶€y�æ¡#rŒ’67 {�_‰þ-ð'‰¼ «ßøwÄGå¬í·ä8=sŠäŸ,þs).Ä~ø‡ã?† ¨ø;SžÐŸ•„mãÒ½ëÄ´‡Æéëe¬êò]DQ@,ä2ã¶Aïë\OÀßÙ»âí'âx<#à =æI±åŒn!±Àõ8¯ÓŸÁ¿l‡~“ÇݶRÚb‘v�È'¯NõÓJPŽ’z˜JkšÍŸ”—ž —Q¶ ­ÈP9çŸs[Ÿ ¾,|Fø[zoü«M`Ò¶Z5<õçŒ×âÍRð¶·uáï*Ç5£2H;C‚¾‘ý‘¿b�ß¶WŠSBøu[EÎëƒÇãòzûU©¯Š{Zjfx¯ö–øëã‹i´¿k—/`ÝmâeQ ÿm‡ÍÏ¥x¼³`G¿Ì-gù†08÷¯ß/ÿÁ»ß´G�|(ºåƧrdPòÜ•D`.v©ÈgÎ;(úWáW�<↞,Ô¼âXçY­YÐ4£¶’QëNšRv�—6§î?üöðíWãÛß|EˆOdþé†~U=yâ¿­‹ðMÙ§GøW«ÿbèVö—Ù•'Š1æªvœãÖ¿†ïø&7ühþŜֺ„7C¹G‹û‡$îö=«ö�â¿ü+yñáÖ¥ðóÁ¨?¶µò Xò_ç½+ÄÄá+Ë¥OáUNecñö‚ý¡~.ü4ø‹©ü3ðæ¿wŒ´1íf +Ø$qôï_ê:·Š¼G­Mw⯵I0ùƒ†n}GÖ¿tþÿÁ¿jÚ÷C?5iÖÊmU¼õóÃ+ÿ0 â½÷ÿðl·ÇY$3k:ÒC*Ÿ¾²ç#¸#ù×ÑÏI¤”‘èÔ”ZÖV? ¿c)§ý¨¼<‰þ³ÏN¼ã,1Ũ¿Á{gƒá¦‘nü¶ŒûäWò×û6Á»~&øKñwKø‡©k›šÎEgó@|rHÃþB¿­/h‘x{E¶ÑámÂÞ5�v1�£åVýä•�1«”S?ÿà¶šU¾¥û8<�æu�F1ÎBœàöÀ¯óÃÔ´± ôѬ¹�\ì®êÿDoø-�Ä–ß³ªJäRG@Hœõ¿Ï/ÄV÷þ ñ“Xè µÔÆ5ò†A òk·O÷|§U(9aâdxÛÃCíúv§9’ÐXÚBxÇÎ�}zV5jI¥ÆöØú*YÂäQšØÿ=Ï…Ÿiø(çíj¾7ñ�q§ÉªL’ÎåÂ0û»†Çï_Þ§�¾x–Ùqþ ø&ô[êvÚ:éö× Àܨ'Û�^ñᯅÿüv×þÒmlç|æH£Unz€@é]°8æ¼ê÷”¹¤a‹Í¥Q«-�àA¿hŸø(ïü‡ãUσ¯¾×¬™¤Ú<ÈÙÒP¤íè1ÎN>µæß·oíáûd~Ý´ð?�¼1y¦i¬Ê ¶DÌ��¤¶�ž�¿/ï«Äß>øÏUþÜñV�e¨]ñûÙãß/NO¥aËû9ü +™ƒ¿…4ì«…Wõ#3íZ<ÅEéNªY½4îã©øÿÿ~¾?ýžü«|Qñ×É7ˆÑ¡ÏÊŠÅ‚Œñõü«à?ø/·ü/âF£ñCRý­üdºŠóbÉ —Qݹ”g8=[Óý«iÚm†‘e›¥Â–öð¨HãŒUQÐ*®·áýÄ–-¦kö‘^[¿XæPê^u'R5]dõfó‰*¾Ñ­àWà/üþ +Aðá=·Ã�#Ã÷wÚe´mI$eÜ)à0b21Ö¼#öoýŽÿi�ø(í2Þ;ø…§\éóO2¼¥“W<ôz’kýåøð‚x¼™|9§•ôòü+¡ð—ßxKÿ†•m§yßë ªÇ©šïX¦“Q�®uVÎi¸µ«>=ý¢c»_‹ÿ±½÷ì×e(…åÓb³�‡#0 ú•¯â;áWÃ�ÛCþ …ûDjº/Ã�òúåK�û—häMÜ7ÊppzgŽõþ�aÿ?ιkïøGQÔ­¨é–³Ü°Úd’$vÇÕ�5…R”y#³8ðy«‚垨þÿjŸÚ#þ +ûuK¦ü6ñ_†nìíÝã?»�ÀÜ0¤·\är}À¯éëþ ÿü›ö;øYý½â€ÃZÖY‘ò�â¡$œ�¥~¸Aàÿ ÚηÚe¬R/!’R>„ +êÆ1Åvá£*�sh�±ù²©HFÄË÷iÔQ_F�¯EW38ŸðU�Ø+⎥â!ûDü ½º¸Ô’Ush¬KoêH#^àvþ_ø7Ä_ðT�x[þ%æ�}¤Ùß/“$ÎÌrŒ6`žOþ5ý”¼i"ì‘CCÍ0XZ«nX�ßh®ªÊÖÐúç²T½›W?!¿àžðM ~ÌÖOãß"ê(Ô›4®£!›?Ë&¿_@`p)HÇVôhrêÝϽw7vQEtQ@QQ)tEÆ7 +(¢¹¥Ê=‚Š(®Ã0¢Š(¢Š+œè +(¢€ +(¢€ +(¢€ +(¢€ +(¢“Øçëþ Ûe#ü[¤¼°Ù_öB±'ó¯á“àv•câ?�Þ�Tp±4ÊÒoÀ‹�‚ON+û‘ÿ‚ù\~¤’bpwãŸç_ÁŠÞêk©X¸I¢û�Œr2A¬p:ÓåógÖá×î"ª§ìë…,¾ xrÇÃÒEåEeUB¹éžÕîáþb§¿½š'Áø+íWðKìš6™¬<–0D³åÙ6ŽBäðAè+ûÿ‚K~Ú^5ý­<>³â‰ÞæE#s± ÉÉ9ôÁ8v¯„pNg|ºê\ýyñ–—aàûû+ØÕákyT« ƒ•=�˜'üëÂóø{ö§ñ-¾ƒl ²–w�ªÇÆÇñÉ?ZÿP/™SÂ7ëËùŒôèküÃࣗWßíUâ�Îà=때^?"Etä�Zܬ½6ÏÜø6“Ãþ ¹�TÕ<@¶°Ü@Ìðî*äüÞ�+úçøÕ”ÿõûÉÞ£ŽÆy76 +à!ägÓµ–Wì÷ûY|\ý‘¼_.«ðþàI¿4�HÍ·w¨ÛƒÅ~‘h?ðTÚ—ö¡ñ‹ðN}Jkk ö7•+…Îzôäðiãpõ]KèMj +o™³óçöÐÒô ~+ëçA·y%iˆi‚’€ÈÜ‘îEX_ðlÇÃhtO†ÚÏ‹.­6\3ciT)ÛŸ¼2+ïïÙ»þñû/j ô¿|G±“R¿Ô#ŽâbÅpîÁl©Ï<ŠýZø#û<ü/ýž<='†þéÑÙÛÊûܪ(bqÜ€c[0ö�öq0Äã!Ù÷Ä=×Yðuý¤Ñ«‘ ²“Ù€È#ñ¯ó ÿ‚�]Çÿ 7â6!§fE#üÌ;}+ý@ü_8‡ÂÚŒŒÛqo''ýÓ_æ;ÿ3Þ~Ðúà’»w'•Æ víÔqÍo”ÂÜÃÀ;ųó+T15ÒÙE™%•JíQ’8éÅ{¯ìÓàæO�¾Öîì.bƒíËç«DsˆÈ'ûWì×üWþ óà/ÚÃÇwž7ø‰h·VvÄŸ%Ç]¬sŒtÆ+úÊø£ÿÂý›¬>_]xGE†ÇS³·wŠu!ÕxobqÍi_ +u=›Ôïö°O•³ìïÙŸÆ~ ¾ø'áé4»»x‡ÙzoQµ�Q×±¯¤ìî­¯#ó­%YSÕ#ô¯óEøÏûP|wø#ñ/]økáÜGoe+ü±ÌT�¬AÛÎ=Áý?Á?j/ˆÿ|©Yx¦þk»hˆ‘RáÌŒ�²1¸õäJxO`•Fôg6;*J¢‘ý#ƒ’Ec]ø‹AÓæû5ýìȆIOÄŠ³}vl¬f¼Æ|¤gÿ¾A5ü!ÿÁJm¯ŽÚÇ�PézÛBQ¤�Cƒ´ ô:ºRu$£ËÁ`ý­Ógë÷üOã�„¼Qð¶?†^ »×ÄK¯lÛÂ3à�qè'Þ¿Ž�‚>ñ•�ÆÝÜh·ÛÇ!Þî‡Ë$Œd1_ÒÏüûᆟû[ÜKã?Šlu%\°Š_š0Àã8ïÆ:þ�d‘A*Ë×ÕGìäé´{5+B’Tdi~ÌßþéŸü?¢ÉªZÀélˆŠÒ*ƒ…ŸJú¯JÖ´­j>“n‡7/å^Ñ3ú)¢”uçcïB7�á7f2Š(­ +(¢€ +(¢¹Î€¢Š(¢Š+œÿÖþü(¢Šñã±qZ…ãš ÀÍ|]û_~Ú¿ ¿dŸKâ]Dnˆ"+v�+3c+Ç$æ¹kÕ×–;�t(J£åŠ>ǹ½´²…®.äXÑFK1À{šðŸþÓŸü¼kþ%²„Æ2ãÍS·ëÏü0~Û_ð^¿Ž¾>Ö®|/á‹áe¦¾õòàà�r'·øGã¿ÛǾ*{ˆ5û¶šc»|“?ζkE–NK÷�#Ò†”ˆÏõHOÛ_öi`Ž|QjA�ÙÊãê+Ù¼+ñoá·�à[� k6׈ÿt£Ž~•þDÖ¿µÅ»KHtÛ]já£UùTÈÜ ô'5÷ŸÀßø)÷Ä�éØz¥ì¡È2¤Ž]c9ÁÀ$ ÏW<òhý™jhðTÍŸêT##�iêyÁ¯æ»þ ëÿ¥Ó~,ßÇàŸŠ.›—b‰È°p±È÷Ükú:е½+ĺTÞ‰:\ÚܨxäB}ÅqÕ¡*R呿âp®›ò70´Òƒ·ùýiËŒqI½i´ºœ£‡SN3ïJ)�š.x$óRîP:×Ëÿ´WícðwögðÔº÷ÄmR+y>\$’îÀg‘_Ë7í#ÿ|Uñv·yá¿€ztëk+ÄQ¸ç:zf·ÃB¤µ‚ùž–*©Qsl�ìÂmFÎÙ‚ÜL‘“Ù˜çVd�nFÈõþ~þý·jψw“kž3›Y�ÎNð%#òþUé~ +ÿ‚µþÓ?6ß>weÝÀ-í�^+ë¥ €}k +X…7dŽŠøIÓI˨´W‰üvø×á¿�¾ “Åž!pð‰œ?_JÒø+ñ[KøËà;_i ²‘�3ŸÖ³�K»èÉCŸ¡ëTÖ$)ÕâHÍMW²2,‚ž”e=?ÏçL Wo20³i=)*S íþJ¯<©[€£4JIubÞˆ}ùÅã?ø(·ÃOüz‹à�ÛG$Ò2¦T�à±À9$)Úý±½·Ôl¢¿µ`ÑÊ¡Ô�CÍfÚêvb0u(¤çÔ·JvãŠç¼Qâ�ÁºLjüGs¥�ª’Yj¨5üï~Ôð\ß ü?ñlÞøk^ÚDæ1tå|µcNR“j(èÂeµ+«ÇcúA¢¿ ?aÿø+÷„¿h�¯„¼W$Ï! ¹Û¸ûóÅ~æÛÜCu +Ü[°tp +�r= )Tå—$ôdã03¡+LšŠ(­ãœv +(¢ °¢Š)=€þzà¿M§ÀHç Ñ*{ƒ_Á4�$×ÖÆ5ÌíˆãÉûÄð1_ßGü¾ÞÞ_€žkòÉ0}Iãü?|$²Òaø½¢êÔ¾]¼7jÒ%Aõê+7»OO3ì0*øtÙôÇÀ¯ø$�í×ûEh¶þ5Ò¼9åè’HÖ;7Ô�ØÈúWö£ÿ†ýˆµ÷ŸÆ¯ø.¿Á�cáþ¥áØ´Ú†£lñ@ƒæÞ¤QÍ|Ïÿãý¡>xÏL²Ñt=F É`…£&6 w·N‡#üŠü,¾²ð¯Æ/ë~#`Ð[Þvl�F{‚q^”h©ÚsZž•:q“æ’?htŸø$'í;ûiÝËñŠ+sd×ɼG�¿»c•Ãdâ¿H?c�ÇÿðG¸g—âÅ‘šÚàŸsª|ˆIÈ<Žõû§û&~ÕŸ³Éø?¤Á»ilÉk˜K*Œí`ßJüUÿ‚éþÕß°ñücá?Ä{UøsðŠÔ7ˆuehaýâ̈ÏÀåxÏÖ¾ø�ÿBøÿûRjâf©yocq¨Ç½¼Ç+.îÆ}«ù¥ý–|wáÿ‡ß4ëc`µ¼Viü¡G|wüëý?fßÛ{à‡�~éÏy­[Ú¼vë³Í‘Tº�ÔklTjaš�£Ý$¨£Êà—ÿðO‹¿Ø�á±ÐüIp—“;dÆr¡OÇ5ú«wj.md·?òÑJþcà–ÿµGìÿ<ÞL^)±Ü}d}3Óõ««ûLüi<µñ%™aÛÌ=k(Rv¼Ó»ÙˆÂÂÒ6{gïq_ÀeŒÐ^FfÁÎqÑkôcþ ÙñëTøñÒÏÄ¥ŒVsO»« ë p¶zÒ¸³.t_.èì„yââÏõ¢FÇ#¡¥|gŠñÏ€Ÿô¯Š¿ +ôŸés$Âx;!Ȇ}³Í{ç¥yXisÅ6x á.Qè;×Ëÿµ×íáŸÙ¯àίñÄr¤bÞÞCg — ò�ž¤ž‚¾ &¿‘Oø8“ãV·®ëZìó¤Ë*­üÑK²,’à­€:í95×FŠ•E±ß—Ðç�ßCâ†ÿ ÿh/ø+×Çû�CVÔg�‘Ì[{p©á�£ tã=kõëâ‡�ÿàœðK¯Ámâ;{½\ V ¢Y c©Ýèy¯µ¿`O�Ú_ì{ûÚx¨Â±ßb‹û‡e·´a”Ìf¿�8|?ñ+þ +‹ÿ»ð¦µ4÷ºSê.rp�ÇÎ8é“[Шë¹=¡è¡zŠM½úwÿƒøXºü—~ðVí)\yn-׿î1�Á¯¸þx§öÿ‚‰i²ø#WÒì­u»øð0‹ѳq”> ×ë÷€?à™ß²—†þX|>Õ¼/iy1(vxÔ18ÁÉ5ø�ñcþ¡ñ+áwíWmñ;ök»’ÃBûZ:¢><¤|3;AéX_;¥xµ³¹çÇMÊÐ>]ý«?à�¿ÿc=y>5|Ô&Ô´ˆœù€¨%s‡U, `^½E~ØÿÁ*ÿà v|+¯ˆ" rЈ`G  ”7®zϧJý^ðWƒ¯%øWmàÿ*Þ»Úˆf~ð>W0=kùøÕ¡]~ŸðR�1tU6ºF¯z’@¬"w °ùFTç;R£?m¥ºÔé¥YbbèÏ¡ý°I�¼úW;á^/x_OÖàq"]ÛÇ*°èC¨9ýjæ»4öú=Ìö瑳ô­ãˆ~Ï™0èûü¬ÑŽö _dR+7 #4·pÛBÓܸ�’Ìpú×áwìSûHøŸÇ?¶?‰~k7Ȱ0¬Å‘G`£°èkîø(¯Ä�|*ý™uÏxfM“ÁPO#'� S§‹¨êrHô'•Z´i_sîûKÛkÈDör,±žŒ¤ùŠ|ã1¸ö5ùGÿ‚ø»ã¿Œ_³$~&ø‡z·—Ò\9]¡@TÏL.1×Ò¿W˜Œ|Ýêª×¼d¥èqâ0þÊ£�cùVøññNø_ÿ²¿ñŽ mtÅœ#oèá�§®kúwðß�|â[('Ð5;k¥‘So—"±;†GCÔ×ðíÿØÕuM'öœŠ×@‘ÅíÔœ2.æR7`xÀ¯%øñ ö÷ý˜âÐþ%øÊk˜ôé"òüíÛJ!ïè@é]±Â©aã(;;mÜú\f5!»;ÔÇüCí0~Í{bÅeyа¹ãñ¯Vÿ‚QÉ<¿±¿‡®î]�åÞIn¹¥|yûküfÓhØ +×Ç(ëö‰ã‰Šî Hcôã5§û|~µø7ÿõ“ÆZˆ,ÚDrº çsè~œ×‘(ÊTáÊõ¹Ã,;tU>§îö»¢é˜•Ü6ÅŽ–EBO¶H©à¿³ºŒMm"Ê¿ÞBüÅ>,ý¤¿n�Û�ãn±â„Ö÷—ºV™)H] dŠ äü¤:õàŸs_°_ðHÿÛ3âm¿Œï?g�¯%® ZQn'bKHœ%²AÈ8çÚº1”jR‡;whÏû*6ÑŸÓœ~ Ðæ¼m>+¸št81‡�ôÆsVîµ (üëÉ’$õrüÍ-ß¶¿í/ñ‹ö@ý­,|M¨LÃÃó\¥Ìžfv˜KŒ�}@WŸþÐðRß~Û<-ðoötœÆ è÷2Û± À‘œ�ýÐ3øW,1\9¤´f¯%Z>mëf9cš1,LHÈ#¥r¾>ÔJðf§¨»lÛÈÛ½0§šÃøU£Ýè~ÒôëÙZib¶Œ3¹Ë´u÷¯ý¶¼{€gm{W•ö+BÊÄvà“�ÈV°Ä)%ȧNÕ”Qü1~Ð?u�SöûþÒº¹alº”+æ(ö =Gþ€Ÿõ{ cán…¨i;,¡Ú}~Q_ç;âÙßãÄ?ê´e�´ñX$Ëu!¾aîR¤`+ûrÿ‚T|m—ã?ìÁ§_ÜcužÛ|g'r¨Ýø]YÍNXFqé¡ífQç‚kd|£ÿÃøý­ø+àË|4ðÜí Þ¯µÓ‚rxæ¿2¿boø"¥÷Çß„ ñâ¶«%¥Ýò ‹n@ÝÈÎO¿Zö?ø,.§7Ž¿kÿü=¹ŒËoźydðûˆÈ?�Mß øsÄw€¬ÒÙǽ[†S·¡÷ÇZò¿Ú‡öZøeû@Oayâ·�fµ•1#�¤~=+ê_‡^ Ò¼á+O èÇ0[ U>¼V”ñN¼¢šÕnrfXÅR’lî(¢Šõícç/p¢Š+p¢Š)K`?Ÿïø/µÃEû8 D»œ÷ë�ú×ð7â ;ˉÄðÊÛÔn\AaÛâ¿Ó›þ + ûOûbüºømk:ÚÉp6ù§¨‚?Zþmî?àÙïêigñ�2  «N:Éä{W-©BÒ>“ �„i(ÍïÃÏÚŸö„ø{¡ i:õäÚ`G6W®z×´é_ðP_ÚžÆe{-Zl:“óHFìp>£ûÅ¡ÿÁ¯Úô’µÎ­âæÚù,7mf㸫§üï«!FÒüQ€•‘›#ÛµrÎtÛ»dº°—ÚGó©ã/Úûö„ñÜÏÄíд-ºHUØïÏ©bsŠùªó†iP”‘²]Ÿ’à÷ɯêœÿÁ¯þ7Œ¬'Äöò"’TòÛ}>óŽõ×üÿãhG™iâ[iL…L¡òNåTîãõ1©MlÍc^œUœ�äÛþÝSìrIk8XÀÉ8ê*ªXÎcr§k oãÓ"¿«ý;þ ~ñìPˆn5øe²G˜û±Ÿ©³qÿÆxɤ�Ää7—µBmõˆÿ0½½>çò9 …̳µ­¦Ù”}âF=>µê> ºñ'�­üÍ6æ[‰Ÿyx\¦rGjþžGüùñ +e/'Š•=7pÃߊַÿƒcüw+Çlþ$¬|;2{ V~ÚÌZ«O¹üóÛþПl)—\¹Pp»£˜äÀ×K/í7ñ‡G�g“ÄWŽ“ 2´§æ>ù9¯èzOø5ö{˜ÚâÉ!@Cc~yÇAÆMb·üõ¬ÝX¼S幘–ÁõÂÕ¼uæü ¾µO¹üÄxçÆúÏŽ®®¼EâRkÛ‡á—#bóÆ0{W›ê�ͦ™\:€cnà�ë_Öý§üúl¬Ä2x¬3ƒó´`üÞã=«^ø6 g’™Ü±‘žGA“ÒŸöŽÖ¿àÎJµiôgòy់Ÿt=#ìV³,1¨%•®üt°µ/øÓÆÞKxÊmûXј³zçq&¿®™ÿà×Ï]X­•ψ]�˜òÇ“óú$S­à× -»Ã«xµ®#_õB0Ѻðy?1éWý¡C¿à\qä·?�[ëU�Ííˬe†TgŸLŠì´‹^=ðÚý‡MÖïxT•¶Ž8Wõ±¤ÿÁ°^¶rú·ˆ%º*pŒîx^À�ŸÏ5ß§üðÀÄ|Fñ�2BîÆIé’iÿiRoAÆ­8ï#øûÿ†ƒø«§Xˆ®|Aqä–ÉŒJßxû“šít�ÚOâ~¡i-…¿ˆ.Ž8üîï“Ö¿®[ø6sàäqÇkw¨ù±©ÜîX–'ïÛÚ»-7þ ›ý˜í.£¹{é'R ù¸ô­qIt'ë”ûŸÈ�Ïíñ£NŒÅ‰æ‰í”8A6ý¬:kúúÿƒ~~8|@ø¡à;›Ÿ\4þoÊϼ°v_Q’Ç9©äÿƒh¿fggWŸÌS¸.Ó´ã nrGã_¯Ÿ±Oì!ðÓö,ðkøgÁ4Í9#m +¡»ì;ô¬gŠ�hòÅjr×ÅÑ«Ÿw'J} ¥¯v +ÈùÆQEP‚Š( šÿv�L~•6EEW :Š(¬ÿÐþý”&«Mþ©¾•a?ÕÔ2(d*{Šòj|Ö›÷�áþñ¶«§üZ¼ð½åÖbŽ1(‹RYxÈê}kùŠpnn%Ϙ�É`xÃC_Ùü‹ðÁ-~'Ùª‡6‰#-’G`vã�­·:£Ï|ÑÈ0’Çãõ®šnÔcÛœ•ŒËåÈ]™�F3žµõìá®x§à �ã g)ip›˜@qØæ¾cKkÝCZ´�GŒÏ*8b dèké߉_¤ºðŒ¿ÒÄw^Z¨8ÀP=*sƾ1øÎOŒßÅÂ0’OVQüG<ÇžÃR[â¾S/9ÝÇ ¨5í>? Ø[ë÷<Œ§h=¸ªW¾(Ôu6h§!J—<`jO�üÚþ£5œ×i +vm<·Œv®ƒN²‡Âž&m-�„qʲ)ë–^@'ÜŠó� kZ½‹Í¨ÛÌU“î‚>S�Zõ¿„Z¥ñ7â�‚ˆMîÉiãŠyNjéè®ÏC ä¢�ôßÿ‚&ëž"ñì9áû�n%Fr]Yycï_¯#­| ÿÇð*ø ö:ð¦ŠmÉü€íu\�€3ÛÒ¾ü +s_?†�îÒÒç‘�½i‡ü³5ücÁe´JÛºö¡›c•LYÁNY?_ǵg¡~\WóñÿÀý�u/xCJøÅ Û‡“JuY6¯ÌYNA,9ÅvRN5<Ú= šªöŽ©úyâks¯~ÄÒÛØ€Zo ¦mÂÏê !_ðDiÞÿ‚€jZv³¶Þy®§‚G~…¤;zã¶F+úEÿ‚hþÓý ?gëo†º½âI{§Û5…Är0³i\mëì2ç_†ß¶üßã�ì}ûC·í ûÄÖÑÁò«ÔÐó(eqMºÏCû|ð—Š´ox~ßĺ —¶ºPñ’1�kùDÿ‚çM£?í3àá¦íþtp7 n8õ­K×Z¿…¿f?�GPñÒÇcáû1¾G;GÈ¿ãøšþFtOêðPÏø( ·Šü=g5î‘§_‰"|¡Q¾÷#uúô¬p2IʯCl²’…IT_ +?°ÿ€‰$?ü7»¾KûÇHìOZôÝY‚i³±ìŒCF‘h,4«kbDÀéò¨S5¸ä—I¸Š%ÜÍ? +®KSå«Ð‘í]Nµà-wRÿ‚~ůY–šÖÕš€dœŽ‚´–‰?0‚Øýƒÿ‚&üð·†eKˆÒ{©»Ç½S »IÝŸ\ä`ÿ�~|þÕ>ѾÁOô_X)X¯/c™âŒm'.™û¸ûÜóï_§ÿðEÝ~ÛVý‘­tô•${yÎà‡îðÏ¿;l}_Bñ¯üÛÃúEœÂW´»�GÉ;ܧ+ÏÂs{ZœÛXç åíd™ôŸüCàv�ñöeµø‡"†—CŒû(* ôþuùÿìüÑücñƒXø…¬Û©—L. ¯þü‰¯×Ÿø-§ÆÏ xöQžUkíAxŒö +æ¿?àß_ÚSCøañ¼®ÚȳkSI˜Ü(Fœz +ôáw‚³:Ú—±iÜñ íí¼+r­µÙ·”f br§×ý™ÿÁ;>9kß?gÍ;Å ‘¥™¼«¶BJsŽôf4ªšO,vÿ?­>Š·J/t$Ù—ô¤òÏ¥OEeõZ}‡ÎÈ<³éG–}*z)}RŸ`çe/Ú�/Ú¬QIà©v´e/Ú�/Ú¬QSõ]ƒÚ2¿—íIåûUš(ú….ÁíWËÇ<ÓªÅ//§Ð~ЯI…«4T¼¾!í +ØZiNr*ÎÅ£bÔK.O¨{B «KSlZ6-U<(:„C­MµEK]°¦‘ °¢Š+AQ@Q@5†E:šßv”¶CEWžö:Š(¬tÿÑþü!l®=iŦ*í\TÁ¾^kÏ…;Å&4ì;ð^ïÙ‹Yø«ð_þ�1DâÕ m +/ÌÀ äŸAÖ¿Î'â‚o|âùô XJ³©ûÛ~BL5²—Å�…þø·à›ßø‘I‚í +‡^Ž}=+ø&ÿ‚­Á,¼KðïX¾µußo34ö×QD@l_OnƱ§&½Éµ©Äþ[<9¬Â'¯G¨(•»ñï^«qá_|jñ z–†‚8íP–w8È•äÿ¾xïán«$^%µdR»'†8ÈëTôOŒ>#ðæš-´¬´{¶8n­ž…ò2Ö¹¨]C$ÞÔahüãÕk–ºO·@$¨ñÁÁ÷«7º­®±~רžW™ÉÏ''©¢ÒÏZÔ.MÐíÞöê_•cA’Äô§ãŒÍm/È—NL*Þlœ(îÕûÕÿrýˆuï�¿,f¸‚h–)Ð8Qò¬y—8äzWÄÿ±çüãâwÅoCkâK7[·`c‰$dôã8â¿Ñsþ ™ûøwödø_a|öb RxÎ,¸`qÓÞ¹±MÔ\‘;cUP^Õî~•x+Ã^ ð¦Ÿá}=vÅcB¿ðk¬UÉÏjk T©÷k¢–A$|ÕJŽrrc0y5çß~øw⟃o|âˆD¶×‘²r2T‘€GÒ½šÀ‘�J½%¦ã¥QÅÝÆ7ÇÙÃö�ÿ‚tüi›âOÃaq6†÷&Fû*±C lþóŒ{šýsøÿ|ýŸ&µŽêÞt(ÊêWãíÿIøñJiõÏ +ÊtË÷CÐ`3cƒ‘Œs\£¿ß-{£è(bèÖ_½vgЯñ?þ ¯ãb5�J} ÈÇ~gUS‘ß¼Ïâ·üŸþ óû0hR[øsU´º‘GËo¦¢áˆã…~|aÿ‚'þÑ^ ñ ®‘á!.¯ †$¤Í€ãž•™ðKþ ùøéñIJ‰Ìº>šWqyÉläôP9-�ÿ]%€ÃGÞ”›;=…Šs¹Kö›ÿ‚Ž|}ÿ‚žø’ÓàßÀ�:kêVˆÛ ËHݽç¥FŸðMø'ç†?cï†V·𬞠»Œ5Ĭ>`ÍÉäŒõ®Ïö"ÿ‚l|ýŒôX—Ãñ-þ«Õ®Yã(9?­~�šé�inUh£ÌÇfI¯gIYŠŠTDÑŸâU’™9&šÃº1UË¡âÆvgòÿjðO�?goÚZËã÷�㔢²³�ÒëÎ;ù/Ç?ðROÚ›öÝ‹LýŸßK6–·RÅ¥ÉLŽqŒó_Ùíû2ü>ý¥< Þñ¬[W$¬ª ·#稯›?gOø&GÀ_Ù÷Ä#Ŷ0¶£¨/Ýy@Âú`cÒ¸"ÚŠN7k©ô´ón–èöïØ³áüø ¤xV@ â0Ò`çƒô¯¬iðA1ˆãPª8qRâ©`o õ<؇)¹ÃÏü3ÿ<+û^Y|J𕼲�1ÖDdBØaÈ=á_1h¿´ïí�ûsÝè³Ò5ܺx‘£) +1ÁÝ�ØW÷}ã߀? >%ßOÇ:®£1]›å@N+›ðì­ðᎾÞ&ðG†¬ì/Ç™G¸÷­eUªj®§» Þ‰5©øÿûCþËMð�ö‹áÖ—nf¿{e‰Ž%ñ–ÏãZ?²·ìŸ©øïöºøeâ2o,YB¯Ul¤\×îÆ·á� ÄvŸa×-Ræî8ȧèžÑ|9d4í +Ù-`ìA�\¯/*8ÿ´—-‘ü iŸ?mßø&v¯¬øOÃ:sç]NÊÓ?Î' +áAÏ=yWÜ?ðLÿ�~<üpºýª~.ÛIP(š)¥\nv;²+ú¡ñ·Àï„_�dñχ¬µFC�gˆ7>þ¿Žk­ðç‚|'á <=ám: (Æ"ô¥J³OMY³Í!mµ?“oÚá/Ä_ÛÓöÆO†²ØÜ�N�Â× Ä,ˆz•4ßÚãö¾ý”¼Wá_üÓ¦�á’/6HÆ¥Bè½WÚ?Ã�h:ĺþ“§Eäç/*¯ÌsïWüCàÏx®5‹Ä‰t©Ê‡ûf·µ}"Öˆ¿íxÝ$´<Ÿà‰µßüÑõÿ¡KÙ-•O$8×óÁeðïÆÿÿà£Ûz¥Œ²iúUîåm§`Øz“_ÔfŸ£Øiv ¦XD"�Tpsº_ÿhš¤šÞ™§Ã Ôijʨ1>§®?©M7¦æx|Î0çk©½in Óã·Œ`"öü“ÿÁC¿f/ˆúoíï |Jð­¬Æ ®Ñæ” ³p<þ½]˜Îkžðlj7×,aºh›rî3D02§ð­Ì09�³mµ¹ùÃûa~ÊSþÓ³¶•§(“S±¶I¡óŠ'ÝŸÖ¿“sûQþܱV¿yà="ˆà‰ÈGŽ#ŒÎ=+ýÚáñ £hP8Ç¥|ÿñöZø1ñ.õ¯üS¤G$®>b8Ͼ*èR�8{7dtÒÌâ×-D +~×hÿÛ¿ãÍ„>$3ÝÉ)_9Xª’~ûòpEtß²ÏÁ÷ø'ðwJðTüMCzú9¥øKû)| +ø%¨O«ü=Р´º¹’R1ý8¯£ãï]ÔÔªÉ6¬‘ÉŽÇs.Hì;;©Çž(¢½4� (¢Š`QEQEQEQEQEQEQEQEQEQEQEQEQEQEQE×û´êkýÚRØkr(¢¼÷±¸QE�ƒ?ÿÒþü�@)TeM4œÔ©÷j ýÔ6:¼«â·Áßüaðì¾ñ¥”WPÈŒ™u @aÏQ^«EsÔ¤§£*qwGòýûPÁ´Ï½ÕÇ��¾©¦ËÊÛÌ KNp§ væ¿™ÏÚþ÷ñÁ:¦ªoôiíâÏ–�o`$Ràž•þœeA¬CÃ:ª¥5 8g®øÔç?Q\µ(VŠ÷ëas5i£üŸì?à˜_nïa³Ó4«Û¨�úÇX(пJýzý‹¿à‰¼OãË;¨ü=5†–UÝ]¨Eõ;¿§Zþþ¬|á-=BÙi–±ÏË +gùWE ¬0.Ø” a@ô¬éCÕ¤mW7�؉ùÛû Á9>~˺{²¥Tg®Õ$œû“ø +ý‰c‰Dq� q�Nù‡ Ò”ç­z0ªž=|Dª;ÈB™9§�ŠZ+¡ì`QE`I�KE0´mSKE+ :RÑE0 +(¢€´vâ”zZ)¨€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 37d3368c3..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,7 +203,6 @@ External serial flash WP25R1635FZUIL0 #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) - // 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