diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml
index 7cd0dfcac..5c1c453dd 100644
--- a/.github/actions/setup-base/action.yml
+++ b/.github/actions/setup-base/action.yml
@@ -5,7 +5,7 @@ runs:
using: composite
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
diff --git a/.github/workflows/build_debian_src.yml b/.github/workflows/build_debian_src.yml
index 5c441f085..7f3f8b672 100644
--- a/.github/workflows/build_debian_src.yml
+++ b/.github/workflows/build_debian_src.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: recursive
path: meshtasticd
diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml
index df1035e62..2ef67405a 100644
--- a/.github/workflows/build_firmware.yml
+++ b/.github/workflows/build_firmware.yml
@@ -20,7 +20,7 @@ jobs:
name: build-${{ inputs.platform }}
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml
index cde7fd274..26a9cff18 100644
--- a/.github/workflows/docker_build.yml
+++ b/.github/workflows/docker_build.yml
@@ -47,7 +47,7 @@ jobs:
runs-on: ${{ inputs.runs-on }}
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
diff --git a/.github/workflows/docker_manifest.yml b/.github/workflows/docker_manifest.yml
index d1d1a5634..20b9ceee6 100644
--- a/.github/workflows/docker_manifest.yml
+++ b/.github/workflows/docker_manifest.yml
@@ -83,7 +83,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
diff --git a/.github/workflows/hook_copr.yml b/.github/workflows/hook_copr.yml
index 94aaca49c..2204cc02c 100644
--- a/.github/workflows/hook_copr.yml
+++ b/.github/workflows/hook_copr.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: recursive
ref: ${{ github.ref }}
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index a98bdc011..ed14907dc 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -42,7 +42,7 @@ jobs:
- check
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: 3.x
@@ -72,7 +72,7 @@ jobs:
version:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -93,7 +93,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Build base
id: base
uses: ./.github/actions/setup-base
@@ -288,12 +288,12 @@ jobs:
]
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- - uses: actions/download-artifact@v4
+ - uses: actions/download-artifact@v5
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
@@ -322,7 +322,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- - uses: actions/download-artifact@v4
+ - uses: actions/download-artifact@v5
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -367,7 +367,7 @@ jobs:
- package-pio-deps-native-tft
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v5
@@ -386,14 +386,14 @@ jobs:
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v5
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v5
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -436,14 +436,14 @@ jobs:
needs: [release-artifacts, version]
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.x
- - uses: actions/download-artifact@v4
+ - uses: actions/download-artifact@v5
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -460,7 +460,7 @@ jobs:
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- - uses: actions/download-artifact@v4
+ - uses: actions/download-artifact@v5
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
merge-multiple: true
@@ -491,14 +491,14 @@ jobs:
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.x
- - uses: actions/download-artifact@v4
+ - uses: actions/download-artifact@v5
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 309772b12..f26073ec4 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Trunk Check
uses: trunk-io/trunk-action@v1
@@ -31,7 +31,7 @@ jobs:
pull-requests: write # For trunk to create PRs
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Trunk Upgrade
uses: trunk-io/trunk-action/upgrade@v1
diff --git a/.github/workflows/package_obs.yml b/.github/workflows/package_obs.yml
index 275ffce0e..4c547eadc 100644
--- a/.github/workflows/package_obs.yml
+++ b/.github/workflows/package_obs.yml
@@ -34,7 +34,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: recursive
path: meshtasticd
@@ -58,7 +58,7 @@ jobs:
id: version
- name: Download artifacts
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v5
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
diff --git a/.github/workflows/package_pio_deps.yml b/.github/workflows/package_pio_deps.yml
index 9f535b7b1..13d3d1b4e 100644
--- a/.github/workflows/package_pio_deps.yml
+++ b/.github/workflows/package_pio_deps.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
diff --git a/.github/workflows/package_ppa.yml b/.github/workflows/package_ppa.yml
index a54b0bd36..aece730a0 100644
--- a/.github/workflows/package_ppa.yml
+++ b/.github/workflows/package_ppa.yml
@@ -32,7 +32,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: recursive
path: meshtasticd
@@ -60,7 +60,7 @@ jobs:
id: version
- name: Download artifacts
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v5
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml
new file mode 100644
index 000000000..786feeced
--- /dev/null
+++ b/.github/workflows/pr_tests.yml
@@ -0,0 +1,238 @@
+name: Tests
+
+# DISABLED: Changed from automatic PR triggers to manual only
+on:
+ workflow_dispatch:
+ inputs:
+ reason:
+ description: "Reason for manual test run"
+ required: false
+ default: "Manual test execution"
+
+concurrency:
+ group: tests-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+ actions: read
+ checks: write
+ pull-requests: write
+
+jobs:
+ native-tests:
+ name: "π§ͺ Native Tests"
+ if: github.repository == 'meshtastic/firmware'
+ uses: ./.github/workflows/test_native.yml
+ permissions:
+ contents: read
+ actions: read
+ checks: write
+
+ test-summary:
+ name: "π Test Results"
+ runs-on: ubuntu-latest
+ needs: [native-tests]
+ if: always()
+ permissions:
+ contents: read
+ actions: read
+ checks: write
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@v5
+ with:
+ submodules: recursive
+
+ - name: Get release version string
+ run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+ id: version
+
+ - name: Download test artifacts
+ if: needs.native-tests.result != 'skipped'
+ uses: actions/download-artifact@v5
+ with:
+ name: platformio-test-report-${{ steps.version.outputs.long }}.zip
+ merge-multiple: true
+
+ - name: Parse test results and create detailed summary
+ id: test-results
+ run: |
+ echo "## π§ͺ Test Results Summary" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ # Check overall job status first
+ if [[ "${{ needs.native-tests.result }}" == "success" ]]; then
+ echo "β
**Overall Status**: PASSED" >> $GITHUB_STEP_SUMMARY
+ elif [[ "${{ needs.native-tests.result }}" == "failure" ]]; then
+ echo "β **Overall Status**: FAILED" >> $GITHUB_STEP_SUMMARY
+ elif [[ "${{ needs.native-tests.result }}" == "cancelled" ]]; then
+ echo "βΈοΈ **Overall Status**: CANCELLED" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "Tests were cancelled before completion." >> $GITHUB_STEP_SUMMARY
+ exit 0
+ else
+ echo "β οΈ **Overall Status**: SKIPPED" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "Tests were skipped." >> $GITHUB_STEP_SUMMARY
+ exit 0
+ fi
+
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ # Parse detailed test results if available
+ if [ -f "testreport.xml" ]; then
+ echo "### π Individual Test Results" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ python3 << 'EOF'
+ import xml.etree.ElementTree as ET
+ import os
+
+ try:
+ tree = ET.parse('testreport.xml')
+ root = tree.getroot()
+
+ total_tests = 0
+ passed_tests = 0
+ failed_tests = 0
+ skipped_tests = 0
+
+ # Parse testsuite elements
+ for testsuite in root.findall('.//testsuite'):
+ suite_name = testsuite.get('name', 'Unknown')
+ suite_tests = int(testsuite.get('tests', '0'))
+ suite_failures = int(testsuite.get('failures', '0'))
+ suite_errors = int(testsuite.get('errors', '0'))
+ suite_skipped = int(testsuite.get('skipped', '0'))
+
+ total_tests += suite_tests
+ failed_tests += suite_failures + suite_errors
+ skipped_tests += suite_skipped
+ passed_tests += suite_tests - suite_failures - suite_errors - suite_skipped
+
+ if suite_tests > 0:
+ status = "β
" if (suite_failures + suite_errors) == 0 else "β"
+ print(f"**{status} Test Suite: {suite_name}**")
+ print(f"- Total: {suite_tests}")
+ print(f"- Passed: β
{suite_tests - suite_failures - suite_errors - suite_skipped}")
+ print(f"- Failed: β {suite_failures + suite_errors}")
+ if suite_skipped > 0:
+ print(f"- Skipped: βοΈ {suite_skipped}")
+ print("")
+
+ # Show individual test results for failed suites
+ if suite_failures + suite_errors > 0:
+ print("**Failed Tests:**")
+ for testcase in testsuite.findall('testcase'):
+ test_name = testcase.get('name', 'Unknown')
+ failure = testcase.find('failure')
+ error = testcase.find('error')
+
+ if failure is not None:
+ msg = failure.get('message', 'Unknown error')[:100]
+ print(f"- β `{test_name}`: {msg}")
+ elif error is not None:
+ msg = error.get('message', 'Unknown error')[:100]
+ print(f"- β `{test_name}`: ERROR - {msg}")
+ print("")
+ else:
+ # Show passed tests for successful suites
+ passed_count = 0
+ for testcase in testsuite.findall('testcase'):
+ if testcase.find('failure') is None and testcase.find('error') is None:
+ if passed_count < 5: # Limit to first 5 to avoid spam
+ test_name = testcase.get('name', 'Unknown')
+ print(f"- β
`{test_name}`: PASSED")
+ passed_count += 1
+ if passed_count > 5:
+ print(f"- ... and {passed_count - 5} more tests passed")
+ print("")
+
+ # Summary statistics
+ print("### π Test Statistics")
+ print(f"- **Total Tests**: {total_tests}")
+ print(f"- **Passed**: β
{passed_tests}")
+ print(f"- **Failed**: β {failed_tests}")
+ if skipped_tests > 0:
+ print(f"- **Skipped**: βοΈ {skipped_tests}")
+
+ if failed_tests > 0:
+ print(f"\nβ **{failed_tests} tests failed out of {total_tests} total**")
+ else:
+ print(f"\nβ
**All {total_tests} tests passed!**")
+
+ except Exception as e:
+ print(f"β Error parsing test results: {e}")
+ EOF
+ else
+ echo "β οΈ **No detailed test report available**" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "Test artifacts may not have been generated properly." >> $GITHUB_STEP_SUMMARY
+ fi
+
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "---" >> $GITHUB_STEP_SUMMARY
+ echo "View detailed logs in the [Actions tab](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
+
+ - name: Comment test results on PR
+ if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped'
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+
+ // Read the step summary to use as PR comment
+ let testSummary = "## π§ͺ Test Results Summary\n\n";
+
+ if ("${{ needs.native-tests.result }}" === "success") {
+ testSummary += "β
**All tests passed!**\n\n";
+ } else if ("${{ needs.native-tests.result }}" === "failure") {
+ testSummary += "β **Some tests failed.**\n\n";
+ } else {
+ testSummary += "β οΈ **Tests did not complete normally.**\n\n";
+ }
+
+ testSummary += `View detailed results: [Actions Run](${context.payload.repository.html_url}/actions/runs/${context.runId})\n\n`;
+ testSummary += "---\n";
+ testSummary += "*This comment will be automatically updated when new commits are pushed.*";
+
+ // Find existing comment
+ const comments = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number
+ });
+
+ const botComment = comments.data.find(comment =>
+ comment.user.type === 'Bot' &&
+ comment.body.includes('π§ͺ Test Results Summary')
+ );
+
+ if (botComment) {
+ // Update existing comment
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: botComment.id,
+ body: testSummary
+ });
+ } else {
+ // Create new comment
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: testSummary
+ });
+ }
+
+ - name: Set overall status
+ run: |
+ if [[ "${{ needs.native-tests.result }}" == "success" ]]; then
+ echo "All tests passed! β
"
+ exit 0
+ else
+ echo "Some tests failed! β"
+ exit 1
+ fi
diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml
index e52e67227..ccd99e792 100644
--- a/.github/workflows/release_channels.yml
+++ b/.github/workflows/release_channels.yml
@@ -60,7 +60,7 @@ jobs:
shell: bash
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v5
diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml
index e391aa07b..96c993cba 100644
--- a/.github/workflows/sec_sast_semgrep_cron.yml
+++ b/.github/workflows/sec_sast_semgrep_cron.yml
@@ -21,7 +21,7 @@ jobs:
steps:
# step 1
- name: clone application source code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
# step 2
- name: full scan
diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml
index 3707c91b8..e93b2ae8b 100644
--- a/.github/workflows/sec_sast_semgrep_pull.yml
+++ b/.github/workflows/sec_sast_semgrep_pull.yml
@@ -13,7 +13,7 @@ jobs:
steps:
# step 1
- name: clone application source code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
fetch-depth: 0
diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml
index dc05959fd..6b788f4c7 100644
--- a/.github/workflows/test_native.yml
+++ b/.github/workflows/test_native.yml
@@ -14,7 +14,7 @@ jobs:
name: Native Simulator Tests
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -70,7 +70,7 @@ jobs:
name: Native PlatformIO Tests
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -127,7 +127,7 @@ jobs:
- platformio-tests
if: always()
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -137,7 +137,7 @@ jobs:
id: version
- name: Download test artifacts
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v5
with:
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
merge-multiple: true
@@ -150,7 +150,7 @@ jobs:
reporter: java-junit
- name: Download coverage artifacts
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v5
with:
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
path: code-coverage-report
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 34b28b39c..52f180aa2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: test-runner
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
# - uses: actions/setup-python@v5
# with:
diff --git a/.github/workflows/trunk_annotate_pr.yml b/.github/workflows/trunk_annotate_pr.yml
index 62c1c01b7..23dcf8d09 100644
--- a/.github/workflows/trunk_annotate_pr.yml
+++ b/.github/workflows/trunk_annotate_pr.yml
@@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Trunk Check
uses: trunk-io/trunk-action@v1
diff --git a/.github/workflows/trunk_check.yml b/.github/workflows/trunk_check.yml
index 55656bf48..41731d491 100644
--- a/.github/workflows/trunk_check.yml
+++ b/.github/workflows/trunk_check.yml
@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Trunk Check
uses: trunk-io/trunk-action@v1
diff --git a/.github/workflows/trunk_format_pr.yml b/.github/workflows/trunk_format_pr.yml
index 33f4182eb..2d191fc44 100644
--- a/.github/workflows/trunk_format_pr.yml
+++ b/.github/workflows/trunk_format_pr.yml
@@ -15,7 +15,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml
index 3952d9d02..c06e06b0a 100644
--- a/.github/workflows/update_protobufs.yml
+++ b/.github/workflows/update_protobufs.yml
@@ -11,7 +11,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: true
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 2d4d3fc16..de38e3ec0 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -1,6 +1,6 @@
version: 0.1
cli:
- version: 1.24.0
+ version: 1.25.0
plugins:
sources:
- id: trunk
@@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- - checkov@3.2.451
- - renovate@41.40.0
+ - checkov@3.2.461
+ - renovate@41.74.0
- prettier@3.6.2
- - trufflehog@3.90.1
+ - trufflehog@3.90.5
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.64.1
- taplo@0.9.3
- - ruff@0.12.4
+ - ruff@0.12.7
- isort@6.0.1
- markdownlint@0.45.0
- oxipng@9.1.5
diff --git a/Dockerfile b/Dockerfile
index e033b1bba..b1e151ac7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
-FROM python:3.13-bookworm AS builder
+FROM python:3.13-slim-trixie AS builder
ARG PIO_ENV=native
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC
@@ -36,7 +36,7 @@ RUN curl -L "https://github.com/meshtastic/web/releases/download/v$(cat /tmp/fir
##### PRODUCTION BUILD #############
-FROM debian:bookworm-slim
+FROM debian:trixie-slim
LABEL org.opencontainers.image.title="Meshtastic" \
org.opencontainers.image.description="Debian Meshtastic daemon and web interface" \
org.opencontainers.image.url="https://meshtastic.org" \
@@ -51,8 +51,8 @@ ENV TZ=Etc/UTC
USER root
RUN apt-get update && apt-get --no-install-recommends -y install \
- libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libuv1 libusb-1.0-0-dev \
- liborcania2.3 libulfius2.7 libssl3 \
+ libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
+ liborcania2.3 libulfius2.7t64 libssl3t64 \
libx11-6 libinput10 libxkbcommon-x11-0 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /var/lib/meshtasticd \
@@ -61,7 +61,7 @@ RUN apt-get update && apt-get --no-install-recommends -y install \
# Fetch compiled binary from the builder
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/bin/
-COPY --from=builder /tmp/web /usr/share/meshtasticd/
+COPY --from=builder /tmp/web /usr/share/meshtasticd/web/
# Copy config templates
COPY ./bin/config.d /etc/meshtasticd/available.d
diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index 4a77ec4b2..36effe017 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -23,7 +23,7 @@ build_flags =
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - - - -
lib_deps=
${arduino_base.lib_deps}
diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini
index 693ab63b7..20b3f8e3d 100644
--- a/arch/portduino/portduino.ini
+++ b/arch/portduino/portduino.ini
@@ -2,7 +2,7 @@
[portduino_base]
platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
- https://github.com/meshtastic/platform-native/archive/6cb7a455b440dd0738e8ed74a18136ed5cf7ea63.zip
+ https://github.com/meshtastic/platform-native/archive/37d986499ce24511952d7146db72d667c6bdaff7.zip
framework = arduino
build_src_filter =
@@ -17,7 +17,6 @@ build_src_filter =
+
-
-
- +<../variants/portduino>
lib_deps =
${env.lib_deps}
@@ -35,6 +34,7 @@ lib_deps =
build_flags =
${arduino_base.build_flags}
+ -D ARCH_PORTDUINO
-fPIC
-Isrc/platform/portduino
-DRADIOLIB_EEPROM_UNSUPPORTED
diff --git a/bin/config.d/lora-RAK6421.yaml b/bin/config.d/lora-RAK6421-13300-slot1.yaml
similarity index 56%
rename from bin/config.d/lora-RAK6421.yaml
rename to bin/config.d/lora-RAK6421-13300-slot1.yaml
index bbf38a474..6f65f9ccd 100644
--- a/bin/config.d/lora-RAK6421.yaml
+++ b/bin/config.d/lora-RAK6421-13300-slot1.yaml
@@ -9,13 +9,4 @@ Lora:
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
- # CS: 8
-
-
- ### RAK13300in Slot 2 pins
-# IRQ: 18 #IO6
-# Reset: 24 # IO4
-# Busy: 19 # IO5
-# # Ant_sw: 23 # IO3
-# spidev: spidev0.1
-# # CS: 7
\ No newline at end of file
+ # CS: 8
\ No newline at end of file
diff --git a/bin/config.d/lora-RAK6421-13300-slot2.yaml b/bin/config.d/lora-RAK6421-13300-slot2.yaml
new file mode 100644
index 000000000..cbc794d39
--- /dev/null
+++ b/bin/config.d/lora-RAK6421-13300-slot2.yaml
@@ -0,0 +1,8 @@
+Lora:
+ ### RAK13300in Slot 2 pins
+ IRQ: 18 #IO6
+ Reset: 24 # IO4
+ Busy: 19 # IO5
+ # Ant_sw: 23 # IO3
+ spidev: spidev0.1
+ # CS: 7
\ No newline at end of file
diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index d52b804ee..f3b3bb14d 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.6
+
https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.5
diff --git a/boards/meshtiny.json b/boards/meshtiny.json
new file mode 100644
index 000000000..1473388ea
--- /dev/null
+++ b/boards/meshtiny.json
@@ -0,0 +1,52 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "nrf52840_s140_v6.ld"
+ },
+ "core": "nRF5",
+ "cpu": "cortex-m4",
+ "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
+ "f_cpu": "64000000L",
+ "hwids": [
+ ["0x239A", "0x8029"],
+ ["0x239A", "0x0029"],
+ ["0x239A", "0x002A"],
+ ["0x239A", "0x802A"]
+ ],
+ "usb_product": "MeshTiny",
+ "mcu": "nrf52840",
+ "variant": "meshtiny",
+ "bsp": {
+ "name": "adafruit"
+ },
+ "softdevice": {
+ "sd_flags": "-DS140",
+ "sd_name": "s140",
+ "sd_version": "6.1.1",
+ "sd_fwid": "0x00B6"
+ },
+ "bootloader": {
+ "settings_addr": "0xFF000"
+ }
+ },
+ "connectivity": ["bluetooth"],
+ "debug": {
+ "jlink_device": "nRF52840_xxAA",
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
+ },
+ "frameworks": ["arduino", "freertos"],
+ "name": "MeshTiny",
+ "upload": {
+ "maximum_ram_size": 248832,
+ "maximum_size": 815104,
+ "speed": 115200,
+ "protocol": "nrfutil",
+ "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
+ "use_1200bps_touch": true,
+ "require_upload_port": true,
+ "wait_for_upload_port": true
+ },
+ "url": "https://github.com/meshtastic/firmware",
+ "vendor": "MTools Tec"
+}
diff --git a/debian/changelog b/debian/changelog
index 9421e9925..b36a22168 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-meshtasticd (2.7.5.0) UNRELEASED; urgency=medium
+meshtasticd (2.7.6.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging
@@ -37,4 +37,7 @@ meshtasticd (2.7.5.0) UNRELEASED; urgency=medium
[ ]
* GitHub Actions Automatic version bump
- -- Sat, 09 Aug 2025 12:46:53 +0000
+ [ ]
+ * GitHub Actions Automatic version bump
+
+ -- Tue, 12 Aug 2025 23:48:48 +0000
diff --git a/platformio.ini b/platformio.ini
index 62bbf8a24..cce4d2dcf 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -60,9 +60,9 @@ monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
- https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0119501e9983bd894830b02f545c377ee08d66fe.zip
- # renovate: datasource=custom.pio depName=OneButton packageName=mathertel/library/OneButton
- mathertel/OneButton@2.6.1
+ https://github.com/meshtastic/esp8266-oled-ssd1306/archive/9573abb64dc9c94f3051348f2bf4fc5cedf03c22.zip
+ # renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
+ https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
https://github.com/meshtastic/arduino-fsm/archive/7db3702bf0cfe97b783d6c72595e3f38e0b19159.zip
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
@@ -102,6 +102,14 @@ lib_deps =
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
arcao/Syslog@2.0.0
+; Minimal networking libs for nrf52 (excludes Syslog to save flash)
+[nrf52_networking_base]
+lib_deps =
+ # renovate: datasource=custom.pio depName=TBPubSubClient packageName=thingsboard/library/TBPubSubClient
+ thingsboard/TBPubSubClient@2.12.1
+ # renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
+ arduino-libraries/NTPClient@3.2.1
+
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
@@ -110,7 +118,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
- https://github.com/meshtastic/device-ui/archive/0cd108ff783539e41ef38258ba2784ab3b1bdc97.zip
+ https://github.com/meshtastic/device-ui/archive/3dc7cf3e233aaa8cc23492cca50541fc099ebfa1.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
diff --git a/protobufs b/protobufs
index e2c0831aa..8985852d7 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit e2c0831aa3d34a58a36c2b9fdcb828e58961cbc5
+Subproject commit 8985852d752de3f7210f9a4a3e0923120ec438b3
diff --git a/src/BluetoothStatus.h b/src/BluetoothStatus.h
index f6bb43cc2..680aec929 100644
--- a/src/BluetoothStatus.h
+++ b/src/BluetoothStatus.h
@@ -89,14 +89,22 @@ class BluetoothStatus : public Status
case ConnectionState::CONNECTED:
LOG_DEBUG("BluetoothStatus CONNECTED");
#ifdef BLE_LED
+#ifdef BLE_LED_INVERTED
+ digitalWrite(BLE_LED, LOW);
+#else
digitalWrite(BLE_LED, HIGH);
+#endif
#endif
break;
case ConnectionState::DISCONNECTED:
LOG_DEBUG("BluetoothStatus DISCONNECTED");
#ifdef BLE_LED
+#ifdef BLE_LED_INVERTED
+ digitalWrite(BLE_LED, HIGH);
+#else
digitalWrite(BLE_LED, LOW);
+#endif
#endif
break;
}
diff --git a/src/buzz/BuzzerFeedbackThread.cpp b/src/buzz/BuzzerFeedbackThread.cpp
index ce762c764..838224c69 100644
--- a/src/buzz/BuzzerFeedbackThread.cpp
+++ b/src/buzz/BuzzerFeedbackThread.cpp
@@ -28,6 +28,7 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
case INPUT_BROKER_USER_PRESS:
case INPUT_BROKER_ALT_PRESS:
case INPUT_BROKER_SELECT:
+ case INPUT_BROKER_SELECT_LONG:
playBeep(); // Confirmation feedback
break;
diff --git a/src/buzz/buzz.cpp b/src/buzz/buzz.cpp
index b09d7a82c..b0d162a44 100644
--- a/src/buzz/buzz.cpp
+++ b/src/buzz/buzz.cpp
@@ -140,6 +140,10 @@ bool playNextLeadUpNote()
playTones(¬e, 1); // Play single note using existing playTones function
leadUpNoteIndex++;
+
+ if (leadUpNoteIndex >= leadUpNotesCount) {
+ return false; // this was the final note
+ }
return true; // Note was played (playTones handles buzzer availability internally)
}
diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
index a627a42cc..1c9f290b6 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -140,13 +140,13 @@ bool EInkDisplay::connect()
#endif
#endif
-#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1)
+#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
{
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
adafruitDisplay = new GxEPD2_BW(*lowLevel);
adafruitDisplay->init();
-#ifdef ELECROW_ThinkNode_M1
+#if defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
adafruitDisplay->setRotation(4);
#else
adafruitDisplay->setRotation(3);
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 8d5635f89..5e29814cb 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -216,6 +216,44 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t
ui->update();
}
+void Screen::showTextInput(const char *header, const char *initialText, uint32_t durationMs,
+ std::function textCallback)
+{
+ 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
+ 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);
+ NotificationRenderer::alertBannerMessage[255] = '\0';
+ NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
+ NotificationRenderer::pauseBanner = false;
+ NotificationRenderer::current_notification_type = notificationTypeEnum::text_input;
+
+ // Set the overlay using the same pattern as other notification types
+ static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
+ ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
+ ui->setTargetFPS(60);
+ ui->update();
+}
+
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
uint8_t module_frame;
@@ -365,9 +403,6 @@ void Screen::doDeepSleep()
{
#ifdef USE_EINK
setOn(false, graphics::UIRenderer::drawDeepSleepFrame);
-#ifdef PIN_EINK_EN
- digitalWrite(PIN_EINK_EN, LOW); // power off backlight
-#endif
#else
// Without E-Ink display:
setOn(false);
@@ -391,8 +426,12 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
dispdev->displayOn();
#endif
-#ifdef ELECROW_ThinkNode_M5
- io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
+#ifdef PIN_EINK_EN
+ if (uiconfig.screen_brightness == 1)
+ digitalWrite(PIN_EINK_EN, HIGH);
+#elif defined(PCA_PIN_EINK_EN)
+ if (uiconfig.screen_brightness == 1)
+ io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
#endif
#if defined(ST7789_CS) && \
@@ -424,13 +463,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
setScreensaverFrames(einkScreensaver);
#endif
-#ifdef ELECROW_ThinkNode_M1
- if (digitalRead(PIN_EINK_EN) == HIGH) {
- digitalWrite(PIN_EINK_EN, LOW);
- }
-#endif
-#ifdef ELECROW_ThinkNode_M5
+#ifdef PIN_EINK_EN
+ digitalWrite(PIN_EINK_EN, LOW);
+#elif defined(PCA_PIN_EINK_EN)
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
#endif
@@ -694,7 +730,7 @@ int32_t Screen::runOnce()
#ifndef DISABLE_WELCOME_UNSET
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
- menuHandler::LoraRegionPicker(0);
+ menuHandler::OnboardMessage();
}
#endif
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
@@ -715,13 +751,19 @@ int32_t Screen::runOnce()
handleSetOn(false);
break;
case Cmd::ON_PRESS:
- handleOnPress();
+ if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
+ handleOnPress();
+ }
break;
case Cmd::SHOW_PREV_FRAME:
- handleShowPrevFrame();
+ if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
+ handleShowPrevFrame();
+ }
break;
case Cmd::SHOW_NEXT_FRAME:
- handleShowNextFrame();
+ if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
+ handleShowNextFrame();
+ }
break;
case Cmd::START_ALERT_FRAME: {
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
@@ -743,7 +785,9 @@ int32_t Screen::runOnce()
NotificationRenderer::pauseBanner = false;
case Cmd::STOP_BOOT_SCREEN:
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
- setFrames();
+ if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
+ setFrames();
+ }
break;
case Cmd::NOOP:
break;
@@ -779,6 +823,7 @@ int32_t Screen::runOnce()
if (showingNormalScreen) {
// standard screen loop handling here
if (config.display.auto_screen_carousel_secs > 0 &&
+ NotificationRenderer::current_notification_type != notificationTypeEnum::text_input &&
!Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) {
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
@@ -869,6 +914,11 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
// Called when a frame should be added / removed, or custom frames should be cleared
void Screen::setFrames(FrameFocus focus)
{
+ // Block setFrames calls when virtual keyboard is active to prevent overlay interference
+ if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
+ return;
+ }
+
uint8_t originalPosition = ui->getUiState()->currentFrame;
uint8_t previousFrameCount = framesetInfo.frameCount;
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
@@ -1315,6 +1365,11 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Triggered by MeshModules
int Screen::handleUIFrameEvent(const UIFrameEvent *event)
{
+ // Block UI frame events when virtual keyboard is active
+ if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
+ return 0;
+ }
+
if (showingNormalScreen) {
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
@@ -1337,6 +1392,16 @@ int Screen::handleInputEvent(const InputEvent *event)
if (!screenOn)
return 0;
+ // Handle text input notifications specially - pass input to virtual keyboard
+ if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
+ NotificationRenderer::inEvent = *event;
+ static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
+ ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
+ setFastFramerate(); // Draw ASAP
+ ui->update();
+ return 0;
+ }
+
#ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw.
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 265900131..0f100d455 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -12,7 +12,7 @@
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
namespace graphics
{
-enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker };
+enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker, text_input };
struct BannerOverlayOptions {
const char *message;
@@ -313,6 +313,8 @@ class Screen : public concurrency::OSThread
void showNodePicker(const char *message, uint32_t durationMs, std::function bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function bannerCallback);
+ void showTextInput(const char *header, const char *initialText, uint32_t durationMs,
+ std::function textCallback);
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
{
diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h
index 3373a47a7..84ec45977 100644
--- a/src/graphics/ScreenFonts.h
+++ b/src/graphics/ScreenFonts.h
@@ -16,7 +16,7 @@
#include "graphics/fonts/OLEDDisplayFontsCS.h"
#endif
-#ifdef CROWPANEL_ESP32S3_5_EPAPER
+#if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
#include "graphics/fonts/EinkDisplayFonts.h"
#endif
@@ -40,6 +40,9 @@
#ifdef OLED_PL
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_PL // Height: 19
#else
+#ifdef OLED_RU
+#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_RU // Height: 19
+#else
#ifdef OLED_UA
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_UA // Height: 19
#else
@@ -50,9 +53,13 @@
#endif
#endif
#endif
+#endif
#ifdef OLED_PL
#define FONT_LARGE_LOCAL ArialMT_Plain_24_PL // Height: 28
#else
+#ifdef OLED_RU
+#define FONT_LARGE_LOCAL ArialMT_Plain_24_RU // Height: 28
+#else
#ifdef OLED_UA
#define FONT_LARGE_LOCAL ArialMT_Plain_24_UA // Height: 28
#else
@@ -63,6 +70,7 @@
#endif
#endif
#endif
+#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)) && \
@@ -77,7 +85,7 @@
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#endif
-#if defined(CROWPANEL_ESP32S3_5_EPAPER)
+#if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
#undef FONT_SMALL
#undef FONT_MEDIUM
#undef FONT_LARGE
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 3e9bafc6c..f8787612f 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -667,15 +667,19 @@ static LGFX *tft = nullptr;
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
#elif ARCH_PORTDUINO
#include // Graphics and font library for ST7735 driver chip
+#if defined(LGFX_SDL)
+#include
+#endif
class LGFX : public lgfx::LGFX_Device
{
- lgfx::Panel_Device *_panel_instance;
lgfx::Bus_SPI _bus_instance;
lgfx::ITouch *_touch_instance;
public:
+ lgfx::Panel_Device *_panel_instance;
+
LGFX(void)
{
if (settingsMap[displayPanel] == st7789)
@@ -694,6 +698,11 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance = new lgfx::Panel_ILI9488;
else if (settingsMap[displayPanel] == hx8357d)
_panel_instance = new lgfx::Panel_HX8357D;
+#if defined(LGFX_SDL)
+ else if (settingsMap[displayPanel] == x11) {
+ _panel_instance = new lgfx::Panel_sdl;
+ }
+#endif
else {
_panel_instance = new lgfx::Panel_NULL;
LOG_ERROR("Unknown display panel configured!");
@@ -754,7 +763,13 @@ class LGFX : public lgfx::LGFX_Device
_touch_instance->config(touch_cfg);
_panel_instance->setTouch(_touch_instance);
}
-
+#if defined(LGFX_SDL)
+ if (settingsMap[displayPanel] == x11) {
+ lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
+ sdl_panel_->setup();
+ sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER);
+ }
+#endif
setPanel(_panel_instance); // Sets the panel to use.
}
};
@@ -849,9 +864,29 @@ static LGFX *tft = nullptr;
#include
#include
+class PanelInit_ST7701 : public lgfx::Panel_ST7701
+{
+ public:
+ const uint8_t *getInitCommands(uint8_t listno) const override
+ {
+ // 180 degree hw rotation: vertical flip, horizontal flip
+ static constexpr const uint8_t list1[] = {0x36, 1, 0x10, // MADCTL for vertical flip
+ 0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x10, // Command2 BK0 SEL
+ 0xC7, 1, 0x04, // SDIR: X-direction Control (Horizontal Flip)
+ 0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x00, // Command2 BK0 DIS
+ 0xFF, 0xFF};
+ switch (listno) {
+ case 1:
+ return list1;
+ default:
+ return lgfx::Panel_ST7701::getInitCommands(listno);
+ }
+ }
+};
+
class LGFX : public lgfx::LGFX_Device
{
- lgfx::Panel_ST7701 _panel_instance;
+ PanelInit_ST7701 _panel_instance;
lgfx::Bus_RGB _bus_instance;
lgfx::Light_PWM _light_instance;
lgfx::Touch_FT5x06 _touch_instance;
@@ -1040,6 +1075,49 @@ void TFTDisplay::display(bool fromBlank)
}
}
+void TFTDisplay::sdlLoop()
+{
+#if defined(LGFX_SDL)
+ static int lastPressed = 0;
+ static int shuttingDown = false;
+ if (settingsMap[displayPanel] == x11) {
+ lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance;
+ if (sdl_panel_->loop() && !shuttingDown) {
+ LOG_WARN("Window Closed!");
+ InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
+ inputBroker->injectInputEvent(&event);
+ }
+
+ // debounce
+ if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed))
+ return;
+ if (!lgfx::v1::gpio_in(37)) {
+ lastPressed = 37;
+ InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
+ inputBroker->injectInputEvent(&event);
+ } else if (!lgfx::v1::gpio_in(36)) {
+ lastPressed = 36;
+ InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
+ inputBroker->injectInputEvent(&event);
+ } else if (!lgfx::v1::gpio_in(38)) {
+ lastPressed = 38;
+ InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
+ inputBroker->injectInputEvent(&event);
+ } else if (!lgfx::v1::gpio_in(39)) {
+ lastPressed = 39;
+ InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
+ inputBroker->injectInputEvent(&event);
+ } else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) {
+ lastPressed = SDL_SCANCODE_KP_ENTER;
+ InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
+ inputBroker->injectInputEvent(&event);
+ } else {
+ lastPressed = 0;
+ }
+ }
+#endif
+}
+
// Send a command to the display (low level function)
void TFTDisplay::sendCommand(uint8_t com)
{
@@ -1184,9 +1262,9 @@ bool TFTDisplay::connect()
attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2)
tft->setRotation(1); // T-Deck has the TFT in landscape
-#elif defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR)
+#elif defined(T_WATCH_S3)
tft->setRotation(2); // T-Watch S3 left-handed orientation
-#elif ARCH_PORTDUINO
+#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR)
tft->setRotation(0); // use config.yaml to set rotation
#else
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h
index 38cd53ebb..60adfdf7c 100644
--- a/src/graphics/TFTDisplay.h
+++ b/src/graphics/TFTDisplay.h
@@ -23,6 +23,7 @@ class TFTDisplay : public OLEDDisplay
// Write the buffer to the display memory
virtual void display() override { display(false); };
virtual void display(bool fromBlank);
+ void sdlLoop();
// Turn the display upside down
virtual void flipScreenVertically();
diff --git a/src/graphics/VirtualKeyboard.cpp b/src/graphics/VirtualKeyboard.cpp
new file mode 100644
index 000000000..84d5551cb
--- /dev/null
+++ b/src/graphics/VirtualKeyboard.cpp
@@ -0,0 +1,738 @@
+#include "VirtualKeyboard.h"
+#include "configuration.h"
+#include "graphics/Screen.h"
+#include "graphics/ScreenFonts.h"
+#include "graphics/SharedUIDisplay.h"
+#include "main.h"
+#include
+#include
+
+namespace graphics
+{
+
+VirtualKeyboard::VirtualKeyboard() : cursorRow(0), cursorCol(0), lastActivityTime(millis())
+{
+ initializeKeyboard();
+ // Set cursor to H(2, 5)
+ cursorRow = 2;
+ cursorCol = 5;
+}
+
+VirtualKeyboard::~VirtualKeyboard() {}
+
+void VirtualKeyboard::initializeKeyboard()
+{
+ // New 4 row, 11 column keyboard layout:
+ static const char LAYOUT[KEYBOARD_ROWS][KEYBOARD_COLS] = {{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b'},
+ {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n'},
+ {'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', ' '},
+ {'z', 'x', 'c', 'v', 'b', 'n', 'm', '.', ',', '?', '\x1b'}};
+
+ // Derive layout dimensions and assert they match the configured keyboard grid
+ constexpr int LAYOUT_ROWS = (int)(sizeof(LAYOUT) / sizeof(LAYOUT[0]));
+ constexpr int LAYOUT_COLS = (int)(sizeof(LAYOUT[0]) / sizeof(LAYOUT[0][0]));
+ static_assert(LAYOUT_ROWS == KEYBOARD_ROWS, "LAYOUT rows must equal KEYBOARD_ROWS");
+ static_assert(LAYOUT_COLS == KEYBOARD_COLS, "LAYOUT cols must equal KEYBOARD_COLS");
+
+ // Initialize all keys to empty first
+ for (int row = 0; row < LAYOUT_ROWS; row++) {
+ for (int col = 0; col < LAYOUT_COLS; col++) {
+ keyboard[row][col] = {0, VK_CHAR, 0, 0, 0, 0};
+ }
+ }
+
+ // Fill keyboard from the 2D layout
+ for (int row = 0; row < LAYOUT_ROWS; row++) {
+ for (int col = 0; col < LAYOUT_COLS; col++) {
+ char ch = LAYOUT[row][col];
+ // No empty slots in the simplified layout
+
+ VirtualKeyType type = VK_CHAR;
+ if (ch == '\b') {
+ type = VK_BACKSPACE;
+ } else if (ch == '\n') {
+ type = VK_ENTER;
+ } else if (ch == '\x1b') { // ESC
+ type = VK_ESC;
+ } else if (ch == ' ') {
+ type = VK_SPACE;
+ }
+
+ // Make action keys wider to fit text while keeping the last column aligned
+ uint8_t width = (type == VK_BACKSPACE || type == VK_ENTER || type == VK_SPACE) ? (KEY_WIDTH * 3) : KEY_WIDTH;
+ keyboard[row][col] = {ch, type, (uint8_t)(col * KEY_WIDTH), (uint8_t)(row * KEY_HEIGHT), width, KEY_HEIGHT};
+ }
+ }
+}
+
+void VirtualKeyboard::draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY)
+{
+ // Repeat ticking is driven by NotificationRenderer once per frame
+ // Base styles
+ display->setColor(WHITE);
+ display->setFont(FONT_SMALL);
+
+ // Screen geometry
+ const int screenW = display->getWidth();
+ const int screenH = display->getHeight();
+
+ // Decide wide-screen mode: if there is comfortable width, allow taller keys and reserve fixed width for last column labels
+ // Heuristic: if screen width >= 200px (e.g., 240x135), treat as wide
+ const bool isWide = screenW >= 200;
+
+ // Determine last-column label max width
+ display->setFont(FONT_SMALL);
+ const int wENTER = display->getStringWidth("ENTER");
+ int lastColLabelW = wENTER; // ENTER is usually the widest
+ // Smaller padding on very small screens to avoid excessive whitespace
+ const int lastColPad = (screenW <= 128 ? 2 : 6);
+ const int reservedLastColW = lastColLabelW + lastColPad; // reserved width for last column keys
+
+ // Always reserve width for the rightmost text column to avoid overlap on small screens
+ int cellW = 0;
+ int leftoverW = 0;
+ {
+ const int leftCols = KEYBOARD_COLS - 1; // 10 input characters
+ int usableW = screenW - reservedLastColW;
+ if (usableW < leftCols) {
+ // Guard: ensure at least 1px per left cell if labels are extremely wide (unlikely)
+ usableW = leftCols;
+ }
+ cellW = usableW / leftCols;
+ leftoverW = usableW - cellW * leftCols; // distribute extra pixels over left columns (left to right)
+ }
+
+ // Dynamic key geometry
+ int cellH = KEY_HEIGHT;
+ int keyboardStartY = 0;
+ if (screenH <= 64) {
+ const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL - 2);
+ const int gapBelowHeader = 0;
+ const int singleLineBoxHeight = FONT_HEIGHT_SMALL;
+ const int gapAboveKeyboard = 0;
+ keyboardStartY = offsetY + headerHeight + gapBelowHeader + singleLineBoxHeight + gapAboveKeyboard;
+ if (keyboardStartY < 0)
+ keyboardStartY = 0;
+ if (keyboardStartY > screenH)
+ keyboardStartY = screenH;
+ int keyboardHeight = screenH - keyboardStartY;
+ cellH = std::max(1, keyboardHeight / KEYBOARD_ROWS);
+ } else if (isWide) {
+ // For wide screens (e.g., T114 240x135), prefer square keys: height equals left-column key width.
+ cellH = std::max((int)KEY_HEIGHT, cellW);
+
+ // Guarantee at least 2 lines of input are visible by reducing cell height minimally if needed.
+ // Replicate the spacing used in drawInputArea(): headerGap=1, box-to-header gap=1, gap above keyboard=1
+ display->setFont(FONT_SMALL);
+ const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL + 1);
+ const int headerToBoxGap = 1;
+ const int gapAboveKb = 1;
+ const int minBoxHeightForTwoLines = 2 * FONT_HEIGHT_SMALL + 2; // inner 1px top/bottom
+ int maxKeyboardHeight = screenH - (offsetY + headerHeight + headerToBoxGap + minBoxHeightForTwoLines + gapAboveKb);
+ int maxCellHAllowed = maxKeyboardHeight / KEYBOARD_ROWS;
+ if (maxCellHAllowed < (int)KEY_HEIGHT)
+ maxCellHAllowed = KEY_HEIGHT;
+ if (maxCellHAllowed > 0 && cellH > maxCellHAllowed) {
+ cellH = maxCellHAllowed;
+ }
+ // Keyboard placement from bottom for wide screens
+ int keyboardHeight = KEYBOARD_ROWS * cellH;
+ keyboardStartY = screenH - keyboardHeight;
+ if (keyboardStartY < 0)
+ keyboardStartY = 0;
+ } else {
+ // Default (non-wide, non-64px) behavior: use key height heuristic and place at bottom
+ cellH = KEY_HEIGHT;
+ int keyboardHeight = KEYBOARD_ROWS * cellH;
+ keyboardStartY = screenH - keyboardHeight;
+ if (keyboardStartY < 0)
+ keyboardStartY = 0;
+ }
+
+ // Draw input area above keyboard
+ drawInputArea(display, offsetX, offsetY, keyboardStartY);
+
+ // Precompute per-column x and width with leftover distributed over left columns for even spacing
+ int colX[KEYBOARD_COLS];
+ int colW[KEYBOARD_COLS];
+ int runningX = offsetX;
+ for (int col = 0; col < KEYBOARD_COLS - 1; ++col) {
+ int wcol = cellW + (col < leftoverW ? 1 : 0);
+ colX[col] = runningX;
+ colW[col] = wcol;
+ runningX += wcol;
+ }
+ // Last column
+ colX[KEYBOARD_COLS - 1] = runningX;
+ colW[KEYBOARD_COLS - 1] = reservedLastColW;
+
+ // Draw keyboard grid
+ for (int row = 0; row < KEYBOARD_ROWS; row++) {
+ for (int col = 0; col < KEYBOARD_COLS; col++) {
+ const VirtualKey &k = keyboard[row][col];
+ if (k.character != 0 || k.type != VK_CHAR) {
+ const bool isLastCol = (col == KEYBOARD_COLS - 1);
+ int x = colX[col];
+ int w = colW[col];
+ int y = offsetY + keyboardStartY + row * cellH;
+ int h = cellH;
+ bool selected = (row == cursorRow && col == cursorCol);
+ drawKey(display, k, selected, x, y, (uint8_t)w, (uint8_t)h, isLastCol);
+ }
+ }
+ }
+}
+
+void VirtualKeyboard::drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY)
+{
+ display->setColor(WHITE);
+
+ const int screenWidth = display->getWidth();
+ const int screenHeight = display->getHeight();
+ // Use the standard small font metrics for input box sizing (restore original size)
+ const int inputLineH = FONT_HEIGHT_SMALL;
+
+ // Header uses the standard small (which may be larger on big screens)
+ display->setFont(FONT_SMALL);
+ int headerHeight = 0;
+ if (!headerText.empty()) {
+ // Draw header and reserve exact font height (plus a tighter gap) to maximize input area
+ display->drawString(offsetX + 2, offsetY, headerText.c_str());
+ if (screenHeight <= 64) {
+ headerHeight = FONT_HEIGHT_SMALL - 2; // 11px
+ } else {
+ headerHeight = FONT_HEIGHT_SMALL; // no extra padding baked in
+ }
+ }
+
+ const int boxX = offsetX;
+ const int boxWidth = screenWidth;
+ int boxY;
+ int boxHeight;
+ if (screenHeight <= 64) {
+ const int gapBelowHeader = 0;
+ const int fixedBoxHeight = inputLineH;
+ const int gapAboveKeyboard = 0;
+ boxY = offsetY + headerHeight + gapBelowHeader;
+ boxHeight = fixedBoxHeight;
+ if (boxY + boxHeight + gapAboveKeyboard > keyboardStartY) {
+ int over = boxY + boxHeight + gapAboveKeyboard - keyboardStartY;
+ boxHeight = std::max(1, fixedBoxHeight - over);
+ }
+ } else {
+ const int gapBelowHeader = 1;
+ int gapAboveKeyboard = 1;
+ int tmpBoxY = offsetY + headerHeight + gapBelowHeader;
+ const int minBoxHeight = inputLineH + 2;
+ int availableH = keyboardStartY - tmpBoxY - gapAboveKeyboard;
+ if (availableH < minBoxHeight)
+ availableH = minBoxHeight;
+ boxY = tmpBoxY;
+ boxHeight = availableH;
+ }
+
+ // Draw box border
+ display->drawRect(boxX, boxY, boxWidth, boxHeight);
+
+ display->setFont(FONT_SMALL);
+
+ // Text rendering: multi-line if space allows (>= 2 lines), else single-line with leading ellipsis
+ const int textX = boxX + 2;
+ const int maxTextWidth = boxWidth - 4;
+ const int maxLines = (boxHeight - 2) / inputLineH;
+ if (maxLines >= 2) {
+ // Inner bounds for caret clamping
+ const int innerLeft = boxX + 1;
+ const int innerRight = boxX + boxWidth - 2;
+ const int innerTop = boxY + 1;
+ const int innerBottom = boxY + boxHeight - 2;
+
+ // Wrap text greedily into lines that fit maxTextWidth
+ std::vector lines;
+ {
+ std::string remaining = inputText;
+ while (!remaining.empty()) {
+ int bestLen = 0;
+ for (int len = 1; len <= (int)remaining.size(); ++len) {
+ int w = display->getStringWidth(remaining.substr(0, len).c_str());
+ if (w <= maxTextWidth)
+ bestLen = len;
+ else
+ break;
+ }
+ if (bestLen == 0) {
+ // At least show one character to make progress
+ bestLen = 1;
+ }
+ lines.emplace_back(remaining.substr(0, bestLen));
+ remaining.erase(0, bestLen);
+ }
+ }
+
+ const bool scrolledUp = ((int)lines.size() > maxLines);
+ int caretX = textX;
+ int caretY = innerTop;
+
+ // Leave a small top gap to render '...' without replacing the first line
+ const int topInset = 2;
+ const int lineStep = std::max(1, inputLineH - 1); // slightly tighter than font height
+ int lineY = innerTop + topInset;
+
+ if (scrolledUp) {
+ // Draw three small dots centered horizontally, vertically at the midpoint of the gap
+ // between the inner top and the first line's top baseline. This avoids using a tall glyph.
+ const int firstLineTop = lineY; // baseline top for the first visible line
+ const int gapMidY = innerTop + (firstLineTop - innerTop) / 2 + 1; // shift down 1px as requested
+ const int centerX = boxX + boxWidth / 2;
+ const int dotSpacing = 3; // px between dots
+ const int dotSize = 1; // small square dot
+ display->fillRect(centerX - dotSpacing, gapMidY, dotSize, dotSize);
+ display->fillRect(centerX, gapMidY, dotSize, dotSize);
+ display->fillRect(centerX + dotSpacing, gapMidY, dotSize, dotSize);
+ }
+
+ // How many lines fit with our top inset and tighter step
+ const int linesCapacity = std::max(1, (innerBottom - lineY + 1) / lineStep);
+ const int linesToShow = std::min((int)lines.size(), linesCapacity);
+ const int startIndex = scrolledUp ? ((int)lines.size() - linesToShow) : 0;
+
+ for (int i = 0; i < linesToShow; ++i) {
+ const std::string &chunk = lines[startIndex + i];
+ display->drawString(textX, lineY, chunk.c_str());
+ caretX = textX + display->getStringWidth(chunk.c_str());
+ caretY = lineY;
+ lineY += lineStep;
+ }
+
+ // Draw caret at end of the last visible line
+ int caretPadY = 2;
+ if (boxHeight >= inputLineH + 4)
+ caretPadY = 3;
+ int cursorTop = caretY + caretPadY;
+ // Use lineStep so caret height matches the row spacing
+ int cursorH = lineStep - caretPadY * 2;
+ if (cursorH < 1)
+ cursorH = 1;
+ // Clamp vertical bounds to stay inside the inner rect
+ if (cursorTop < innerTop)
+ cursorTop = innerTop;
+ if (cursorTop + cursorH - 1 > innerBottom)
+ cursorH = innerBottom - cursorTop + 1;
+ if (cursorH < 1)
+ cursorH = 1;
+ // Only draw if cursor is inside inner bounds
+ if (caretX >= innerLeft && caretX <= innerRight) {
+ display->drawVerticalLine(caretX, cursorTop, cursorH);
+ }
+ } else {
+ std::string displayText = inputText;
+ int textW = display->getStringWidth(displayText.c_str());
+ std::string scrolled = displayText;
+ if (textW > maxTextWidth) {
+ // Trim from the left until it fits
+ while (textW > maxTextWidth && !scrolled.empty()) {
+ scrolled.erase(0, 1);
+ textW = display->getStringWidth(scrolled.c_str());
+ }
+ // Add leading ellipsis and ensure it still fits
+ if (scrolled != displayText) {
+ scrolled = "..." + scrolled;
+ textW = display->getStringWidth(scrolled.c_str());
+ // If adding ellipsis causes overflow, trim more after the ellipsis
+ while (textW > maxTextWidth && scrolled.size() > 3) {
+ scrolled.erase(3, 1); // remove chars after the ellipsis
+ textW = display->getStringWidth(scrolled.c_str());
+ }
+ }
+ } else {
+ // Keep textW in sync with what we draw
+ textW = display->getStringWidth(scrolled.c_str());
+ }
+
+ int textY;
+ if (screenHeight <= 64) {
+ textY = boxY + (boxHeight - inputLineH) / 2;
+ } else {
+ const int innerLeft = boxX + 1;
+ const int innerRight = boxX + boxWidth - 2;
+ const int innerTop = boxY + 1;
+ const int innerBottom = boxY + boxHeight - 2;
+
+ // Center text vertically within inner box for single-line, then clamp so it never overlaps borders
+ int innerH = innerBottom - innerTop + 1;
+ textY = innerTop + std::max(0, (innerH - inputLineH) / 2);
+ // Clamp fully inside the inner rect
+ if (textY < innerTop)
+ textY = innerTop;
+ int maxTop = innerBottom - inputLineH + 1;
+ if (textY > maxTop)
+ textY = maxTop;
+ }
+
+ if (!scrolled.empty()) {
+ display->drawString(textX, textY, scrolled.c_str());
+ }
+
+ int cursorX = textX + textW;
+ if (screenHeight > 64) {
+ const int innerRight = boxX + boxWidth - 2;
+ if (cursorX > innerRight)
+ cursorX = innerRight;
+ }
+
+ int cursorTop, cursorH;
+ if (screenHeight <= 64) {
+ cursorH = 10;
+ cursorTop = boxY + (boxHeight - cursorH) / 2;
+ } else {
+ const int innerLeft = boxX + 1;
+ const int innerRight = boxX + boxWidth - 2;
+ const int innerTop = boxY + 1;
+ const int innerBottom = boxY + boxHeight - 2;
+
+ cursorTop = boxY + 2;
+ cursorH = boxHeight - 4;
+ if (cursorH < 1)
+ cursorH = 1;
+ if (cursorTop < innerTop)
+ cursorTop = innerTop;
+ if (cursorTop + cursorH - 1 > innerBottom)
+ cursorH = innerBottom - cursorTop + 1;
+ if (cursorH < 1)
+ cursorH = 1;
+
+ if (cursorX < innerLeft || cursorX > innerRight)
+ return;
+ }
+
+ display->drawVerticalLine(cursorX, cursorTop, cursorH);
+ }
+}
+
+void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t width,
+ uint8_t height, bool isLastCol)
+{
+ // Draw key content
+ display->setFont(FONT_SMALL);
+ const int fontH = FONT_HEIGHT_SMALL;
+ // Build label and metrics first
+ std::string keyText;
+ if (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC) {
+ // Keep literal text labels for the action keys on the rightmost column
+ keyText = (key.type == VK_BACKSPACE) ? "BACK"
+ : (key.type == VK_ENTER) ? "ENTER"
+ : (key.type == VK_SPACE) ? "SPACE"
+ : (key.type == VK_ESC) ? "ESC"
+ : "";
+ } else {
+ char c = getCharForKey(key, false);
+ if (c >= 'a' && c <= 'z') {
+ c = c - 'a' + 'A';
+ }
+ keyText = (key.character == ' ' || key.character == '_') ? "_" : std::string(1, c);
+ }
+
+ int textWidth = display->getStringWidth(keyText.c_str());
+ // Label alignment
+ // - Rightmost action column: right-align text with a small right padding (~2px) so it hugs screen edge neatly.
+ // - Other keys: center horizontally; use ceil-style rounding to avoid appearing left-biased on odd widths.
+ int textX;
+ if (isLastCol) {
+ const int rightPad = 1;
+ textX = x + width - textWidth - rightPad;
+ if (textX < x)
+ textX = x; // guard
+ } else {
+ if (display->getHeight() <= 64 && (key.character >= '0' && key.character <= '9')) {
+ textX = x + (width - textWidth + 1) / 2;
+ } else {
+ textX = x + (width - textWidth) / 2;
+ }
+ }
+ int contentTop = y;
+ int contentH = height;
+ if (selected) {
+ display->setColor(WHITE);
+ bool isAction = (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC);
+
+ if (display->getHeight() <= 64 && !isAction) {
+ display->fillRect(x, y, width, height);
+ } else if (isAction) {
+ const int padX = 1;
+ const int padY = 2;
+ int hlW = textWidth + padX * 2;
+ int hlX = textX - padX;
+
+ if (hlX < x) {
+ hlW -= (x - hlX);
+ hlX = x;
+ }
+ int maxW = (x + width) - hlX;
+ if (hlW > maxW)
+ hlW = maxW;
+ if (hlW < 1)
+ hlW = 1;
+
+ int hlH = std::min(fontH + padY * 2, (int)height);
+ int hlY = y + (height - hlH) / 2;
+ display->fillRect(hlX, hlY, hlW, hlH);
+ contentTop = hlY;
+ contentH = hlH;
+ } else {
+ display->fillRect(x, y, width, height);
+ }
+ display->setColor(BLACK);
+ } else {
+ display->setColor(WHITE);
+ }
+
+ int centeredTextY;
+ if (display->getHeight() <= 64) {
+ centeredTextY = y + (height - fontH) / 2;
+ } else {
+ centeredTextY = contentTop + (contentH - fontH) / 2;
+ }
+ if (display->getHeight() > 64) {
+ if (centeredTextY < contentTop)
+ centeredTextY = contentTop;
+ if (centeredTextY + fontH > contentTop + contentH)
+ centeredTextY = std::max(contentTop, contentTop + contentH - fontH);
+ }
+
+ if (display->getHeight() <= 64 && keyText.size() == 1) {
+ char ch = keyText[0];
+ if (ch == '.' || ch == ',' || ch == ';') {
+ centeredTextY -= 1;
+ }
+ }
+ display->drawString(textX, centeredTextY, keyText.c_str());
+}
+
+char VirtualKeyboard::getCharForKey(const VirtualKey &key, bool isLongPress)
+{
+ if (key.type != VK_CHAR) {
+ return key.character;
+ }
+
+ char c = key.character;
+
+ // Long-press: only keep letter lowercase->uppercase conversion; remove other symbol mappings
+ if (isLongPress && c >= 'a' && c <= 'z') {
+ c = (char)(c - 'a' + 'A');
+ }
+
+ return c;
+}
+
+void VirtualKeyboard::moveCursorDelta(int dRow, int dCol)
+{
+ resetTimeout();
+ // wrap around rows and cols in the 4x11 grid
+ int r = (int)cursorRow + dRow;
+ int c = (int)cursorCol + dCol;
+ if (r < 0)
+ r = KEYBOARD_ROWS - 1;
+ else if (r >= KEYBOARD_ROWS)
+ r = 0;
+ if (c < 0)
+ c = KEYBOARD_COLS - 1;
+ else if (c >= KEYBOARD_COLS)
+ c = 0;
+ cursorRow = (uint8_t)r;
+ cursorCol = (uint8_t)c;
+}
+
+void VirtualKeyboard::moveCursorUp()
+{
+ moveCursorDelta(-1, 0);
+}
+void VirtualKeyboard::moveCursorDown()
+{
+ moveCursorDelta(1, 0);
+}
+void VirtualKeyboard::moveCursorLeft()
+{
+ resetTimeout();
+
+ if (cursorCol > 0) {
+ cursorCol--;
+ } else {
+ if (cursorRow > 0) {
+ cursorRow--;
+ cursorCol = KEYBOARD_COLS - 1;
+ } else {
+ cursorRow = KEYBOARD_ROWS - 1;
+ cursorCol = KEYBOARD_COLS - 1;
+ }
+ }
+}
+void VirtualKeyboard::moveCursorRight()
+{
+ resetTimeout();
+
+ if (cursorCol < KEYBOARD_COLS - 1) {
+ cursorCol++;
+ } else {
+ if (cursorRow < KEYBOARD_ROWS - 1) {
+ cursorRow++;
+ cursorCol = 0;
+ } else {
+ cursorRow = 0;
+ cursorCol = 0;
+ }
+ }
+}
+
+void VirtualKeyboard::handlePress()
+{
+ resetTimeout(); // Reset timeout on any input activity
+
+ const VirtualKey &key = keyboard[cursorRow][cursorCol];
+
+ // Don't handle press if the key is empty (but allow special keys)
+ if (key.character == 0 && key.type == VK_CHAR) {
+ return;
+ }
+
+ // For character keys, insert lowercase character
+ if (key.type == VK_CHAR) {
+ insertCharacter(getCharForKey(key, false)); // false = lowercase/normal char
+ return;
+ }
+
+ // Handle non-character keys immediately
+ switch (key.type) {
+ case VK_BACKSPACE:
+ deleteCharacter();
+ break;
+ case VK_ENTER:
+ submitText();
+ break;
+ case VK_SPACE:
+ insertCharacter(' ');
+ break;
+ case VK_ESC:
+ if (onTextEntered) {
+ std::function callback = onTextEntered;
+ onTextEntered = nullptr;
+ inputText = "";
+ callback("");
+ }
+ return;
+ default:
+ break;
+ }
+}
+
+void VirtualKeyboard::handleLongPress()
+{
+ resetTimeout(); // Reset timeout on any input activity
+
+ const VirtualKey &key = keyboard[cursorRow][cursorCol];
+
+ // Don't handle press if the key is empty (but allow special keys)
+ if (key.character == 0 && key.type == VK_CHAR) {
+ return;
+ }
+
+ // For character keys, insert uppercase/alternate character
+ if (key.type == VK_CHAR) {
+ insertCharacter(getCharForKey(key, true)); // true = uppercase/alternate char
+ return;
+ }
+
+ switch (key.type) {
+ case VK_BACKSPACE:
+ // One-shot: delete up to 5 characters on long press
+ for (int i = 0; i < 5; ++i) {
+ if (inputText.empty())
+ break;
+ deleteCharacter();
+ }
+ break;
+ case VK_ENTER:
+ submitText();
+ break;
+ case VK_SPACE:
+ insertCharacter(' ');
+ break;
+ case VK_ESC:
+ if (onTextEntered) {
+ onTextEntered("");
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void VirtualKeyboard::insertCharacter(char c)
+{
+ if (inputText.length() < 160) { // Reasonable text length limit
+ inputText += c;
+ }
+}
+
+void VirtualKeyboard::deleteCharacter()
+{
+ if (!inputText.empty()) {
+ inputText.pop_back();
+ }
+}
+
+void VirtualKeyboard::submitText()
+{
+ LOG_INFO("Virtual keyboard: submitting text '%s'", inputText.c_str());
+
+ // Only submit if text is not empty
+ if (!inputText.empty() && onTextEntered) {
+ // Store callback and text to submit before clearing callback
+ std::function callback = onTextEntered;
+ std::string textToSubmit = inputText;
+ onTextEntered = nullptr;
+ // Don't clear inputText here - let the calling module handle cleanup
+ // inputText = ""; // Removed: keep text visible until module cleans up
+ callback(textToSubmit);
+ } else if (inputText.empty()) {
+ // For empty text, just ignore the submission - don't clear callback
+ // This keeps the virtual keyboard responsive for further input
+ LOG_INFO("Virtual keyboard: empty text submitted, ignoring - keyboard remains active");
+ } else {
+ // No callback available
+ if (screen) {
+ screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
+ }
+ }
+}
+
+void VirtualKeyboard::setInputText(const std::string &text)
+{
+ inputText = text;
+}
+
+std::string VirtualKeyboard::getInputText() const
+{
+ return inputText;
+}
+
+void VirtualKeyboard::setHeader(const std::string &header)
+{
+ headerText = header;
+}
+
+void VirtualKeyboard::setCallback(std::function callback)
+{
+ onTextEntered = callback;
+}
+
+void VirtualKeyboard::resetTimeout()
+{
+ lastActivityTime = millis();
+}
+
+bool VirtualKeyboard::isTimedOut() const
+{
+ return (millis() - lastActivityTime) > TIMEOUT_MS;
+}
+
+} // namespace graphics
diff --git a/src/graphics/VirtualKeyboard.h b/src/graphics/VirtualKeyboard.h
new file mode 100644
index 000000000..169163b57
--- /dev/null
+++ b/src/graphics/VirtualKeyboard.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include "configuration.h"
+#include
+#include
+#include
+
+namespace graphics
+{
+
+enum VirtualKeyType { VK_CHAR, VK_BACKSPACE, VK_ENTER, VK_SHIFT, VK_ESC, VK_SPACE };
+
+struct VirtualKey {
+ char character;
+ VirtualKeyType type;
+ uint8_t x;
+ uint8_t y;
+ uint8_t width;
+ uint8_t height;
+};
+
+class VirtualKeyboard
+{
+ public:
+ VirtualKeyboard();
+ ~VirtualKeyboard();
+
+ void draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY);
+ void setInputText(const std::string &text);
+ std::string getInputText() const;
+ void setHeader(const std::string &header);
+ void setCallback(std::function callback);
+
+ // Navigation methods for encoder input
+ void moveCursorUp();
+ void moveCursorDown();
+ void moveCursorLeft();
+ void moveCursorRight();
+ void handlePress();
+ void handleLongPress();
+
+ // Timeout management
+ void resetTimeout();
+ bool isTimedOut() const;
+
+ private:
+ static const uint8_t KEYBOARD_ROWS = 4;
+ static const uint8_t KEYBOARD_COLS = 11;
+ static const uint8_t KEY_WIDTH = 9;
+ static const uint8_t KEY_HEIGHT = 9; // Compressed to fit 4 rows on 64px displays
+ static const uint8_t KEYBOARD_START_Y = 26; // Start just below input box bottom
+
+ VirtualKey keyboard[KEYBOARD_ROWS][KEYBOARD_COLS];
+
+ std::string inputText;
+ std::string headerText;
+ std::function onTextEntered;
+
+ uint8_t cursorRow;
+ uint8_t cursorCol;
+
+ // Timeout management for auto-exit
+ uint32_t lastActivityTime;
+ static const uint32_t TIMEOUT_MS = 60000; // 1 minute timeout
+
+ void initializeKeyboard();
+ void drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t w, uint8_t h,
+ bool isLastCol);
+ void drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY);
+
+ // Unified cursor movement helper
+ void moveCursorDelta(int dRow, int dCol);
+
+ char getCharForKey(const VirtualKey &key, bool isLongPress = false);
+ void insertCharacter(char c);
+ void deleteCharacter();
+ void submitText();
+};
+
+} // namespace graphics
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index b7bd068c4..e02948864 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -10,7 +10,10 @@
#include "graphics/Screen.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/draw/UIRenderer.h"
+#include "input/RotaryEncoderInterruptImpl1.h"
+#include "input/UpDownInterruptImpl1.h"
#include "main.h"
+#include "mesh/MeshTypes.h"
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h"
@@ -26,6 +29,27 @@ menuHandler::screenMenus menuHandler::menuQueue = menu_none;
bool test_enabled = false;
uint8_t test_count = 0;
+void menuHandler::OnboardMessage()
+{
+ static const char *optionsArray[] = {"OK", "Got it!"};
+ enum optionsNumbers { OK, got };
+ BannerOverlayOptions bannerOptions;
+#if HAS_TFT
+ bannerOptions.message = "Welcome to Meshtastic!\nSwipe to navigate and\nlong press to select\nor open a menu.";
+#elif defined(BUTTON_PIN)
+ bannerOptions.message = "Welcome to Meshtastic!\nClick to navigate and\nlong press to select\nor open a menu.";
+#else
+ bannerOptions.message = "Welcome to Meshtastic!\nUse the Select button\nto open menus\nand make selections.";
+#endif
+ bannerOptions.optionsArrayPtr = optionsArray;
+ bannerOptions.optionsCount = 2;
+ bannerOptions.bannerCallback = [](int selected) -> void {
+ menuHandler::menuQueue = menuHandler::no_timeout_lora_picker;
+ screen->runNow();
+ };
+ screen->showOverlayBanner(bannerOptions);
+}
+
void menuHandler::LoraRegionPicker(uint32_t duration)
{
static const char *optionsArray[] = {"Back",
@@ -318,7 +342,7 @@ void menuHandler::homeBaseMenu()
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
-#ifdef PIN_EINK_EN
+#if defined(PIN_EINK_EN) || defined(PCA_PIN_EINK_EN)
optionsArray[options] = "Toggle Backlight";
optionsEnumArray[options++] = Backlight;
#else
@@ -342,12 +366,24 @@ void menuHandler::homeBaseMenu()
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Backlight) {
-#ifdef PIN_EINK_EN
- if (digitalRead(PIN_EINK_EN) == HIGH) {
+#if defined(PIN_EINK_EN)
+ if (uiconfig.screen_brightness == 1) {
+ uiconfig.screen_brightness = 0;
digitalWrite(PIN_EINK_EN, LOW);
} else {
+ uiconfig.screen_brightness = 1;
digitalWrite(PIN_EINK_EN, HIGH);
}
+ saveUIConfig();
+#elif defined(PCA_PIN_EINK_EN)
+ if (uiconfig.screen_brightness == 1) {
+ uiconfig.screen_brightness = 0;
+ io.digitalWrite(PCA_PIN_EINK_EN, LOW);
+ } else {
+ uiconfig.screen_brightness = 1;
+ io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
+ }
+ saveUIConfig();
#endif
} else if (selected == Sleep) {
screen->setOn(false);
@@ -1120,6 +1156,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case lora_picker:
LoraRegionPicker();
break;
+ case no_timeout_lora_picker:
+ LoraRegionPicker(0);
+ break;
case TZ_picker:
TZPicker();
break;
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 87a0b055e..b15cf237d 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -10,6 +10,7 @@ class menuHandler
enum screenMenus {
menu_none,
lora_picker,
+ no_timeout_lora_picker,
TZ_picker,
twelve_hour_picker,
clock_face_picker,
@@ -41,6 +42,7 @@ class menuHandler
};
static screenMenus menuQueue;
+ static void OnboardMessage();
static void LoraRegionPicker(uint32_t duration = 30000);
static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function onConfirm);
diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp
index 524f88f9b..117829167 100644
--- a/src/graphics/draw/MessageRenderer.cpp
+++ b/src/graphics/draw/MessageRenderer.cpp
@@ -137,7 +137,11 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
display->drawString(cursorX + 1, fontY, textChunk.c_str());
}
display->drawString(cursorX, fontY, textChunk.c_str());
+#if defined(OLED_UA) || defined(OLED_RU)
+ cursorX += display->getStringWidth(textChunk.c_str(), textChunk.length(), true);
+#else
cursorX += display->getStringWidth(textChunk.c_str());
+#endif
i = nextControl;
continue;
}
@@ -155,7 +159,12 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
display->drawString(cursorX + 1, fontY, remaining.c_str());
}
display->drawString(cursorX, fontY, remaining.c_str());
+#if defined(OLED_UA) || defined(OLED_RU)
+ cursorX += display->getStringWidth(remaining.c_str(), remaining.length(), true);
+#else
cursorX += display->getStringWidth(remaining.c_str());
+#endif
+
break;
}
}
@@ -374,10 +383,16 @@ std::vector generateLines(OLEDDisplay *display, const char *headerS
} else {
word += ch;
std::string test = line + word;
- // Keep these lines for diagnostics
- // LOG_INFO("Char: '%c' (0x%02X)", ch, (unsigned char)ch);
- // LOG_INFO("Current String: %s", test.c_str());
- if (display->getStringWidth(test.c_str()) > textWidth) {
+// Keep these lines for diagnostics
+// LOG_INFO("Char: '%c' (0x%02X)", ch, (unsigned char)ch);
+// LOG_INFO("Current String: %s", test.c_str());
+// Note: there are boolean comparison uint16 (getStringWidth) with int (textWidth), hope textWidth is always positive :)
+#if defined(OLED_UA) || defined(OLED_RU)
+ uint16_t strWidth = display->getStringWidth(test.c_str(), test.length(), true);
+#else
+ uint16_t strWidth = display->getStringWidth(test.c_str());
+#endif
+ if (strWidth > textWidth) {
if (!line.empty())
lines.push_back(line);
line = word;
diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp
index d9cf280ac..221d95075 100644
--- a/src/graphics/draw/NotificationRenderer.cpp
+++ b/src/graphics/draw/NotificationRenderer.cpp
@@ -38,6 +38,8 @@ bool NotificationRenderer::pauseBanner = false;
notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none;
uint32_t NotificationRenderer::numDigits = 0;
uint32_t NotificationRenderer::currentNumber = 0;
+VirtualKeyboard *NotificationRenderer::virtualKeyboard = nullptr;
+std::function NotificationRenderer::textInputCallback = nullptr;
uint32_t pow_of_10(uint32_t n)
{
@@ -89,14 +91,33 @@ void NotificationRenderer::resetBanner()
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
{
- if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
- resetBanner();
- if (!isOverlayBannerShowing() || pauseBanner)
+ // Handle text_input notifications first - they have their own timeout/banner logic
+ if (current_notification_type == notificationTypeEnum::text_input) {
+ // Check for timeout and reset if needed for text input
+ if (millis() > alertBannerUntil && alertBannerUntil > 0) {
+ resetBanner();
+ return;
+ }
+ drawTextInput(display, state);
return;
+ }
+
+ if (millis() > alertBannerUntil && alertBannerUntil > 0) {
+ resetBanner();
+ }
+
+ // Exit if no banner is showing or banner is paused
+ if (!isOverlayBannerShowing() || pauseBanner) {
+ return;
+ }
+
switch (current_notification_type) {
case notificationTypeEnum::none:
// Do nothing - no notification to display
break;
+ case notificationTypeEnum::text_input:
+ // Already handled above with dedicated logic (early return). Keep a case here to satisfy -Wswitch.
+ break;
case notificationTypeEnum::text_banner:
case notificationTypeEnum::selection_picker:
drawAlertBannerOverlay(display, state);
@@ -383,7 +404,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
uint8_t firstOptionToShow = 0;
if (alertBannerOptions > 0) {
- if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) {
+ if (visibleTotalLines - lineCount == 1) {
+ firstOptionToShow = curSelected;
+ } else if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) {
if (curSelected > alertBannerOptions - visibleTotalLines + lineCount)
firstOptionToShow = alertBannerOptions - visibleTotalLines + lineCount;
else
@@ -392,6 +415,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
firstOptionToShow = 0;
}
}
+ // Useful log line for troubleshooting:
+ /* LOG_WARN("alertBannerOptions: %u, curSelected: %u, visibleTotalLines: %u, lineCount: %u, firstOptionToShow: %u",
+ alertBannerOptions, curSelected, visibleTotalLines, lineCount, firstOptionToShow); */
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
if (i == curSelected) {
@@ -570,6 +596,90 @@ void NotificationRenderer::drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUi
"Please be patient and do not power off.");
}
+void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state)
+{
+ if (virtualKeyboard) {
+ // Check for timeout and auto-exit if needed
+ if (virtualKeyboard->isTimedOut()) {
+ LOG_INFO("Virtual keyboard timeout - auto-exiting");
+ // Cancel virtual keyboard - call callback with empty string to indicate timeout
+ auto callback = textInputCallback; // Store callback before clearing
+
+ // Clean up first to prevent re-entry
+ delete virtualKeyboard;
+ virtualKeyboard = nullptr;
+ textInputCallback = nullptr;
+ resetBanner();
+
+ // Call callback after cleanup
+ if (callback) {
+ callback("");
+ }
+
+ // Restore normal overlays
+ if (screen) {
+ screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
+ }
+ return;
+ }
+
+ // Handle input events for virtual keyboard navigation
+ if (inEvent.inputEvent != INPUT_BROKER_NONE) {
+ if (inEvent.inputEvent == INPUT_BROKER_UP) {
+ virtualKeyboard->moveCursorUp();
+ } else if (inEvent.inputEvent == INPUT_BROKER_DOWN) {
+ 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_ALT_PRESS) {
+ // Long press UP = move left
+ virtualKeyboard->moveCursorLeft();
+ } else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
+ // Long press DOWN = move right
+ 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) {
+ // Cancel virtual keyboard - call callback with empty string
+ auto callback = textInputCallback; // Store callback before clearing
+
+ // Clean up first to prevent re-entry
+ delete virtualKeyboard;
+ virtualKeyboard = nullptr;
+ textInputCallback = nullptr;
+ resetBanner();
+
+ // Call callback after cleanup
+ if (callback) {
+ callback("");
+ }
+
+ // Restore normal overlays
+ if (screen) {
+ screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
+ }
+ return;
+ }
+
+ // Reset input event after processing
+ inEvent.inputEvent = INPUT_BROKER_NONE;
+ }
+
+ // Clear the display and draw virtual keyboard
+ display->setColor(BLACK);
+ display->fillRect(0, 0, display->getWidth(), display->getHeight());
+ display->setColor(WHITE);
+ virtualKeyboard->draw(display, 0, 0);
+ } else {
+ // If virtualKeyboard is null, reset the banner to avoid getting stuck
+ resetBanner();
+ }
+}
+
bool NotificationRenderer::isOverlayBannerShowing()
{
return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil);
diff --git a/src/graphics/draw/NotificationRenderer.h b/src/graphics/draw/NotificationRenderer.h
index 9c30b329c..edb069513 100644
--- a/src/graphics/draw/NotificationRenderer.h
+++ b/src/graphics/draw/NotificationRenderer.h
@@ -3,6 +3,9 @@
#include "OLEDDisplay.h"
#include "OLEDDisplayUi.h"
#include "graphics/Screen.h"
+#include "graphics/VirtualKeyboard.h"
+#include
+#include
#define MAX_LINES 5
namespace graphics
@@ -22,6 +25,8 @@ class NotificationRenderer
static std::function alertBannerCallback;
static uint32_t numDigits;
static uint32_t currentNumber;
+ static VirtualKeyboard *virtualKeyboard;
+ static std::function textInputCallback;
static bool pauseBanner;
@@ -30,6 +35,7 @@ class NotificationRenderer
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state);
+ static void drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1],
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
diff --git a/src/graphics/fonts/EinkDisplayFonts.cpp b/src/graphics/fonts/EinkDisplayFonts.cpp
index cfe2c931f..497b3b389 100644
--- a/src/graphics/fonts/EinkDisplayFonts.cpp
+++ b/src/graphics/fonts/EinkDisplayFonts.cpp
@@ -1,3 +1,5 @@
+#ifdef USE_EINK
+
#include "EinkDisplayFonts.h"
// Created by https://oleddisplay.squix.ch/ Consider a donation
@@ -1182,3 +1184,5 @@ const uint8_t Monospaced_plain_30[] PROGMEM = {
0xF0, 0xFF, 0x01, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0xE0, 0x00, 0xFE, 0x03, 0x00, 0xE0, 0x80, 0x7F,
0x00, 0x00, 0xE0, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x10 // 255
};
+
+#endif // USE_EINK
diff --git a/src/graphics/fonts/EinkDisplayFonts.h b/src/graphics/fonts/EinkDisplayFonts.h
index 342525a19..a4a44ba47 100644
--- a/src/graphics/fonts/EinkDisplayFonts.h
+++ b/src/graphics/fonts/EinkDisplayFonts.h
@@ -1,6 +1,8 @@
#ifndef EINKDISPLAYFONTS_h
#define EINKDISPLAYFONTS_h
+#ifdef USE_EINK
+
#ifdef ARDUINO
#include
#elif __MBED__
@@ -11,4 +13,7 @@
* Monospaced Plain 30
*/
extern const uint8_t Monospaced_plain_30[] PROGMEM;
+
+#endif // USE_EINK
+
#endif
diff --git a/src/graphics/fonts/OLEDDisplayFontsCS.cpp b/src/graphics/fonts/OLEDDisplayFontsCS.cpp
index 67208b4d9..c8045285e 100644
--- a/src/graphics/fonts/OLEDDisplayFontsCS.cpp
+++ b/src/graphics/fonts/OLEDDisplayFontsCS.cpp
@@ -1,3 +1,5 @@
+#ifdef OLED_CS
+
#include "OLEDDisplayFontsCS.h"
// Font generated or edited with the glyphEditor
@@ -1860,4 +1862,6 @@ const uint8_t ArialMT_Plain_24_CS[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00,
0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
0x06, // 255
-};
\ No newline at end of file
+};
+
+#endif // OLED_CS
\ No newline at end of file
diff --git a/src/graphics/fonts/OLEDDisplayFontsPL.cpp b/src/graphics/fonts/OLEDDisplayFontsPL.cpp
index 0767e24e7..00f0913fe 100644
--- a/src/graphics/fonts/OLEDDisplayFontsPL.cpp
+++ b/src/graphics/fonts/OLEDDisplayFontsPL.cpp
@@ -1,4 +1,5 @@
// trunk-ignore-all(clang-format): Preserve long lines
+#ifdef OLED_PL
#include "OLEDDisplayFontsPL.h"
const uint8_t ArialMT_Plain_10_PL[] PROGMEM = {
@@ -1310,4 +1311,6 @@ const uint8_t ArialMT_Plain_24_PL[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x80, 0x00, 0xFE, 0x03, 0xE0, 0x00, 0xFC, 0x00, 0x60, 0xC0, 0x1F, 0x00, 0x20, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 253
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 254
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 255
-};
\ No newline at end of file
+};
+
+#endif // OLED_PL
\ No newline at end of file
diff --git a/src/graphics/fonts/OLEDDisplayFontsRU.cpp b/src/graphics/fonts/OLEDDisplayFontsRU.cpp
index fa055d8b5..3a1159511 100644
--- a/src/graphics/fonts/OLEDDisplayFontsRU.cpp
+++ b/src/graphics/fonts/OLEDDisplayFontsRU.cpp
@@ -1,3 +1,5 @@
+#ifdef OLED_RU
+
#include "OLEDDisplayFontsRU.h"
// Font generated or edited with the glyphEditor
@@ -423,4 +425,1345 @@ const uint8_t ArialMT_Plain_10_RU[] PROGMEM = {
0x00, 0x00, 0x40, 0x01, 0x20, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, // 253
0x00, 0x00, 0xE0, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, 0xC0, 0x01, // 254
0x00, 0x00, 0x40, 0x02, 0xA0, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0xE0, 0x03, // 255
-};
\ No newline at end of file
+};
+
+// Font generated or edited with the glyphEditor (@mrekin)
+const uint8_t ArialMT_Plain_16_RU[] PROGMEM = {
+ 0x10, // Width: 16
+ 0x13, // Height: 19
+ 0x20, // First char: 32
+ 0xE0, // Number of chars: 224
+ // Jump Table:
+ 0xFF, 0xFF, 0x00, 0x04, // 32
+ 0x00, 0x00, 0x08, 0x05, // 33
+ 0x00, 0x08, 0x0D, 0x06, // 34
+ 0x00, 0x15, 0x1A, 0x09, // 35
+ 0x00, 0x2F, 0x17, 0x09, // 36
+ 0x00, 0x46, 0x26, 0x0E, // 37
+ 0x00, 0x6C, 0x1D, 0x0B, // 38
+ 0x00, 0x89, 0x04, 0x03, // 39
+ 0x00, 0x8D, 0x0C, 0x05, // 40
+ 0x00, 0x99, 0x0B, 0x05, // 41
+ 0x00, 0xA4, 0x0D, 0x06, // 42
+ 0x00, 0xB1, 0x17, 0x09, // 43
+ 0x00, 0xC8, 0x09, 0x04, // 44
+ 0x00, 0xD1, 0x0B, 0x05, // 45
+ 0x00, 0xDC, 0x08, 0x04, // 46
+ 0x00, 0xE4, 0x0A, 0x04, // 47
+ 0x00, 0xEE, 0x17, 0x09, // 48
+ 0x01, 0x05, 0x11, 0x09, // 49
+ 0x01, 0x16, 0x17, 0x09, // 50
+ 0x01, 0x2D, 0x17, 0x09, // 51
+ 0x01, 0x44, 0x17, 0x09, // 52
+ 0x01, 0x5B, 0x17, 0x09, // 53
+ 0x01, 0x72, 0x17, 0x09, // 54
+ 0x01, 0x89, 0x16, 0x09, // 55
+ 0x01, 0x9F, 0x17, 0x09, // 56
+ 0x01, 0xB6, 0x17, 0x09, // 57
+ 0x01, 0xCD, 0x05, 0x04, // 58
+ 0x01, 0xD2, 0x06, 0x04, // 59
+ 0x01, 0xD8, 0x17, 0x09, // 60
+ 0x01, 0xEF, 0x17, 0x09, // 61
+ 0x02, 0x06, 0x17, 0x09, // 62
+ 0x02, 0x1D, 0x16, 0x09, // 63
+ 0x02, 0x33, 0x2F, 0x10, // 64
+ 0x02, 0x62, 0x1D, 0x0B, // 65
+ 0x02, 0x7F, 0x1D, 0x0B, // 66
+ 0x02, 0x9C, 0x20, 0x0C, // 67
+ 0x02, 0xBC, 0x20, 0x0C, // 68
+ 0x02, 0xDC, 0x1D, 0x0B, // 69
+ 0x02, 0xF9, 0x19, 0x0A, // 70
+ 0x03, 0x12, 0x20, 0x0C, // 71
+ 0x03, 0x32, 0x1D, 0x0B, // 72
+ 0x03, 0x4F, 0x05, 0x03, // 73
+ 0x03, 0x54, 0x14, 0x08, // 74
+ 0x03, 0x68, 0x1D, 0x0B, // 75
+ 0x03, 0x85, 0x17, 0x09, // 76
+ 0x03, 0x9C, 0x23, 0x0D, // 77
+ 0x03, 0xBF, 0x1D, 0x0B, // 78
+ 0x03, 0xDC, 0x20, 0x0C, // 79
+ 0x03, 0xFC, 0x1C, 0x0B, // 80
+ 0x04, 0x18, 0x20, 0x0C, // 81
+ 0x04, 0x38, 0x1D, 0x0B, // 82
+ 0x04, 0x55, 0x1D, 0x0B, // 83
+ 0x04, 0x72, 0x19, 0x09, // 84
+ 0x04, 0x8B, 0x1D, 0x0B, // 85
+ 0x04, 0xA8, 0x1C, 0x0B, // 86
+ 0x04, 0xC4, 0x2B, 0x0F, // 87
+ 0x04, 0xEF, 0x20, 0x0B, // 88
+ 0x05, 0x0F, 0x19, 0x09, // 89
+ 0x05, 0x28, 0x1A, 0x09, // 90
+ 0x05, 0x42, 0x0C, 0x04, // 91
+ 0x05, 0x4E, 0x0B, 0x04, // 92
+ 0x05, 0x59, 0x09, 0x04, // 93
+ 0x05, 0x62, 0x14, 0x07, // 94
+ 0x05, 0x76, 0x1B, 0x09, // 95
+ 0x05, 0x91, 0x07, 0x05, // 96
+ 0x05, 0x98, 0x17, 0x09, // 97
+ 0x05, 0xAF, 0x17, 0x09, // 98
+ 0x05, 0xC6, 0x14, 0x08, // 99
+ 0x05, 0xDA, 0x17, 0x09, // 100
+ 0x05, 0xF1, 0x17, 0x09, // 101
+ 0x06, 0x08, 0x0A, 0x04, // 102
+ 0x06, 0x12, 0x17, 0x09, // 103
+ 0x06, 0x29, 0x14, 0x08, // 104
+ 0x06, 0x3D, 0x05, 0x04, // 105
+ 0x06, 0x42, 0x06, 0x03, // 106
+ 0x06, 0x48, 0x17, 0x08, // 107
+ 0x06, 0x5F, 0x05, 0x03, // 108
+ 0x06, 0x64, 0x23, 0x0D, // 109
+ 0x06, 0x87, 0x14, 0x08, // 110
+ 0x06, 0x9B, 0x17, 0x09, // 111
+ 0x06, 0xB2, 0x17, 0x09, // 112
+ 0x06, 0xC9, 0x18, 0x09, // 113
+ 0x06, 0xE1, 0x0D, 0x05, // 114
+ 0x06, 0xEE, 0x14, 0x08, // 115
+ 0x07, 0x02, 0x0B, 0x04, // 116
+ 0x07, 0x0D, 0x14, 0x08, // 117
+ 0x07, 0x21, 0x13, 0x07, // 118
+ 0x07, 0x34, 0x1F, 0x0B, // 119
+ 0x07, 0x53, 0x14, 0x07, // 120
+ 0x07, 0x67, 0x13, 0x07, // 121
+ 0x07, 0x7A, 0x14, 0x07, // 122
+ 0x07, 0x8E, 0x0F, 0x05, // 123
+ 0x07, 0x9D, 0x06, 0x03, // 124
+ 0x07, 0xA3, 0x0E, 0x05, // 125
+ 0x07, 0xB1, 0x17, 0x09, // 126
+ 0xFF, 0xFF, 0x00, 0x10, // 127
+ 0xFF, 0xFF, 0x00, 0x10, // 128
+ 0xFF, 0xFF, 0x00, 0x10, // 129
+ 0xFF, 0xFF, 0x00, 0x10, // 130
+ 0xFF, 0xFF, 0x00, 0x10, // 131
+ 0xFF, 0xFF, 0x00, 0x10, // 132
+ 0xFF, 0xFF, 0x00, 0x10, // 133
+ 0xFF, 0xFF, 0x00, 0x10, // 134
+ 0xFF, 0xFF, 0x00, 0x10, // 135
+ 0xFF, 0xFF, 0x00, 0x10, // 136
+ 0xFF, 0xFF, 0x00, 0x10, // 137
+ 0xFF, 0xFF, 0x00, 0x10, // 138
+ 0xFF, 0xFF, 0x00, 0x10, // 139
+ 0xFF, 0xFF, 0x00, 0x10, // 140
+ 0xFF, 0xFF, 0x00, 0x10, // 141
+ 0xFF, 0xFF, 0x00, 0x10, // 142
+ 0xFF, 0xFF, 0x00, 0x10, // 143
+ 0xFF, 0xFF, 0x00, 0x10, // 144
+ 0xFF, 0xFF, 0x00, 0x10, // 145
+ 0xFF, 0xFF, 0x00, 0x10, // 146
+ 0xFF, 0xFF, 0x00, 0x10, // 147
+ 0xFF, 0xFF, 0x00, 0x10, // 148
+ 0xFF, 0xFF, 0x00, 0x10, // 149
+ 0xFF, 0xFF, 0x00, 0x10, // 150
+ 0xFF, 0xFF, 0x00, 0x10, // 151
+ 0xFF, 0xFF, 0x00, 0x10, // 152
+ 0xFF, 0xFF, 0x00, 0x10, // 153
+ 0xFF, 0xFF, 0x00, 0x10, // 154
+ 0xFF, 0xFF, 0x00, 0x10, // 155
+ 0xFF, 0xFF, 0x00, 0x10, // 156
+ 0xFF, 0xFF, 0x00, 0x10, // 157
+ 0xFF, 0xFF, 0x00, 0x10, // 158
+ 0xFF, 0xFF, 0x00, 0x10, // 159
+ 0xFF, 0xFF, 0x00, 0x10, // 160
+ 0x07, 0xC8, 0x09, 0x05, // 161
+ 0x07, 0xD1, 0x17, 0x09, // 162
+ 0x07, 0xE8, 0x17, 0x09, // 163
+ 0x07, 0xFF, 0x14, 0x09, // 164
+ 0x08, 0x13, 0x1A, 0x09, // 165
+ 0x08, 0x2D, 0x06, 0x03, // 166
+ 0x08, 0x33, 0x17, 0x09, // 167
+ 0x08, 0x4A, 0x1D, 0x0B, // 168
+ 0x08, 0x67, 0x23, 0x0C, // 169
+ 0x08, 0x8A, 0x0E, 0x05, // 170
+ 0x08, 0x98, 0x14, 0x09, // 171
+ 0x08, 0xAC, 0x17, 0x09, // 172
+ 0x08, 0xC3, 0x0B, 0x05, // 173
+ 0x08, 0xCE, 0x23, 0x0C, // 174
+ 0x08, 0xF1, 0x19, 0x09, // 175
+ 0x09, 0x0A, 0x0D, 0x06, // 176
+ 0x09, 0x17, 0x17, 0x09, // 177
+ 0x09, 0x2E, 0x0E, 0x05, // 178
+ 0x09, 0x3C, 0x0D, 0x05, // 179
+ 0x09, 0x49, 0x0A, 0x05, // 180
+ 0x09, 0x53, 0x17, 0x09, // 181
+ 0x09, 0x6A, 0x19, 0x09, // 182
+ 0x09, 0x83, 0x08, 0x05, // 183
+ 0x09, 0x8B, 0x17, 0x09, // 184
+ 0x09, 0xA2, 0x0B, 0x05, // 185
+ 0x09, 0xAD, 0x0D, 0x05, // 186
+ 0x09, 0xBA, 0x17, 0x09, // 187
+ 0x09, 0xD1, 0x26, 0x0D, // 188
+ 0x09, 0xF7, 0x26, 0x0D, // 189
+ 0x0A, 0x1D, 0x26, 0x0D, // 190
+ 0x0A, 0x43, 0x1B, 0x0C, // 191
+ 0x0A, 0x5E, 0x1D, 0x0B, // 192
+ 0x0A, 0x7B, 0x1A, 0x0B, // 193
+ 0x0A, 0x95, 0x1D, 0x0B, // 194
+ 0x0A, 0xB2, 0x19, 0x09, // 195
+ 0x0A, 0xCB, 0x1E, 0x0B, // 196
+ 0x0A, 0xE9, 0x1D, 0x0B, // 197
+ 0x0B, 0x06, 0x2C, 0x0F, // 198
+ 0x0B, 0x32, 0x1A, 0x0A, // 199
+ 0x0B, 0x4C, 0x20, 0x0C, // 200
+ 0x0B, 0x6C, 0x20, 0x0C, // 201
+ 0x0B, 0x8C, 0x1A, 0x09, // 202
+ 0x0B, 0xA6, 0x1A, 0x0B, // 203
+ 0x0B, 0xC0, 0x23, 0x0D, // 204
+ 0x0B, 0xE3, 0x1D, 0x0B, // 205
+ 0x0C, 0x00, 0x20, 0x0C, // 206
+ 0x0C, 0x20, 0x1D, 0x0C, // 207
+ 0x0C, 0x3D, 0x1C, 0x0B, // 208
+ 0x0C, 0x59, 0x20, 0x0C, // 209
+ 0x0C, 0x79, 0x19, 0x09, // 210
+ 0x0C, 0x92, 0x1C, 0x0A, // 211
+ 0x0C, 0xAE, 0x23, 0x0D, // 212
+ 0x0C, 0xD1, 0x20, 0x0B, // 213
+ 0x0C, 0xF1, 0x21, 0x0C, // 214
+ 0x0D, 0x12, 0x1A, 0x0B, // 215
+ 0x0D, 0x2C, 0x26, 0x0F, // 216
+ 0x0D, 0x52, 0x2A, 0x0F, // 217
+ 0x0D, 0x7C, 0x23, 0x0D, // 218
+ 0x0D, 0x9F, 0x23, 0x0E, // 219
+ 0x0D, 0xC2, 0x1A, 0x0A, // 220
+ 0x0D, 0xDC, 0x1D, 0x0C, // 221
+ 0x0D, 0xF9, 0x2C, 0x10, // 222
+ 0x0E, 0x25, 0x20, 0x0C, // 223
+ 0x0E, 0x45, 0x17, 0x09, // 224
+ 0x0E, 0x5C, 0x1A, 0x09, // 225
+ 0x0E, 0x76, 0x14, 0x09, // 226
+ 0x0E, 0x8A, 0x10, 0x06, // 227
+ 0x0E, 0x9A, 0x1B, 0x09, // 228
+ 0x0E, 0xB5, 0x17, 0x09, // 229
+ 0x0E, 0xCC, 0x20, 0x0B, // 230
+ 0x0E, 0xEC, 0x11, 0x07, // 231
+ 0x0E, 0xFD, 0x17, 0x09, // 232
+ 0x0F, 0x14, 0x17, 0x09, // 233
+ 0x0F, 0x2B, 0x14, 0x07, // 234
+ 0x0F, 0x3F, 0x17, 0x09, // 235
+ 0x0F, 0x56, 0x1D, 0x0B, // 236
+ 0x0F, 0x73, 0x17, 0x09, // 237
+ 0x0F, 0x8A, 0x17, 0x09, // 238
+ 0x0F, 0xA1, 0x17, 0x09, // 239
+ 0x0F, 0xB8, 0x17, 0x09, // 240
+ 0x0F, 0xCF, 0x14, 0x08, // 241
+ 0x0F, 0xE3, 0x13, 0x07, // 242
+ 0x0F, 0xF6, 0x13, 0x07, // 243
+ 0x10, 0x09, 0x23, 0x0D, // 244
+ 0x10, 0x2C, 0x17, 0x09, // 245
+ 0x10, 0x43, 0x1A, 0x0A, // 246
+ 0x10, 0x5D, 0x14, 0x08, // 247
+ 0x10, 0x71, 0x23, 0x0D, // 248
+ 0x10, 0x94, 0x23, 0x0D, // 249
+ 0x10, 0xB7, 0x1A, 0x0A, // 250
+ 0x10, 0xD1, 0x1D, 0x0C, // 251
+ 0x10, 0xEE, 0x17, 0x09, // 252
+ 0x11, 0x05, 0x14, 0x08, // 253
+ 0x11, 0x19, 0x20, 0x0C, // 254
+ 0x11, 0x39, 0x17, 0x09, // 255
+ // Font Data:
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x5F, // 33
+ 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, // 34
+ 0x80, 0x08, 0x00, 0x80, 0x78, 0x00, 0xC0, 0x0F, 0x00, 0xB8, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x78, 0x00, 0xC0, 0x0F, 0x00,
+ 0xB8, 0x08, 0x00, 0x80, 0x08, // 35
+ 0x00, 0x00, 0x00, 0xE0, 0x10, 0x00, 0x10, 0x21, 0x00, 0x08, 0x41, 0x00, 0xFC, 0xFF, 0x00, 0x08, 0x42, 0x00, 0x08, 0x22, 0x00,
+ 0x30, 0x1C, // 36
+ 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x61, 0x00, 0xF0, 0x18, 0x00, 0x00, 0x06, 0x00,
+ 0xC0, 0x01, 0x00, 0x30, 0x3C, 0x00, 0x08, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x3C, // 37
+ 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x22, 0x00, 0x88, 0x41, 0x00, 0x08, 0x43, 0x00, 0x88, 0x44, 0x00, 0x70, 0x28, 0x00,
+ 0x00, 0x10, 0x00, 0x00, 0x28, 0x00, 0x00, 0x44, // 38
+ 0x00, 0x00, 0x00, 0x78, // 39
+ 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x70, 0xC0, 0x01, 0x08, 0x00, 0x02, // 40
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x70, 0xC0, 0x01, 0x80, 0x3F, // 41
+ 0x10, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x38, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x10, // 42
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x02, // 43
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, // 44
+ 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, // 45
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, // 46
+ 0x00, 0x60, 0x00, 0x00, 0x1E, 0x00, 0xE0, 0x01, 0x00, 0x18, // 47
+ 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00,
+ 0xE0, 0x1F, // 48
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0xF8, 0x7F, // 49
+ 0x00, 0x00, 0x00, 0x20, 0x40, 0x00, 0x10, 0x60, 0x00, 0x08, 0x50, 0x00, 0x08, 0x48, 0x00, 0x08, 0x44, 0x00, 0x18, 0x43, 0x00,
+ 0xE0, 0x40, // 50
+ 0x00, 0x00, 0x00, 0x20, 0x30, 0x00, 0x10, 0x20, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x88, 0x41, 0x00, 0xF0, 0x22, 0x00,
+ 0x00, 0x1C, // 51
+ 0x00, 0x0C, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x09, 0x00, 0xC0, 0x08, 0x00, 0x20, 0x08, 0x00, 0x10, 0x08, 0x00, 0xF8, 0x7F, 0x00,
+ 0x00, 0x08, // 52
+ 0x00, 0x00, 0x00, 0xC0, 0x11, 0x00, 0xB8, 0x20, 0x00, 0x88, 0x40, 0x00, 0x88, 0x40, 0x00, 0x88, 0x40, 0x00, 0x08, 0x21, 0x00,
+ 0x08, 0x1E, // 53
+ 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x10, 0x21, 0x00, 0x88, 0x40, 0x00, 0x88, 0x40, 0x00, 0x88, 0x40, 0x00, 0x10, 0x21, 0x00,
+ 0x20, 0x1E, // 54
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x78, 0x00, 0x88, 0x07, 0x00, 0x68, 0x00, 0x00,
+ 0x18, // 55
+ 0x00, 0x00, 0x00, 0x60, 0x1C, 0x00, 0x90, 0x22, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x90, 0x22, 0x00,
+ 0x60, 0x1C, // 56
+ 0x00, 0x00, 0x00, 0xE0, 0x11, 0x00, 0x10, 0x22, 0x00, 0x08, 0x44, 0x00, 0x08, 0x44, 0x00, 0x08, 0x44, 0x00, 0x10, 0x22, 0x00,
+ 0xE0, 0x1F, // 57
+ 0x00, 0x00, 0x00, 0x40, 0x40, // 58
+ 0x00, 0x00, 0x00, 0x40, 0xC0, 0x01, // 59
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00,
+ 0x40, 0x10, // 60
+ 0x00, 0x00, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00,
+ 0x80, 0x08, // 61
+ 0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x00,
+ 0x00, 0x02, // 62
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x5C, 0x00, 0x08, 0x02, 0x00, 0x10, 0x01, 0x00,
+ 0xE0, // 63
+ 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0xC0, 0x40, 0x00, 0x20, 0x80, 0x00, 0x10, 0x1E, 0x01, 0x10, 0x21, 0x01, 0x88, 0x40, 0x02,
+ 0x48, 0x40, 0x02, 0x48, 0x40, 0x02, 0x48, 0x20, 0x02, 0x88, 0x7C, 0x02, 0xC8, 0x43, 0x02, 0x10, 0x40, 0x02, 0x10, 0x20, 0x01,
+ 0x60, 0x10, 0x01, 0x80, 0x8F, // 64
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x70, 0x04, 0x00, 0x08, 0x04, 0x00, 0x70, 0x04, 0x00,
+ 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 65
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00,
+ 0x08, 0x41, 0x00, 0x90, 0x22, 0x00, 0x60, 0x1C, // 66
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00,
+ 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, // 67
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00,
+ 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 68
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00,
+ 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 69
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00,
+ 0x08, 0x02, 0x00, 0x08, // 70
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x42, 0x00,
+ 0x08, 0x42, 0x00, 0x10, 0x22, 0x00, 0x20, 0x12, 0x00, 0x00, 0x0E, // 71
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF8, 0x7F, // 72
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, // 73
+ 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xF8, 0x3F, // 74
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x80, 0x03, 0x00, 0x40, 0x04, 0x00,
+ 0x20, 0x18, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, // 75
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, // 76
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x30, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, 0x00,
+ 0x00, 0x1C, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x00, 0x00, 0xF8, 0x7F, // 77
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x10, 0x00, 0x00, 0x60, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x04, 0x00,
+ 0x00, 0x18, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x7F, // 78
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00,
+ 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 79
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00,
+ 0x08, 0x02, 0x00, 0x10, 0x01, 0x00, 0xE0, // 80
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x50, 0x00,
+ 0x08, 0x50, 0x00, 0x10, 0x20, 0x00, 0x20, 0x70, 0x00, 0xC0, 0x5F, // 81
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x06, 0x00,
+ 0x08, 0x1A, 0x00, 0x10, 0x21, 0x00, 0xE0, 0x40, // 82
+ 0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x90, 0x20, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x42, 0x00,
+ 0x08, 0x42, 0x00, 0x10, 0x22, 0x00, 0x20, 0x1C, // 83
+ 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x08, // 84
+ 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 85
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x18, 0x00, 0x00, 0x60, 0x00, 0x00, 0x18, 0x00,
+ 0x00, 0x07, 0x00, 0xE0, 0x00, 0x00, 0x18, // 86
+ 0x18, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x03, 0x00, 0x70, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x70, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1E, 0x00, 0xE0, 0x01, 0x00,
+ 0x18, // 87
+ 0x00, 0x40, 0x00, 0x08, 0x20, 0x00, 0x10, 0x10, 0x00, 0x60, 0x0C, 0x00, 0x80, 0x02, 0x00, 0x00, 0x01, 0x00, 0x80, 0x02, 0x00,
+ 0x60, 0x0C, 0x00, 0x10, 0x10, 0x00, 0x08, 0x20, 0x00, 0x00, 0x40, // 88
+ 0x08, 0x00, 0x00, 0x30, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x7E, 0x00, 0x80, 0x01, 0x00, 0x40, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x08, // 89
+ 0x00, 0x40, 0x00, 0x08, 0x60, 0x00, 0x08, 0x58, 0x00, 0x08, 0x44, 0x00, 0x08, 0x43, 0x00, 0x88, 0x40, 0x00, 0x68, 0x40, 0x00,
+ 0x18, 0x40, 0x00, 0x08, 0x40, // 90
+ 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x03, 0x08, 0x00, 0x02, 0x08, 0x00, 0x02, // 91
+ 0x18, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x60, // 92
+ 0x08, 0x00, 0x02, 0x08, 0x00, 0x02, 0xF8, 0xFF, 0x03, // 93
+ 0x00, 0x01, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x08, 0x00, 0x00, 0x30, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x01, // 94
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, // 95
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, // 96
+ 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x42, 0x00, 0x40, 0x22, 0x00,
+ 0x80, 0x7F, // 97
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0x00, 0x1F, // 98
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, // 99
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0xF8, 0x7F, // 100
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x24, 0x00,
+ 0x00, 0x17, // 101
+ 0x40, 0x00, 0x00, 0xF0, 0x7F, 0x00, 0x48, 0x00, 0x00, 0x48, // 102
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x01, 0x80, 0x20, 0x02, 0x40, 0x40, 0x02, 0x40, 0x40, 0x02, 0x40, 0x40, 0x02, 0x80, 0x20, 0x01,
+ 0xC0, 0xFF, // 103
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x7F, // 104
+ 0x00, 0x00, 0x00, 0xC8, 0x7F, // 105
+ 0x00, 0x00, 0x02, 0xC8, 0xFF, 0x01, // 106
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x06, 0x00, 0x00, 0x19, 0x00, 0x80, 0x20, 0x00,
+ 0x40, 0x40, // 107
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, // 108
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x7F, 0x00,
+ 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x7F, // 109
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x7F, // 110
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0x00, 0x1F, // 111
+ 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x03, 0x80, 0x60, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0x00, 0x1F, // 112
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0xC0, 0xFF, 0x03, // 113
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, // 114
+ 0x00, 0x00, 0x00, 0x80, 0x23, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x38, // 115
+ 0x40, 0x00, 0x00, 0xF0, 0x7F, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, // 116
+ 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x7F, // 117
+ 0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x03, 0x00, 0xC0, // 118
+ 0xC0, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x03, 0x00,
+ 0x00, 0x1C, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1F, 0x00, 0xC0, // 119
+ 0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x04, 0x00, 0x00, 0x1B, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, // 120
+ 0xC0, 0x01, 0x00, 0x00, 0x06, 0x02, 0x00, 0x38, 0x02, 0x00, 0xC0, 0x01, 0x00, 0x78, 0x00, 0x00, 0x07, 0x00, 0xC0, // 121
+ 0x40, 0x40, 0x00, 0x40, 0x60, 0x00, 0x40, 0x58, 0x00, 0x40, 0x44, 0x00, 0x40, 0x43, 0x00, 0xC0, 0x40, 0x00, 0x40, 0x40, // 122
+ 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0xF0, 0xFB, 0x01, 0x08, 0x00, 0x02, 0x08, 0x00, 0x02, // 123
+ 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x03, // 124
+ 0x08, 0x00, 0x02, 0x08, 0x00, 0x02, 0xF0, 0xFB, 0x01, 0x00, 0x04, 0x00, 0x00, 0x04, // 125
+ 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x01, // 126
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xFF, 0x03, // 161
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x03, 0x40, 0xF0, 0x00, 0x40, 0x4E, 0x00, 0xC0, 0x41, 0x00, 0xB8, 0x20, 0x00,
+ 0x00, 0x11, // 162
+ 0x00, 0x41, 0x00, 0xF0, 0x31, 0x00, 0x18, 0x2F, 0x00, 0x08, 0x21, 0x00, 0x08, 0x61, 0x00, 0x08, 0x40, 0x00, 0x10, 0x40, 0x00,
+ 0x20, 0x20, // 163
+ 0x00, 0x00, 0x00, 0x40, 0x0B, 0x00, 0x80, 0x04, 0x00, 0x40, 0x08, 0x00, 0x40, 0x08, 0x00, 0x80, 0x04, 0x00, 0x40, 0x0B, // 164
+ 0x08, 0x0A, 0x00, 0x10, 0x0A, 0x00, 0x60, 0x0A, 0x00, 0x80, 0x0B, 0x00, 0x00, 0x7E, 0x00, 0x80, 0x0B, 0x00, 0x60, 0x0A, 0x00,
+ 0x10, 0x0A, 0x00, 0x08, 0x0A, // 165
+ 0x00, 0x00, 0x00, 0xF8, 0xF1, 0x03, // 166
+ 0x00, 0x86, 0x00, 0x70, 0x09, 0x01, 0xC8, 0x10, 0x02, 0x88, 0x10, 0x02, 0x08, 0x21, 0x02, 0x08, 0x61, 0x02, 0x30, 0xD2, 0x01,
+ 0x00, 0x0C, // 167
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x09, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x08, 0x41, 0x00, 0x09, 0x41, 0x00,
+ 0x0A, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 168
+ 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0xC8, 0x47, 0x00, 0x28, 0x48, 0x00, 0x28, 0x48, 0x00, 0x28, 0x48, 0x00,
+ 0x28, 0x48, 0x00, 0x48, 0x44, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 169
+ 0xD0, 0x00, 0x00, 0x48, 0x01, 0x00, 0x28, 0x01, 0x00, 0x28, 0x01, 0x00, 0xF0, 0x01, // 170
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x1B, 0x00, 0x80, 0x20, 0x00, 0x00, 0x04, 0x00, 0x00, 0x1B, 0x00, 0x80, 0x20, // 171
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x80, 0x0F, // 172
+ 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, // 173
+ 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0xE8, 0x4F, 0x00, 0x28, 0x41, 0x00, 0x28, 0x41, 0x00, 0x28, 0x43, 0x00,
+ 0x28, 0x45, 0x00, 0xC8, 0x48, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 174
+ 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x04, // 175
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x48, 0x00, 0x00, 0x48, 0x00, 0x00, 0x30, // 176
+ 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0xE0, 0x4F, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00,
+ 0x00, 0x41, // 177
+ 0x10, 0x01, 0x00, 0x88, 0x01, 0x00, 0x48, 0x01, 0x00, 0x48, 0x01, 0x00, 0x30, 0x01, // 178
+ 0x90, 0x00, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x28, 0x01, 0x00, 0xD8, // 179
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, // 180
+ 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x03, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00,
+ 0xC0, 0x7F, // 181
+ 0xF0, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0x01, 0x00, 0xF8, 0x01, 0x00, 0xF8, 0xFF, 0x03, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0xF8, 0xFF, 0x03, 0x08, // 182
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // 183
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x90, 0x24, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x90, 0x24, 0x00,
+ 0x00, 0x17, // 184
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x01, // 185
+ 0xF0, 0x00, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0xF0, // 186
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x04, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1B, 0x00,
+ 0x00, 0x04, // 187
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x40, 0x00, 0xF8, 0x21, 0x00, 0x00, 0x10, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x02, 0x00,
+ 0x80, 0x01, 0x00, 0x40, 0x30, 0x00, 0x30, 0x28, 0x00, 0x08, 0x24, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x20, // 188
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x40, 0x00, 0xF8, 0x31, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00,
+ 0x80, 0x00, 0x00, 0x60, 0x44, 0x00, 0x10, 0x62, 0x00, 0x08, 0x52, 0x00, 0x00, 0x52, 0x00, 0x00, 0x4C, // 189
+ 0x90, 0x00, 0x00, 0x08, 0x01, 0x00, 0x08, 0x41, 0x00, 0x28, 0x21, 0x00, 0xD8, 0x18, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00,
+ 0x80, 0x00, 0x00, 0x40, 0x30, 0x00, 0x30, 0x28, 0x00, 0x08, 0x24, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x20, // 190
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x60, 0x03, 0x00, 0x3B, 0x02, 0x00, 0x3B, 0x02,
+ 0x00, 0x00, 0x03, 0x00, 0x80, 0x01, // 191
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x70, 0x04, 0x00, 0x08, 0x04, 0x00, 0x70, 0x04, 0x00,
+ 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 192
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00,
+ 0x08, 0x66, 0x00, 0x08, 0x3C, // 193
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00,
+ 0x08, 0x41, 0x00, 0x90, 0x22, 0x00, 0x60, 0x1C, // 194
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x18, // 195
+ 0x00, 0xC0, 0x01, 0x00, 0x60, 0x00, 0xF0, 0x5F, 0x00, 0x18, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00,
+ 0x08, 0x40, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0xC0, 0x01, // 196
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00,
+ 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 197
+ 0x08, 0x40, 0x00, 0x08, 0x60, 0x00, 0x38, 0x38, 0x00, 0xE0, 0x0C, 0x00, 0x80, 0x07, 0x00, 0x00, 0x03, 0x00, 0x00, 0x02, 0x00,
+ 0xF8, 0x7F, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x80, 0x07, 0x00, 0xE0, 0x0C, 0x00, 0x38, 0x38, 0x00, 0x08, 0x60, 0x00,
+ 0x08, 0x40, // 198
+ 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00,
+ 0x10, 0x22, 0x00, 0xE0, 0x1D, // 199
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0xF8, 0x7F, // 200
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x20, 0x00, 0x01, 0x10, 0x00, 0x02, 0x08, 0x00, 0x02, 0x04, 0x00, 0x02, 0x02, 0x00,
+ 0x01, 0x01, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0xF8, 0x7F, // 201
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x01, 0x00, 0x80, 0x02, 0x00, 0x40, 0x04, 0x00, 0x20, 0x08, 0x00, 0x10, 0x10, 0x00,
+ 0x08, 0x20, 0x00, 0x08, 0x40, // 202
+ 0x00, 0x40, 0x00, 0x00, 0x60, 0x00, 0xF0, 0x3F, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0xF8, 0x7F, // 203
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x30, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x0C, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x00, 0x00, 0xF8, 0x7F, // 204
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF8, 0x7F, // 205
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00,
+ 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 206
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x7F, // 207
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00,
+ 0x08, 0x02, 0x00, 0x10, 0x01, 0x00, 0xE0, // 208
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00,
+ 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, // 209
+ 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x08, // 210
+ 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0xE0, 0x80, 0x00, 0x80, 0x83, 0x00, 0x00, 0xE6, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x07, 0x00,
+ 0xC0, 0x01, 0x00, 0x60, 0x00, 0x00, 0x18, // 211
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x60, 0x18, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0xF8, 0x7F, 0x00,
+ 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x60, 0x18, 0x00, 0xC0, 0x0F, // 212
+ 0x08, 0x40, 0x00, 0x08, 0x20, 0x00, 0x10, 0x10, 0x00, 0x60, 0x0C, 0x00, 0x80, 0x02, 0x00, 0x00, 0x01, 0x00, 0x80, 0x02, 0x00,
+ 0x60, 0x0C, 0x00, 0x10, 0x10, 0x00, 0x08, 0x20, 0x00, 0x08, 0x40, // 213
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0xC0, 0x01, // 214
+ 0x00, 0x00, 0x00, 0xF8, 0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00,
+ 0x00, 0x04, 0x00, 0xF8, 0x7F, // 215
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0xF8, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xF8, 0x7F, // 216
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0xF8, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0xC0,
+ 0x01, // 217
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00,
+ 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x63, 0x00, 0x00, 0x3E, // 218
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00,
+ 0x00, 0x63, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x7F, // 219
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00,
+ 0x00, 0x63, 0x00, 0x00, 0x3E, // 220
+ 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00,
+ 0x10, 0x61, 0x00, 0x10, 0x31, 0x00, 0xE0, 0x1F, // 221
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x80, 0x0F, 0x00, 0xE0, 0x18, 0x00,
+ 0x30, 0x30, 0x00, 0x18, 0x60, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x18, 0x60, 0x00, 0x30, 0x30, 0x00, 0xE0, 0x18, 0x00,
+ 0x80, 0x0F, // 222
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x61, 0x00, 0x18, 0x13, 0x00, 0x08, 0x0A, 0x00, 0x08, 0x06, 0x00, 0x08, 0x02, 0x00,
+ 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0xF8, 0x7F, // 223
+ 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x42, 0x00, 0x40, 0x22, 0x00,
+ 0x80, 0x7F, // 224
+ 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x30, 0x73, 0x00, 0x90, 0x40, 0x00, 0x90, 0x40, 0x00, 0x90, 0x40, 0x00, 0x98, 0x61, 0x00,
+ 0x18, 0x3F, 0x00, 0x00, 0x0C, // 225
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0xC0, 0x6E, 0x00, 0x80, 0x3B, // 226
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, // 227
+ 0x00, 0xC0, 0x01, 0x00, 0x70, 0x00, 0x80, 0x5F, 0x00, 0xC0, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00,
+ 0xC0, 0x7F, 0x00, 0x00, 0xC0, 0x01, // 228
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x24, 0x00,
+ 0x00, 0x17, // 229
+ 0x40, 0x40, 0x00, 0xC0, 0x70, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x04, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x04, 0x00,
+ 0x00, 0x0E, 0x00, 0x00, 0x3B, 0x00, 0xC0, 0x60, 0x00, 0x40, 0x40, // 230
+ 0x00, 0x00, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x3B, // 231
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x02, 0x00,
+ 0xC0, 0x7F, // 232
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0x20, 0x08, 0x00, 0x20, 0x04, 0x00, 0x10, 0x02, 0x00,
+ 0xC0, 0x7F, // 233
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1B, 0x00, 0xC0, 0x70, 0x00, 0x40, 0x40, // 234
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x80, 0x7F, 0x00, 0xC0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0xC0, 0x7F, // 235
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x60, 0x00, 0x00, 0x38, 0x00,
+ 0xC0, 0x07, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x7F, // 236
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00,
+ 0xC0, 0x7F, // 237
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0x00, 0x1F, // 238
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00,
+ 0xC0, 0x7F, // 239
+ 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x03, 0x80, 0x60, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0x00, 0x1F, // 240
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, // 241
+ 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, // 242
+ 0xC0, 0x01, 0x00, 0x00, 0x06, 0x02, 0x00, 0x38, 0x02, 0x00, 0xC0, 0x01, 0x00, 0x78, 0x00, 0x00, 0x07, 0x00, 0xC0, // 243
+ 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0xC0, 0x60, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0xC0, 0x60, 0x00, 0xE0, 0xFF, 0x03,
+ 0xC0, 0x60, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0xC0, 0x60, 0x00, 0x80, 0x3F, // 244
+ 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x04, 0x00, 0x00, 0x1B, 0x00, 0x80, 0x20, 0x00,
+ 0x40, 0x40, // 245
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0xC0, 0x7F, 0x00, 0x00, 0xC0, // 246
+ 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0xC0, 0x7F, // 247
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xC0, 0x7F, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xC0, 0x7F, // 248
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xC0, 0x7F, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0xC0, // 249
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00,
+ 0x00, 0x64, 0x00, 0x00, 0x38, // 250
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00, 0x00, 0x6C, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x7F, // 251
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00, 0x00, 0x6C, 0x00,
+ 0x00, 0x38, // 252
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x80, 0x20, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0xC0, 0x24, 0x00, 0x80, 0x1F, // 253
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x1E, 0x00, 0x80, 0x33, 0x00, 0xC0, 0x60, 0x00,
+ 0x40, 0x40, 0x00, 0xC0, 0x60, 0x00, 0x80, 0x31, 0x00, 0x00, 0x1F, // 254
+ 0x00, 0x00, 0x00, 0x80, 0x43, 0x00, 0xC0, 0x7A, 0x00, 0x40, 0x0C, 0x00, 0x40, 0x04, 0x00, 0x40, 0x04, 0x00, 0xC0, 0x06, 0x00,
+ 0xC0, 0x7F, // 255
+};
+
+// Font generated or edited with the glyphEditor (@mrekin)
+const uint8_t ArialMT_Plain_24_RU[] PROGMEM = {
+ 0x18, // Width: 24
+ 0x1C, // Height: 28
+ 0x20, // First char: 32
+ 0xE0, // Number of chars: 224
+ // Jump Table:
+ 0xFF, 0xFF, 0x00, 0x07, // 32
+ 0x00, 0x00, 0x13, 0x08, // 33
+ 0x00, 0x13, 0x1A, 0x09, // 34
+ 0x00, 0x2D, 0x33, 0x0D, // 35
+ 0x00, 0x60, 0x2F, 0x0D, // 36
+ 0x00, 0x8F, 0x4F, 0x15, // 37
+ 0x00, 0xDE, 0x3B, 0x10, // 38
+ 0x01, 0x19, 0x0A, 0x05, // 39
+ 0x01, 0x23, 0x1C, 0x08, // 40
+ 0x01, 0x3F, 0x1B, 0x08, // 41
+ 0x01, 0x5A, 0x21, 0x09, // 42
+ 0x01, 0x7B, 0x32, 0x0E, // 43
+ 0x01, 0xAD, 0x10, 0x07, // 44
+ 0x01, 0xBD, 0x1B, 0x08, // 45
+ 0x01, 0xD8, 0x0F, 0x07, // 46
+ 0x01, 0xE7, 0x19, 0x07, // 47
+ 0x02, 0x00, 0x2F, 0x0D, // 48
+ 0x02, 0x2F, 0x23, 0x0D, // 49
+ 0x02, 0x52, 0x2F, 0x0D, // 50
+ 0x02, 0x81, 0x2F, 0x0D, // 51
+ 0x02, 0xB0, 0x2F, 0x0D, // 52
+ 0x02, 0xDF, 0x2F, 0x0D, // 53
+ 0x03, 0x0E, 0x2F, 0x0D, // 54
+ 0x03, 0x3D, 0x2D, 0x0D, // 55
+ 0x03, 0x6A, 0x2F, 0x0D, // 56
+ 0x03, 0x99, 0x2F, 0x0D, // 57
+ 0x03, 0xC8, 0x0F, 0x07, // 58
+ 0x03, 0xD7, 0x10, 0x07, // 59
+ 0x03, 0xE7, 0x2F, 0x0E, // 60
+ 0x04, 0x16, 0x2F, 0x0E, // 61
+ 0x04, 0x45, 0x2E, 0x0E, // 62
+ 0x04, 0x73, 0x2E, 0x0D, // 63
+ 0x04, 0xA1, 0x5B, 0x18, // 64
+ 0x04, 0xFC, 0x3B, 0x0F, // 65
+ 0x05, 0x37, 0x3B, 0x10, // 66
+ 0x05, 0x72, 0x3F, 0x11, // 67
+ 0x05, 0xB1, 0x3F, 0x11, // 68
+ 0x05, 0xF0, 0x3B, 0x10, // 69
+ 0x06, 0x2B, 0x35, 0x0F, // 70
+ 0x06, 0x60, 0x43, 0x13, // 71
+ 0x06, 0xA3, 0x3B, 0x11, // 72
+ 0x06, 0xDE, 0x0F, 0x06, // 73
+ 0x06, 0xED, 0x27, 0x0C, // 74
+ 0x07, 0x14, 0x3F, 0x10, // 75
+ 0x07, 0x53, 0x2F, 0x0D, // 76
+ 0x07, 0x82, 0x43, 0x13, // 77
+ 0x07, 0xC5, 0x3B, 0x11, // 78
+ 0x08, 0x00, 0x47, 0x13, // 79
+ 0x08, 0x47, 0x3A, 0x10, // 80
+ 0x08, 0x81, 0x47, 0x13, // 81
+ 0x08, 0xC8, 0x3F, 0x11, // 82
+ 0x09, 0x07, 0x3B, 0x10, // 83
+ 0x09, 0x42, 0x35, 0x0E, // 84
+ 0x09, 0x77, 0x3B, 0x11, // 85
+ 0x09, 0xB2, 0x39, 0x0F, // 86
+ 0x09, 0xEB, 0x59, 0x17, // 87
+ 0x0A, 0x44, 0x3B, 0x0F, // 88
+ 0x0A, 0x7F, 0x3D, 0x10, // 89
+ 0x0A, 0xBC, 0x37, 0x0F, // 90
+ 0x0A, 0xF3, 0x14, 0x07, // 91
+ 0x0B, 0x07, 0x1B, 0x07, // 92
+ 0x0B, 0x22, 0x18, 0x07, // 93
+ 0x0B, 0x3A, 0x2A, 0x0C, // 94
+ 0x0B, 0x64, 0x34, 0x0D, // 95
+ 0x0B, 0x98, 0x11, 0x08, // 96
+ 0x0B, 0xA9, 0x2F, 0x0D, // 97
+ 0x0B, 0xD8, 0x33, 0x0E, // 98
+ 0x0C, 0x0B, 0x2B, 0x0C, // 99
+ 0x0C, 0x36, 0x2F, 0x0E, // 100
+ 0x0C, 0x65, 0x2F, 0x0D, // 101
+ 0x0C, 0x94, 0x1A, 0x07, // 102
+ 0x0C, 0xAE, 0x2F, 0x0E, // 103
+ 0x0C, 0xDD, 0x2F, 0x0E, // 104
+ 0x0D, 0x0C, 0x0F, 0x05, // 105
+ 0x0D, 0x1B, 0x10, 0x06, // 106
+ 0x0D, 0x2B, 0x2F, 0x0C, // 107
+ 0x0D, 0x5A, 0x0F, 0x06, // 108
+ 0x0D, 0x69, 0x47, 0x14, // 109
+ 0x0D, 0xB0, 0x2F, 0x0E, // 110
+ 0x0D, 0xDF, 0x2F, 0x0D, // 111
+ 0x0E, 0x0E, 0x33, 0x0E, // 112
+ 0x0E, 0x41, 0x30, 0x0E, // 113
+ 0x0E, 0x71, 0x1E, 0x08, // 114
+ 0x0E, 0x8F, 0x2B, 0x0C, // 115
+ 0x0E, 0xBA, 0x1B, 0x07, // 116
+ 0x0E, 0xD5, 0x2F, 0x0E, // 117
+ 0x0F, 0x04, 0x2A, 0x0B, // 118
+ 0x0F, 0x2E, 0x42, 0x11, // 119
+ 0x0F, 0x70, 0x2B, 0x0B, // 120
+ 0x0F, 0x9B, 0x2A, 0x0C, // 121
+ 0x0F, 0xC5, 0x2B, 0x0C, // 122
+ 0x0F, 0xF0, 0x1C, 0x08, // 123
+ 0x10, 0x0C, 0x10, 0x06, // 124
+ 0x10, 0x1C, 0x1B, 0x08, // 125
+ 0x10, 0x37, 0x32, 0x0E, // 126
+ 0xFF, 0xFF, 0x00, 0x18, // 127
+ 0xFF, 0xFF, 0x00, 0x18, // 128
+ 0xFF, 0xFF, 0x00, 0x18, // 129
+ 0xFF, 0xFF, 0x00, 0x18, // 130
+ 0xFF, 0xFF, 0x00, 0x18, // 131
+ 0xFF, 0xFF, 0x00, 0x18, // 132
+ 0xFF, 0xFF, 0x00, 0x18, // 133
+ 0xFF, 0xFF, 0x00, 0x18, // 134
+ 0xFF, 0xFF, 0x00, 0x18, // 135
+ 0xFF, 0xFF, 0x00, 0x18, // 136
+ 0xFF, 0xFF, 0x00, 0x18, // 137
+ 0xFF, 0xFF, 0x00, 0x18, // 138
+ 0xFF, 0xFF, 0x00, 0x18, // 139
+ 0xFF, 0xFF, 0x00, 0x18, // 140
+ 0xFF, 0xFF, 0x00, 0x18, // 141
+ 0xFF, 0xFF, 0x00, 0x18, // 142
+ 0xFF, 0xFF, 0x00, 0x18, // 143
+ 0xFF, 0xFF, 0x00, 0x18, // 144
+ 0xFF, 0xFF, 0x00, 0x18, // 145
+ 0xFF, 0xFF, 0x00, 0x18, // 146
+ 0xFF, 0xFF, 0x00, 0x18, // 147
+ 0xFF, 0xFF, 0x00, 0x18, // 148
+ 0xFF, 0xFF, 0x00, 0x18, // 149
+ 0xFF, 0xFF, 0x00, 0x18, // 150
+ 0xFF, 0xFF, 0x00, 0x18, // 151
+ 0xFF, 0xFF, 0x00, 0x18, // 152
+ 0xFF, 0xFF, 0x00, 0x18, // 153
+ 0xFF, 0xFF, 0x00, 0x18, // 154
+ 0xFF, 0xFF, 0x00, 0x18, // 155
+ 0xFF, 0xFF, 0x00, 0x18, // 156
+ 0xFF, 0xFF, 0x00, 0x18, // 157
+ 0xFF, 0xFF, 0x00, 0x18, // 158
+ 0xFF, 0xFF, 0x00, 0x18, // 159
+ 0xFF, 0xFF, 0x00, 0x07, // 160
+ 0x10, 0x69, 0x14, 0x08, // 161
+ 0x10, 0x7D, 0x2B, 0x0D, // 162
+ 0x10, 0xA8, 0x2F, 0x0D, // 163
+ 0x10, 0xD7, 0x33, 0x0D, // 164
+ 0x11, 0x0A, 0x31, 0x0D, // 165
+ 0x11, 0x3B, 0x10, 0x06, // 166
+ 0x11, 0x4B, 0x2F, 0x0D, // 167
+ 0x11, 0x7A, 0x3B, 0x10, // 168
+ 0x11, 0xB5, 0x46, 0x12, // 169
+ 0x11, 0xFB, 0x1A, 0x09, // 170
+ 0x12, 0x15, 0x27, 0x0D, // 171
+ 0x12, 0x3C, 0x2F, 0x0E, // 172
+ 0x12, 0x6B, 0x1B, 0x08, // 173
+ 0x12, 0x86, 0x46, 0x12, // 174
+ 0x12, 0xCC, 0x31, 0x0D, // 175
+ 0x12, 0xFD, 0x1E, 0x0A, // 176
+ 0x13, 0x1B, 0x33, 0x0D, // 177
+ 0x13, 0x4E, 0x1A, 0x08, // 178
+ 0x13, 0x68, 0x1A, 0x08, // 179
+ 0x13, 0x82, 0x19, 0x08, // 180
+ 0x13, 0x9B, 0x2F, 0x0E, // 181
+ 0x13, 0xCA, 0x31, 0x0D, // 182
+ 0x13, 0xFB, 0x12, 0x08, // 183
+ 0x14, 0x0D, 0x2F, 0x0D, // 184
+ 0x14, 0x3C, 0x16, 0x08, // 185
+ 0x14, 0x52, 0x1E, 0x09, // 186
+ 0x14, 0x70, 0x2E, 0x0D, // 187
+ 0x14, 0x9E, 0x4F, 0x14, // 188
+ 0x14, 0xED, 0x4B, 0x14, // 189
+ 0x15, 0x38, 0x4B, 0x14, // 190
+ 0x15, 0x83, 0x3B, 0x12, // 191
+ 0x15, 0xBE, 0x3B, 0x0F, // 192
+ 0x15, 0xF9, 0x3B, 0x10, // 193
+ 0x16, 0x34, 0x3B, 0x10, // 194
+ 0x16, 0x6F, 0x31, 0x0D, // 195
+ 0x16, 0xA0, 0x3C, 0x10, // 196
+ 0x16, 0xDC, 0x3B, 0x10, // 197
+ 0x17, 0x17, 0x57, 0x16, // 198
+ 0x17, 0x6E, 0x33, 0x0F, // 199
+ 0x17, 0xA1, 0x3B, 0x11, // 200
+ 0x17, 0xDC, 0x3B, 0x11, // 201
+ 0x18, 0x17, 0x37, 0x0E, // 202
+ 0x18, 0x4E, 0x37, 0x10, // 203
+ 0x18, 0x85, 0x43, 0x13, // 204
+ 0x18, 0xC8, 0x3B, 0x11, // 205
+ 0x19, 0x03, 0x47, 0x13, // 206
+ 0x19, 0x4A, 0x3B, 0x11, // 207
+ 0x19, 0x85, 0x3A, 0x10, // 208
+ 0x19, 0xBF, 0x3F, 0x11, // 209
+ 0x19, 0xFE, 0x35, 0x0E, // 210
+ 0x1A, 0x33, 0x39, 0x0F, // 211
+ 0x1A, 0x6C, 0x42, 0x12, // 212
+ 0x1A, 0xAE, 0x3B, 0x0F, // 213
+ 0x1A, 0xE9, 0x43, 0x12, // 214
+ 0x1B, 0x2C, 0x37, 0x10, // 215
+ 0x1B, 0x63, 0x4F, 0x16, // 216
+ 0x1B, 0xB2, 0x58, 0x17, // 217
+ 0x1C, 0x0A, 0x47, 0x13, // 218
+ 0x1C, 0x51, 0x4B, 0x15, // 219
+ 0x1C, 0x9C, 0x3B, 0x10, // 220
+ 0x1C, 0xD7, 0x3F, 0x11, // 221
+ 0x1D, 0x16, 0x5B, 0x18, // 222
+ 0x1D, 0x71, 0x3B, 0x11, // 223
+ 0x1D, 0xAC, 0x2F, 0x0D, // 224
+ 0x1D, 0xDB, 0x33, 0x0E, // 225
+ 0x1E, 0x0E, 0x2F, 0x0D, // 226
+ 0x1E, 0x3D, 0x22, 0x09, // 227
+ 0x1E, 0x5F, 0x33, 0x0E, // 228
+ 0x1E, 0x92, 0x2F, 0x0D, // 229
+ 0x1E, 0xC1, 0x3F, 0x10, // 230
+ 0x1F, 0x00, 0x27, 0x0B, // 231
+ 0x1F, 0x27, 0x2F, 0x0D, // 232
+ 0x1F, 0x56, 0x2F, 0x0D, // 233
+ 0x1F, 0x85, 0x27, 0x0B, // 234
+ 0x1F, 0xAC, 0x2F, 0x0E, // 235
+ 0x1F, 0xDB, 0x3B, 0x11, // 236
+ 0x20, 0x16, 0x2F, 0x0D, // 237
+ 0x20, 0x45, 0x2F, 0x0D, // 238
+ 0x20, 0x74, 0x2B, 0x0D, // 239
+ 0x20, 0x9F, 0x33, 0x0E, // 240
+ 0x20, 0xD2, 0x2B, 0x0C, // 241
+ 0x20, 0xFD, 0x2A, 0x0B, // 242
+ 0x21, 0x27, 0x2A, 0x0C, // 243
+ 0x21, 0x51, 0x4B, 0x14, // 244
+ 0x21, 0x9C, 0x2B, 0x0B, // 245
+ 0x21, 0xC7, 0x33, 0x0E, // 246
+ 0x21, 0xFA, 0x2B, 0x0D, // 247
+ 0x22, 0x25, 0x47, 0x13, // 248
+ 0x22, 0x6C, 0x4B, 0x14, // 249
+ 0x22, 0xB7, 0x37, 0x0F, // 250
+ 0x22, 0xEE, 0x3B, 0x11, // 251
+ 0x23, 0x29, 0x2F, 0x0D, // 252
+ 0x23, 0x58, 0x2B, 0x0C, // 253
+ 0x23, 0x83, 0x43, 0x12, // 254
+ 0x23, 0xC6, 0x2B, 0x0D, // 255
+ // Font Data:
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x33, 0x00, 0xE0, 0xFF, 0x33, // 33
+ 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0,
+ 0x07, 0x00, 0x00, 0xE0, 0x07, // 34
+ 0x00, 0x0C, 0x03, 0x00, 0x00, 0x0C, 0x33, 0x00, 0x00, 0x0C, 0x3F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x80, 0xFF, 0x03, 0x00, 0xE0,
+ 0x0F, 0x03, 0x00, 0x60, 0x0C, 0x33, 0x00, 0x00, 0x0C, 0x3F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x80, 0xFF, 0x03, 0x00, 0xE0, 0x0F,
+ 0x03, 0x00, 0x60, 0x0C, 0x03, 0x00, 0x00, 0x0C, 0x03, // 35
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x06, 0x00, 0xC0, 0x0F, 0x1E, 0x00, 0xC0, 0x18, 0x1C, 0x00, 0x60, 0x18, 0x38, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0xF0, 0xFF, 0xFF, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x60, 0x30, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0xC1,
+ 0x1F, 0x00, 0x80, 0x81, 0x07, // 36
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x20,
+ 0x20, 0x20, 0x00, 0x60, 0x30, 0x38, 0x00, 0xC0, 0x1F, 0x1E, 0x00, 0x80, 0x8F, 0x0F, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x8F, 0x0F, 0x00, 0xC0, 0xC3, 0x1F, 0x00, 0xE0, 0x60, 0x30, 0x00, 0x60, 0x20, 0x20,
+ 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x80, 0x0F, // 37
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x80, 0xE3, 0x1C, 0x00, 0xC0, 0x7F, 0x30, 0x00, 0xE0,
+ 0x3C, 0x30, 0x00, 0x60, 0x38, 0x30, 0x00, 0x60, 0x78, 0x30, 0x00, 0xE0, 0xEC, 0x38, 0x00, 0xC0, 0x8F, 0x1B, 0x00, 0x80, 0x03,
+ 0x1F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x80, 0x38, 0x00, 0x00, 0x00, 0x10, // 38
+ 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xE0, 0x07, // 39
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xFE, 0x7F, 0x00, 0x80, 0x0F, 0xF0, 0x01, 0xC0, 0x01, 0x80, 0x03, 0x60,
+ 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x04, // 40
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x04, 0x60, 0x00, 0x00, 0x06, 0xC0, 0x01, 0x80, 0x03, 0x80, 0x0F, 0xF0, 0x01, 0x00,
+ 0xFE, 0x7F, 0x00, 0x00, 0xF0, 0x0F, // 41
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0xE0,
+ 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x80, // 42
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 43
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0xF0, 0x01, // 44
+ 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, // 45
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, // 46
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xE0,
+ 0x03, 0x00, 0x00, 0x60, // 47
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x80, 0xFF, 0x1F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x60,
+ 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x38, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0xFF,
+ 0x0F, 0x00, 0x00, 0xFE, 0x03, // 48
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 49
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x00, 0xC0, 0x03, 0x38, 0x00, 0xC0, 0x00, 0x3C, 0x00, 0x60, 0x00, 0x36, 0x00, 0x60,
+ 0x00, 0x33, 0x00, 0x60, 0x80, 0x33, 0x00, 0x60, 0xC0, 0x31, 0x00, 0x60, 0x60, 0x30, 0x00, 0xC0, 0x30, 0x30, 0x00, 0xC0, 0x1F,
+ 0x30, 0x00, 0x00, 0x0F, 0x30, // 50
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x06, 0x00, 0xC0, 0x01, 0x0E, 0x00, 0xC0, 0x00, 0x1C, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0xC0, 0x38, 0x30, 0x00, 0xC0, 0x6F, 0x18, 0x00, 0x80, 0xC7,
+ 0x0F, 0x00, 0x00, 0x80, 0x07, // 51
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x3C, 0x03, 0x00, 0x00,
+ 0x0E, 0x03, 0x00, 0x80, 0x07, 0x03, 0x00, 0xC0, 0x01, 0x03, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, // 52
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x06, 0x00, 0x80, 0x3F, 0x1E, 0x00, 0xE0, 0x1F, 0x18, 0x00, 0x60, 0x08, 0x30, 0x00, 0x60,
+ 0x0C, 0x30, 0x00, 0x60, 0x0C, 0x30, 0x00, 0x60, 0x0C, 0x30, 0x00, 0x60, 0x0C, 0x30, 0x00, 0x60, 0x18, 0x1C, 0x00, 0x60, 0xF0,
+ 0x0F, 0x00, 0x00, 0xE0, 0x03, // 53
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x03, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0x63, 0x1C, 0x00, 0xC0, 0x30, 0x38, 0x00, 0x60,
+ 0x18, 0x30, 0x00, 0x60, 0x18, 0x30, 0x00, 0x60, 0x18, 0x30, 0x00, 0x60, 0x18, 0x30, 0x00, 0xE0, 0x30, 0x18, 0x00, 0xC0, 0xF1,
+ 0x0F, 0x00, 0x80, 0xC1, 0x07, // 54
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x60,
+ 0x80, 0x3F, 0x00, 0x60, 0xF0, 0x03, 0x00, 0x60, 0x78, 0x00, 0x00, 0x60, 0x0E, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0xE0, 0x01,
+ 0x00, 0x00, 0x60, // 55
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x80, 0xC7, 0x1F, 0x00, 0xC0, 0x6F, 0x18, 0x00, 0xE0, 0x38, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0xE0, 0x38, 0x30, 0x00, 0xC0, 0x7F, 0x18, 0x00, 0x80, 0xC7,
+ 0x1F, 0x00, 0x00, 0x80, 0x07, // 56
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x0C, 0x00, 0x80, 0x7F, 0x1C, 0x00, 0xC0, 0x61, 0x38, 0x00, 0x60, 0xC0, 0x30, 0x00, 0x60,
+ 0xC0, 0x30, 0x00, 0x60, 0xC0, 0x30, 0x00, 0x60, 0xC0, 0x30, 0x00, 0x60, 0x60, 0x18, 0x00, 0xC0, 0x31, 0x1E, 0x00, 0x80, 0xFF,
+ 0x0F, 0x00, 0x00, 0xFE, 0x01, // 57
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, // 58
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x30, 0x03, 0x00, 0x06, 0xF0, 0x01, // 59
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x00,
+ 0xD8, 0x00, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x06,
+ 0x03, 0x00, 0x00, 0x03, 0x06, // 60
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00,
+ 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C,
+ 0x01, 0x00, 0x00, 0x8C, 0x01, // 61
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00,
+ 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x20, // 62
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x60,
+ 0x80, 0x33, 0x00, 0x60, 0xC0, 0x33, 0x00, 0x60, 0xE0, 0x00, 0x00, 0xE0, 0x30, 0x00, 0x00, 0xC0, 0x38, 0x00, 0x00, 0xC0, 0x1F,
+ 0x00, 0x00, 0x00, 0x07, // 63
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x1E, 0xF0, 0x00, 0x00, 0x07, 0xC0, 0x01, 0x80,
+ 0xC3, 0x87, 0x01, 0xC0, 0xF1, 0x9F, 0x03, 0xC0, 0x38, 0x18, 0x03, 0xC0, 0x0C, 0x30, 0x03, 0x60, 0x0E, 0x30, 0x06, 0x60, 0x06,
+ 0x30, 0x06, 0x60, 0x06, 0x18, 0x06, 0x60, 0x06, 0x1C, 0x06, 0x60, 0x0C, 0x1E, 0x06, 0x60, 0xF8, 0x3F, 0x06, 0xE0, 0xFE, 0x31,
+ 0x06, 0xC0, 0x0E, 0x30, 0x06, 0xC0, 0x01, 0x18, 0x03, 0x80, 0x03, 0x1C, 0x03, 0x00, 0x07, 0x8F, 0x01, 0x00, 0xFE, 0x87, 0x01,
+ 0x00, 0xF8, 0xC1, 0x00, 0x00, 0x00, 0x40, // 64
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x80,
+ 0x8F, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x80, 0x8F, 0x01, 0x00, 0x00, 0xFE,
+ 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 65
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30,
+ 0x30, 0x00, 0xC0, 0x78, 0x30, 0x00, 0xC0, 0xFF, 0x18, 0x00, 0x80, 0xC7, 0x1F, 0x00, 0x00, 0x80, 0x07, // 66
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0E, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0,
+ 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00,
+ 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0F, 0x00, 0x00, 0x02,
+ 0x03, // 67
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60,
+ 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00,
+ 0x30, 0x00, 0xE0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0E, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC,
+ 0x01, // 68
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30,
+ 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 69
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60,
+ 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30,
+ 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, // 70
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0,
+ 0x00, 0x18, 0x00, 0xE0, 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x60,
+ 0x30, 0x00, 0x60, 0x60, 0x30, 0x00, 0xE0, 0x60, 0x38, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0x61, 0x18, 0x00, 0x80, 0xE3, 0x0F,
+ 0x00, 0x00, 0xE2, 0x0F, // 71
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30,
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 72
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 73
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0xE0, 0xFF, 0x0F, // 74
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0xE7, 0x01, 0x00, 0x80, 0x83,
+ 0x07, 0x00, 0xC0, 0x01, 0x0F, 0x00, 0xE0, 0x00, 0x1E, 0x00, 0x60, 0x00, 0x38, 0x00, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x20, // 75
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x30, // 76
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0,
+ 0x0F, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x3F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0xFF, 0x3F,
+ 0x00, 0xE0, 0xFF, 0x3F, // 77
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x80,
+ 0x07, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80,
+ 0x03, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 78
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0,
+ 0x00, 0x18, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00,
+ 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F,
+ 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 79
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60,
+ 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60,
+ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x00, 0x0F, // 80
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0,
+ 0x00, 0x18, 0x00, 0xE0, 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00,
+ 0x36, 0x00, 0x60, 0x00, 0x36, 0x00, 0xE0, 0x00, 0x3C, 0x00, 0xC0, 0x00, 0x1C, 0x00, 0xC0, 0x01, 0x3C, 0x00, 0x80, 0x07, 0x3F,
+ 0x00, 0x00, 0xFF, 0x77, 0x00, 0x00, 0xFC, 0x61, // 81
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60,
+ 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x70, 0x00, 0x00, 0x60, 0xF0, 0x00, 0x00, 0x60, 0xF0,
+ 0x03, 0x00, 0x60, 0xB0, 0x07, 0x00, 0xE0, 0x18, 0x1F, 0x00, 0xC0, 0x1F, 0x3C, 0x00, 0x80, 0x0F, 0x30, 0x00, 0x00, 0x00,
+ 0x20, // 82
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0x0F, 0x00, 0xC0, 0x1F, 0x1C, 0x00, 0xC0, 0x18, 0x18, 0x00, 0x60,
+ 0x38, 0x38, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x70, 0x30, 0x00, 0x60, 0x70,
+ 0x30, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0xE1, 0x1C, 0x00, 0x80, 0xC3, 0x0F, 0x00, 0x00, 0x83, 0x07, // 83
+ 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00,
+ 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 84
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x07, // 85
+ 0x20, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00,
+ 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xF8,
+ 0x01, 0x00, 0x00, 0x3E, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x20, // 86
+ 0x60, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xE0, 0x03,
+ 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xE0, 0x0F,
+ 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x80, 0xFF, 0x00, 0x00,
+ 0xE0, 0x07, 0x00, 0x00, 0x60, // 87
+ 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x30, 0x00, 0x60, 0x00, 0x3C, 0x00, 0xE0, 0x01, 0x1E, 0x00, 0xC0, 0x83, 0x0F, 0x00, 0x00,
+ 0xCF, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x80, 0xCF, 0x03, 0x00, 0xC0, 0x83,
+ 0x07, 0x00, 0xE0, 0x01, 0x1E, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00, 0x20, // 88
+ 0x20, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x1E,
+ 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x20, // 89
+ 0x00, 0x00, 0x30, 0x00, 0x60, 0x00, 0x38, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x60, 0x00, 0x37, 0x00, 0x60, 0x80, 0x33, 0x00, 0x60,
+ 0xC0, 0x31, 0x00, 0x60, 0xE0, 0x30, 0x00, 0x60, 0x38, 0x30, 0x00, 0x60, 0x1C, 0x30, 0x00, 0x60, 0x0E, 0x30, 0x00, 0x60, 0x07,
+ 0x30, 0x00, 0xE0, 0x01, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, // 90
+ 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x60, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, // 91
+ 0x60, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00,
+ 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 92
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, 0xE0, 0xFF, 0xFF, 0x07, 0xE0,
+ 0xFF, 0xFF, 0x07, // 93
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0xE0,
+ 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x20, // 94
+ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
+ 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, // 95
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x80, // 96
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x00, 0x86, 0x31, 0x00, 0x00,
+ 0x86, 0x31, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x18, 0x00, 0x00, 0xCE, 0x1C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8,
+ 0x3F, 0x00, 0x00, 0x00, 0x20, // 97
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00,
+ 0x0C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C,
+ 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 98
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00,
+ 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x18, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x18,
+ 0x0C, // 99
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00,
+ 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0xE0, 0xFF,
+ 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 100
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xCC, 0x1C, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00,
+ 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xDC, 0x18, 0x00, 0x00, 0xF8,
+ 0x0C, 0x00, 0x00, 0xF0, 0x04, // 101
+ 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xC0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x06, 0x00, 0x00, 0x60,
+ 0x06, 0x00, 0x00, 0x60, 0x06, // 102
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x01, 0x00, 0xF8, 0x8F, 0x03, 0x00, 0x1C, 0x1C, 0x07, 0x00, 0x0E, 0x38, 0x06, 0x00,
+ 0x06, 0x30, 0x06, 0x00, 0x06, 0x30, 0x06, 0x00, 0x06, 0x30, 0x06, 0x00, 0x0C, 0x18, 0x07, 0x00, 0x18, 0x8C, 0x03, 0x00, 0xFE,
+ 0xFF, 0x03, 0x00, 0xFE, 0xFF, // 103
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x0C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 104
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0x60, 0xFE, 0x3F, // 105
+ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x60, 0xFE, 0xFF, 0x07, 0x60, 0xFE, 0xFF, 0x03, // 106
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00,
+ 0xE0, 0x00, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x98, 0x07, 0x00, 0x00, 0x0C, 0x0F, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x02,
+ 0x30, 0x00, 0x00, 0x00, 0x20, // 107
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 108
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF8,
+ 0x3F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00,
+ 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 109
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x0C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 110
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00,
+ 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8,
+ 0x0F, 0x00, 0x00, 0xF0, 0x07, // 111
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0x18, 0x0C, 0x00, 0x00,
+ 0x0C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C,
+ 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 112
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00,
+ 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0xFE,
+ 0xFF, 0x07, 0x00, 0xFE, 0xFF, 0x07, // 113
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, // 114
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x0C, 0x00, 0x00, 0x7C, 0x1C, 0x00, 0x00, 0xEE, 0x38, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00,
+ 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x31, 0x00, 0x00, 0xC6, 0x31, 0x00, 0x00, 0x8E, 0x39, 0x00, 0x00, 0x9C, 0x1F, 0x00, 0x00, 0x18,
+ 0x0F, // 115
+ 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xC0, 0xFF, 0x1F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00,
+ 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, // 116
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0xFE,
+ 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 117
+ 0x00, 0x06, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
+ 0x06, // 118
+ 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00,
+ 0x80, 0x1F, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0xE0,
+ 0x03, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x7E, 0x00,
+ 0x00, 0x00, 0x0E, // 119
+ 0x00, 0x02, 0x20, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x38, 0x0E, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00,
+ 0xC0, 0x01, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x38, 0x0E, 0x00, 0x00, 0x1C, 0x3C, 0x00, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x02,
+ 0x20, // 120
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x00,
+ 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0xF8, 0x03, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00,
+ 0x06, // 121
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x06, 0x3E, 0x00, 0x00, 0x06, 0x37, 0x00, 0x00,
+ 0xC6, 0x33, 0x00, 0x00, 0xE6, 0x30, 0x00, 0x00, 0x76, 0x30, 0x00, 0x00, 0x3E, 0x30, 0x00, 0x00, 0x1E, 0x30, 0x00, 0x00, 0x06,
+ 0x30, // 122
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0xFF, 0xFF, 0x03, 0xE0, 0x3F, 0xFC, 0x07, 0x60,
+ 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, // 123
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFF, 0x0F, // 124
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, 0xE0, 0x3F, 0xFC, 0x07, 0xC0, 0xFF, 0xFF, 0x03, 0x00,
+ 0xC0, 0x03, 0x00, 0x00, 0x80, 0x01, // 125
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0,
+ 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x60, // 126
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0x07, 0x00, 0xE6, 0xFF, 0x07, // 161
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x9C, 0x07, 0x00, 0x0E, 0x78, 0x00, 0x00,
+ 0x06, 0x3F, 0x00, 0x00, 0xE6, 0x30, 0x00, 0x00, 0x1E, 0x30, 0x00, 0xE0, 0x0D, 0x18, 0x00, 0x00, 0x1C, 0x0E, 0x00, 0x00, 0x10,
+ 0x06, // 162
+ 0x00, 0x60, 0x10, 0x00, 0x00, 0x60, 0x38, 0x00, 0x80, 0x7F, 0x1C, 0x00, 0xC0, 0xFF, 0x1F, 0x00, 0xE0, 0xE0, 0x19, 0x00, 0x60,
+ 0x60, 0x18, 0x00, 0x60, 0x60, 0x18, 0x00, 0x60, 0x60, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0xC0, 0x01, 0x30, 0x00, 0x80, 0x01,
+ 0x38, 0x00, 0x00, 0x00, 0x10, // 163
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0xF7, 0x0E, 0x00, 0x00, 0xFE, 0x07, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00,
+ 0x06, 0x06, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0xFE,
+ 0x07, 0x00, 0x00, 0xF7, 0x0E, 0x00, 0x00, 0x02, 0x04, // 164
+ 0xE0, 0x60, 0x06, 0x00, 0xC0, 0x61, 0x06, 0x00, 0x80, 0x67, 0x06, 0x00, 0x00, 0x6E, 0x06, 0x00, 0x00, 0x7C, 0x06, 0x00, 0x00,
+ 0xF0, 0x3F, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x7C, 0x06, 0x00, 0x00, 0x7E, 0x06, 0x00, 0x80, 0x67, 0x06, 0x00, 0xC0, 0x61,
+ 0x06, 0x00, 0xE0, 0x60, 0x06, 0x00, 0x20, // 165
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x7F, 0xF8, 0x0F, 0xE0, 0x7F, 0xF8, 0x0F, // 166
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x80, 0xF3, 0xC1, 0x00, 0xC0, 0x1F, 0xC3, 0x03, 0xE0, 0x0C, 0x07, 0x03, 0x60,
+ 0x1C, 0x06, 0x06, 0x60, 0x18, 0x0C, 0x06, 0x60, 0x30, 0x1C, 0x06, 0xE0, 0x70, 0x38, 0x07, 0xC0, 0xE1, 0xF4, 0x03, 0x80, 0xC1,
+ 0xE7, 0x01, 0x00, 0x80, 0x03, // 167
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x6C,
+ 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x6C, 0x30,
+ 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 168
+ 0x00, 0xF8, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0x07, 0x07, 0x00, 0x80, 0x01, 0x0C, 0x00, 0xC0, 0x78, 0x1C, 0x00, 0xC0,
+ 0xFE, 0x19, 0x00, 0x60, 0x86, 0x31, 0x00, 0x60, 0x03, 0x33, 0x00, 0x60, 0x03, 0x33, 0x00, 0x60, 0x03, 0x33, 0x00, 0x60, 0x03,
+ 0x33, 0x00, 0x60, 0x87, 0x33, 0x00, 0xC0, 0x86, 0x19, 0x00, 0xC0, 0x85, 0x1C, 0x00, 0x80, 0x01, 0x0C, 0x00, 0x00, 0x07, 0x07,
+ 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xF8, // 169
+ 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1C, 0x00, 0x00, 0xE0, 0x3E, 0x00, 0x00, 0x60, 0x32, 0x00, 0x00, 0x60, 0x32, 0x00, 0x00, 0xE0,
+ 0x3F, 0x00, 0x00, 0xC0, 0x3F, // 170
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x78, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00,
+ 0x84, 0x10, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x78, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x04, 0x10, // 171
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+ 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xFC,
+ 0x01, 0x00, 0x00, 0xFC, 0x01, // 172
+ 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, // 173
+ 0x00, 0xF8, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0x07, 0x07, 0x00, 0x80, 0x01, 0x0C, 0x00, 0xC0, 0x00, 0x1C, 0x00, 0xC0,
+ 0xFE, 0x1B, 0x00, 0x60, 0xFE, 0x33, 0x00, 0x60, 0x66, 0x30, 0x00, 0x60, 0x66, 0x30, 0x00, 0x60, 0xE6, 0x30, 0x00, 0x60, 0xFE,
+ 0x31, 0x00, 0x60, 0x3C, 0x33, 0x00, 0xC0, 0x00, 0x1A, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x01, 0x0C, 0x00, 0x00, 0x07, 0x07,
+ 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xF8, // 174
+ 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C,
+ 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00,
+ 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, // 175
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x20,
+ 0x08, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x80, 0x03, // 176
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00,
+ 0x60, 0x30, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60,
+ 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, // 177
+ 0x40, 0x20, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x20, 0x38, 0x00, 0x00, 0x20, 0x2C, 0x00, 0x00, 0x20, 0x26, 0x00, 0x00, 0xE0,
+ 0x23, 0x00, 0x00, 0xC0, 0x21, // 178
+ 0x40, 0x10, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x20, 0x22, 0x00, 0x00, 0x20, 0x22, 0x00, 0x00, 0xE0,
+ 0x3D, 0x00, 0x00, 0xC0, 0x1D, // 179
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0x20, // 180
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0xFE,
+ 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 181
+ 0x00, 0x0F, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0xE0, 0x7F, 0x00, 0x00, 0xE0, 0x7F, 0x00, 0x00, 0xE0,
+ 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF,
+ 0xFF, 0x07, 0x60, 0x00, 0x00, 0x00, 0x60, // 182
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 183
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x80, 0x9C, 0x1C, 0x00, 0x00, 0x8C, 0x30, 0x00, 0x00,
+ 0x86, 0x30, 0x00, 0x00, 0x86, 0x30, 0x00, 0x00, 0x86, 0x30, 0x00, 0x00, 0x8C, 0x30, 0x00, 0x80, 0x9C, 0x18, 0x00, 0x00, 0xF8,
+ 0x1C, 0x00, 0x00, 0xF0, 0x0C, // 184
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x3F, 0x00, 0x00, 0xE0,
+ 0x3F, // 185
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0xE0, 0x38, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0xE0,
+ 0x38, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x80, 0x0F, // 186
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00,
+ 0x78, 0x0F, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x84, 0x10, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x78, 0x0F, 0x00, 0x00, 0xE0,
+ 0x03, 0x00, 0x00, 0x80, // 187
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x20, 0x00, 0xE0, 0x3F, 0x38, 0x00, 0xE0,
+ 0x3F, 0x1C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x78,
+ 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x80, 0x07, 0x0C, 0x00, 0xC0, 0x01, 0x0E, 0x00, 0xE0, 0x80, 0x0B,
+ 0x00, 0x60, 0xC0, 0x08, 0x00, 0x00, 0xE0, 0x3F, 0x00, 0x00, 0xE0, 0x3F, 0x00, 0x00, 0x00, 0x08, // 188
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x20, 0x00, 0xE0, 0x3F, 0x30, 0x00, 0xE0,
+ 0x3F, 0x1C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x4E, 0x20, 0x00, 0x00, 0x67, 0x30, 0x00, 0xC0, 0x21, 0x38, 0x00, 0xE0, 0x20, 0x2C,
+ 0x00, 0x60, 0x20, 0x26, 0x00, 0x00, 0xE0, 0x27, 0x00, 0x00, 0xC0, 0x21, // 189
+ 0x40, 0x10, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x20, 0x22, 0x20, 0x00, 0x20, 0x22, 0x30, 0x00, 0xE0,
+ 0x3D, 0x38, 0x00, 0xC0, 0x1D, 0x0E, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0E, 0x0C, 0x00, 0x00, 0x07, 0x0E, 0x00, 0x80, 0x83, 0x0B, 0x00, 0xE0, 0xC0, 0x09,
+ 0x00, 0x60, 0xE0, 0x3F, 0x00, 0x20, 0xE0, 0x3F, 0x00, 0x00, 0x00, 0x08, // 190
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xF0, 0x01, 0x00, 0x00, 0xF8, 0x03, 0x00, 0x00, 0x0C, 0x03, 0x00, 0xF6, 0x07, 0x03, 0x00, 0xF6, 0x07, 0x03, 0x00, 0x00,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x90, 0x03, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0xE0, // 191
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x80,
+ 0x8F, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x80, 0x8F, 0x01, 0x00, 0x00, 0xFE,
+ 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 192
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30,
+ 0x30, 0x00, 0x60, 0x60, 0x38, 0x00, 0x60, 0xE0, 0x1F, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x80, 0x07, // 193
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30,
+ 0x30, 0x00, 0xC0, 0x78, 0x30, 0x00, 0xC0, 0xFF, 0x18, 0x00, 0x80, 0xC7, 0x1F, 0x00, 0x00, 0x80, 0x07, // 194
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00,
+ 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 195
+ 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x3E, 0x00, 0x80, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x33, 0x00, 0xE0,
+ 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00,
+ 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xF0, 0x01, // 196
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30,
+ 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 197
+ 0x60, 0x00, 0x20, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x3C, 0x00, 0xE0, 0x00, 0x1F, 0x00, 0xC0, 0x87, 0x07, 0x00, 0x00,
+ 0xCF, 0x03, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xE0, 0xFF,
+ 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xFC, 0x00,
+ 0x00, 0x00, 0xCF, 0x03, 0x00, 0xC0, 0x87, 0x07, 0x00, 0xE0, 0x01, 0x1F, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x60, 0x00, 0x38, 0x00,
+ 0x60, 0x00, 0x20, // 198
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x00, 0x80, 0x03, 0x0E, 0x00, 0xC0, 0x03, 0x1C, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x60,
+ 0x00, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x78, 0x30, 0x00, 0xE0, 0x7C,
+ 0x38, 0x00, 0xC0, 0xEF, 0x1F, 0x00, 0x00, 0xC7, 0x0F, // 199
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x00, 0x0F, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x1E,
+ 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 200
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x06,
+ 0x00, 0x0F, 0x00, 0x0C, 0x80, 0x03, 0x00, 0x08, 0xE0, 0x01, 0x00, 0x08, 0x70, 0x00, 0x00, 0x08, 0x3C, 0x00, 0x00, 0x0C, 0x0E,
+ 0x00, 0x00, 0x86, 0x07, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 201
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0xCF, 0x03, 0x00, 0xC0, 0x87, 0x07, 0x00, 0xE0, 0x00,
+ 0x1F, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x20, // 202
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xC0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0xE0,
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00,
+ 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 203
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xC0,
+ 0x0F, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F,
+ 0x00, 0xE0, 0xFF, 0x3F, // 204
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30,
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 205
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0,
+ 0x00, 0x18, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00,
+ 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F,
+ 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 206
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00,
+ 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 207
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60,
+ 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60,
+ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x00, 0x0F, // 208
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0E, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0,
+ 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00,
+ 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0F, 0x00, 0x00, 0x02,
+ 0x03, // 209
+ 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00,
+ 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 210
+ 0x20, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x30, 0x00, 0x80, 0x07, 0x30, 0x00, 0x00, 0x1E, 0x30, 0x00, 0x00,
+ 0x78, 0x30, 0x00, 0x00, 0xE0, 0x38, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x7C,
+ 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x20, // 211
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0x8F, 0x03, 0x00, 0x00, 0x03, 0x07, 0x00, 0x80,
+ 0x01, 0x06, 0x00, 0x80, 0x01, 0x0C, 0x00, 0x80, 0x01, 0x0C, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x80, 0x01,
+ 0x0C, 0x00, 0x80, 0x01, 0x0C, 0x00, 0x80, 0x01, 0x06, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x87, 0x03, 0x00, 0x00, 0xFE, 0x01,
+ 0x00, 0x00, 0xF8, // 212
+ 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x30, 0x00, 0x60, 0x00, 0x38, 0x00, 0xE0, 0x01, 0x1E, 0x00, 0xC0, 0x83, 0x07, 0x00, 0x00,
+ 0xCF, 0x03, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x80, 0xCF, 0x03, 0x00, 0xC0, 0x83,
+ 0x07, 0x00, 0xE0, 0x01, 0x1E, 0x00, 0x60, 0x00, 0x38, 0x00, 0x20, 0x00, 0x30, 0x00, 0x20, 0x00, 0x20, // 213
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0x00, 0xF0, // 214
+ 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0xE0, 0x3F, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00,
+ 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0,
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 215
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xE0, 0xFF,
+ 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30,
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 216
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xE0, 0xFF,
+ 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30,
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0xF0, 0x01,
+ 0x00, 0x00, 0xF0, 0x01, // 217
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0,
+ 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30,
+ 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x70, 0x38, 0x00, 0x00, 0xE0, 0x1C,
+ 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x80, 0x07, // 218
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00,
+ 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30,
+ 0x30, 0x00, 0x00, 0x70, 0x38, 0x00, 0x00, 0xE0, 0x1C, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 219
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00,
+ 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30,
+ 0x30, 0x00, 0x00, 0x70, 0x38, 0x00, 0x00, 0xE0, 0x1C, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x80, 0x07, // 220
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x80, 0x03, 0x0E, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x60,
+ 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30,
+ 0x30, 0x00, 0xC0, 0x30, 0x18, 0x00, 0xC0, 0x30, 0x1C, 0x00, 0x80, 0x33, 0x0E, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC,
+ 0x03, // 221
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0x03,
+ 0x1E, 0x00, 0xE0, 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30,
+ 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x38, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F, 0x00,
+ 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 222
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x30, 0x00, 0x80, 0x0F, 0x38, 0x00, 0xC0, 0x1D, 0x1E, 0x00, 0xE0,
+ 0xB8, 0x0F, 0x00, 0x60, 0xF0, 0x03, 0x00, 0x60, 0xF0, 0x01, 0x00, 0x60, 0x70, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30,
+ 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 223
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x00, 0x86, 0x31, 0x00, 0x00,
+ 0x86, 0x31, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x10, 0x00, 0x00, 0xCE, 0x18, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8,
+ 0x3F, 0x00, 0x00, 0x00, 0x20, // 224
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0x19, 0x1E, 0x00, 0xE0, 0x0C, 0x38, 0x00, 0x60,
+ 0x0C, 0x30, 0x00, 0x60, 0x04, 0x30, 0x00, 0x60, 0x04, 0x30, 0x00, 0x60, 0x0C, 0x30, 0x00, 0x60, 0x0C, 0x38, 0x00, 0x60, 0x78,
+ 0x1E, 0x00, 0x70, 0xF0, 0x0F, 0x00, 0x10, 0xC0, 0x03, // 225
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x84, 0x31, 0x00, 0x00,
+ 0x84, 0x31, 0x00, 0x00, 0x84, 0x31, 0x00, 0x00, 0x84, 0x31, 0x00, 0x00, 0xCC, 0x31, 0x00, 0x00, 0xFC, 0x31, 0x00, 0x00, 0x78,
+ 0x1B, 0x00, 0x00, 0x00, 0x0E, // 226
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, // 227
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x30, 0x00, 0x00,
+ 0x04, 0x30, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0xF0, // 228
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xCC, 0x1C, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00,
+ 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xDC, 0x18, 0x00, 0x00, 0xF8,
+ 0x0C, 0x00, 0x00, 0xF0, 0x04, // 229
+ 0x00, 0x04, 0x20, 0x00, 0x00, 0x04, 0x38, 0x00, 0x00, 0x0C, 0x3C, 0x00, 0x00, 0x38, 0x0F, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00,
+ 0xC0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xC0,
+ 0x01, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0x38, 0x0F, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x04, 0x38, 0x00, 0x00, 0x04,
+ 0x20, // 230
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x1C, 0x00, 0x00, 0x1C, 0x3C, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x86, 0x20, 0x00, 0x00,
+ 0x86, 0x20, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xFC, 0x31, 0x00, 0x00, 0x7C, 0x3F, 0x00, 0x00, 0x30, 0x1F, // 231
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0x07, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC, 0x3F, // 232
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x60, 0xFC, 0x3F, 0x00, 0xC0, 0x00, 0x3C, 0x00, 0x80,
+ 0x00, 0x0F, 0x00, 0x80, 0xC0, 0x03, 0x00, 0x80, 0xE0, 0x01, 0x00, 0xC0, 0x70, 0x00, 0x00, 0x60, 0x18, 0x00, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC, 0x3F, // 233
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00,
+ 0xC0, 0x01, 0x00, 0x00, 0x70, 0x07, 0x00, 0x00, 0x3C, 0x1E, 0x00, 0x00, 0x0C, 0x3C, 0x00, 0x00, 0x04, 0x30, // 234
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC, 0x3F, // 235
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00,
+ 0x78, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xE0,
+ 0x03, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, // 236
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC, 0x3F, // 237
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00,
+ 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8,
+ 0x0F, 0x00, 0x00, 0xF0, 0x07, // 238
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, // 239
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0x18, 0x0C, 0x00, 0x00,
+ 0x0C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C,
+ 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 240
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00,
+ 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x18, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x18,
+ 0x0C, // 241
+ 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00,
+ 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, // 242
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x00,
+ 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0xF8, 0x03, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00,
+ 0x06, // 243
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00,
+ 0x06, 0x30, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x0C, 0x18, 0x00, 0x80, 0xFF, 0xFF, 0x01, 0x80, 0xFF,
+ 0xFF, 0x01, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0C, 0x30,
+ 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x07, // 244
+ 0x00, 0x02, 0x20, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x38, 0x0E, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00,
+ 0xC0, 0x01, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x38, 0x0E, 0x00, 0x00, 0x1C, 0x3C, 0x00, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x02,
+ 0x20, // 245
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xF0, // 246
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, // 247
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30,
+ 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, // 248
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30,
+ 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xF0, // 249
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00,
+ 0xFC, 0x3F, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0,
+ 0x30, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x0F, // 250
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00,
+ 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x31, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, 0x80,
+ 0x1F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, // 251
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00,
+ 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, 0x80, 0x3B, 0x00, 0x00, 0x00,
+ 0x1F, 0x00, 0x00, 0x00, 0x0E, // 252
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x1C, 0x0E, 0x00, 0x00, 0x0C, 0x1C, 0x00, 0x00, 0x06, 0x38, 0x00, 0x00,
+ 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xCC, 0x38, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF0,
+ 0x07, // 253
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x3C, 0x1E, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x06,
+ 0x30, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x1C, 0x3C, 0x00, 0x00, 0xF8, 0x1F,
+ 0x00, 0x00, 0xE0, 0x07, // 254
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x30, 0x00, 0x00, 0xFC, 0x38, 0x00, 0x00, 0xCC, 0x1D, 0x00, 0x00, 0x8C, 0x0F, 0x00, 0x00,
+ 0x84, 0x03, 0x00, 0x00, 0x84, 0x01, 0x00, 0x00, 0x84, 0x01, 0x00, 0x00, 0x84, 0x01, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, // 255
+};
+
+#endif // OLED_RU
\ No newline at end of file
diff --git a/src/graphics/fonts/OLEDDisplayFontsRU.h b/src/graphics/fonts/OLEDDisplayFontsRU.h
index 7510dcdfc..0437517dd 100644
--- a/src/graphics/fonts/OLEDDisplayFontsRU.h
+++ b/src/graphics/fonts/OLEDDisplayFontsRU.h
@@ -8,4 +8,6 @@
#endif
extern const uint8_t ArialMT_Plain_10_RU[] PROGMEM;
+extern const uint8_t ArialMT_Plain_16_RU[] PROGMEM;
+extern const uint8_t ArialMT_Plain_24_RU[] PROGMEM;
#endif
\ No newline at end of file
diff --git a/src/graphics/fonts/OLEDDisplayFontsUA.cpp b/src/graphics/fonts/OLEDDisplayFontsUA.cpp
index 2a97526ef..8bc56ea94 100644
--- a/src/graphics/fonts/OLEDDisplayFontsUA.cpp
+++ b/src/graphics/fonts/OLEDDisplayFontsUA.cpp
@@ -1,3 +1,5 @@
+#ifdef OLED_UA
+
#include "OLEDDisplayFontsUA.h"
// Font generated or edited with the glyphEditor
@@ -1920,4 +1922,6 @@ const uint8_t ArialMT_Plain_24_UA[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xC1, 0x00, 0x00, 0xF0, 0xE3, 0x00, 0x00, 0x38, 0x7B, 0x00, 0x00, 0x18, 0x1A, 0x00, 0x00,
0x18, 0x0E, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x00, 0xF8,
0xFF, // 1103
-};
\ No newline at end of file
+};
+
+#endif // OLED_UA
\ No newline at end of file
diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp
index 233bbefe0..32882f7ae 100644
--- a/src/input/ButtonThread.cpp
+++ b/src/input/ButtonThread.cpp
@@ -92,8 +92,11 @@ bool ButtonThread::initButton(const ButtonConfig &config)
if (config.shortLong != INPUT_BROKER_NONE) {
_shortLong = config.shortLong;
}
-
+#ifdef USE_EINK
+ userButton.setDebounceMs(0);
+#else
userButton.setDebounceMs(1);
+#endif
userButton.setPressMs(_longPressTime);
if (screen) {
@@ -137,8 +140,7 @@ int32_t ButtonThread::runOnce()
}
// Progressive lead-up sound system
- if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS &&
- (millis() - buttonPressStartTime) < _longLongPressTime) {
+ if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
// Start the progressive sequence if not already active
if (!leadUpSequenceActive) {
@@ -150,13 +152,14 @@ int32_t ButtonThread::runOnce()
else if ((millis() - lastLeadUpNoteTime) >= 400) { // 400ms interval between notes
if (playNextLeadUpNote()) {
lastLeadUpNoteTime = millis();
+ } else {
+ leadUpPlayed = true;
}
}
}
// Reset when button is released
if (!buttonCurrentlyPressed && buttonWasPressed) {
- leadUpPlayed = false;
leadUpSequenceActive = false;
resetLeadUpSequence();
}
@@ -253,12 +256,13 @@ int32_t ButtonThread::runOnce()
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
- (millis() - buttonPressStartTime) >= _longLongPressTime) {
+ (millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
evt.inputEvent = _longLongPress;
this->notifyObservers(&evt);
}
// Reset combination tracking
waitingForLongPress = false;
+ leadUpPlayed = false;
break;
}
diff --git a/src/input/ButtonThread.h b/src/input/ButtonThread.h
index bbc8da2a7..c6d6557e2 100644
--- a/src/input/ButtonThread.h
+++ b/src/input/ButtonThread.h
@@ -92,7 +92,7 @@ class ButtonThread : public Observable, public concurrency::
voidFuncPtr _intRoutine = nullptr;
uint16_t _longPressTime = 500;
- uint16_t _longLongPressTime = 5000;
+ uint16_t _longLongPressTime = 3900;
int _pinNum = 0;
bool _activeLow = true;
bool _touchQuirk = false;
diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h
index 4487fa662..012a403f5 100644
--- a/src/input/InputBroker.h
+++ b/src/input/InputBroker.h
@@ -4,6 +4,7 @@
enum input_broker_event {
INPUT_BROKER_NONE = 0,
INPUT_BROKER_SELECT = 10,
+ INPUT_BROKER_SELECT_LONG,
INPUT_BROKER_UP = 17,
INPUT_BROKER_DOWN = 18,
INPUT_BROKER_LEFT = 19,
diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp
index d41ad2fd6..4c8ce6409 100644
--- a/src/input/TrackballInterruptBase.cpp
+++ b/src/input/TrackballInterruptBase.cpp
@@ -1,12 +1,14 @@
#include "TrackballInterruptBase.h"
#include "configuration.h"
+extern bool osk_found;
TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {}
void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress,
input_broker_event eventDown, input_broker_event eventUp, input_broker_event eventLeft,
- input_broker_event eventRight, input_broker_event eventPressed, void (*onIntDown)(),
- void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)())
+ input_broker_event eventRight, input_broker_event eventPressed,
+ input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(),
+ void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)())
{
this->_pinDown = pinDown;
this->_pinUp = pinUp;
@@ -18,6 +20,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
this->_eventLeft = eventLeft;
this->_eventRight = eventRight;
this->_eventPressed = eventPressed;
+ this->_eventPressedLong = eventPressedLong;
if (pinPress != 255) {
pinMode(pinPress, INPUT_PULLUP);
@@ -40,9 +43,9 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
attachInterrupt(this->_pinRight, onIntRight, TB_DIRECTION);
}
- LOG_DEBUG("Trackball GPIO initialized (%d, %d, %d, %d, %d)", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight,
- pinPress);
-
+ LOG_DEBUG("Trackball GPIO initialized - UP:%d DOWN:%d LEFT:%d RIGHT:%d PRESS:%d", this->_pinUp, this->_pinDown,
+ this->_pinLeft, this->_pinRight, pinPress);
+ osk_found = true;
this->setInterval(100);
}
@@ -50,10 +53,47 @@ int32_t TrackballInterruptBase::runOnce()
{
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
+
+ // Handle long press detection for press button
+ if (pressDetected && pressStartTime > 0) {
+ uint32_t pressDuration = millis() - pressStartTime;
+ bool buttonStillPressed = false;
+
+#if defined(T_DECK)
+ buttonStillPressed = (this->action == TB_ACTION_PRESSED);
+#else
+ buttonStillPressed = !digitalRead(_pinPress);
+#endif
+
+ if (!buttonStillPressed) {
+ // Button released
+ if (pressDuration < LONG_PRESS_DURATION) {
+ // Short press
+ e.inputEvent = this->_eventPressed;
+ }
+ // Reset state
+ pressDetected = false;
+ pressStartTime = 0;
+ lastLongPressEventTime = 0;
+ this->action = TB_ACTION_NONE;
+ } else if (pressDuration >= LONG_PRESS_DURATION) {
+ // Long press detected
+ uint32_t currentTime = millis();
+ // Only trigger long press event if enough time has passed since the last one
+ if (lastLongPressEventTime == 0 || (currentTime - lastLongPressEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
+ e.inputEvent = this->_eventPressedLong;
+ lastLongPressEventTime = currentTime;
+ }
+ this->action = TB_ACTION_PRESSED_LONG;
+ }
+ }
+
#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
- if (this->action == TB_ACTION_PRESSED) {
- // LOG_DEBUG("Trackball event Press");
- e.inputEvent = this->_eventPressed;
+ if (this->action == TB_ACTION_PRESSED && !pressDetected) {
+ // Start long press detection
+ 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 && lastEvent == TB_ACTION_UP) {
// LOG_DEBUG("Trackball event UP");
e.inputEvent = this->_eventUp;
@@ -68,9 +108,11 @@ int32_t TrackballInterruptBase::runOnce()
e.inputEvent = this->_eventRight;
}
#else
- if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress)) {
- // LOG_DEBUG("Trackball event Press");
- e.inputEvent = this->_eventPressed;
+ if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress) && !pressDetected) {
+ // Start long press detection
+ 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");
e.inputEvent = this->_eventUp;
@@ -91,10 +133,16 @@ int32_t TrackballInterruptBase::runOnce()
e.kbchar = 0x00;
this->notifyObservers(&e);
}
- lastEvent = action;
- this->action = TB_ACTION_NONE;
- return 100;
+ // Only update lastEvent for non-press actions or completed press actions
+ if (this->action != TB_ACTION_PRESSED || !pressDetected) {
+ lastEvent = action;
+ if (!pressDetected) {
+ this->action = TB_ACTION_NONE;
+ }
+ }
+
+ return 50; // Check more frequently for better long press detection
}
void TrackballInterruptBase::intPressHandler()
diff --git a/src/input/TrackballInterruptBase.h b/src/input/TrackballInterruptBase.h
index 92db8720e..38be22f20 100644
--- a/src/input/TrackballInterruptBase.h
+++ b/src/input/TrackballInterruptBase.h
@@ -18,8 +18,8 @@ class TrackballInterruptBase : public Observable, public con
explicit TrackballInterruptBase(const char *name);
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress, input_broker_event eventDown,
input_broker_event eventUp, input_broker_event eventLeft, input_broker_event eventRight,
- input_broker_event eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(),
- void (*onIntPress)());
+ input_broker_event eventPressed, input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(),
+ void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)());
void intPressHandler();
void intDownHandler();
void intUpHandler();
@@ -33,6 +33,7 @@ class TrackballInterruptBase : public Observable, public con
enum TrackballInterruptBaseActionType {
TB_ACTION_NONE,
TB_ACTION_PRESSED,
+ TB_ACTION_PRESSED_LONG,
TB_ACTION_UP,
TB_ACTION_DOWN,
TB_ACTION_LEFT,
@@ -46,12 +47,20 @@ class TrackballInterruptBase : public Observable, public con
volatile TrackballInterruptBaseActionType action = TB_ACTION_NONE;
+ // Long press detection for press button
+ uint32_t pressStartTime = 0;
+ bool pressDetected = false;
+ uint32_t lastLongPressEventTime = 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
+
private:
input_broker_event _eventDown = INPUT_BROKER_NONE;
input_broker_event _eventUp = INPUT_BROKER_NONE;
input_broker_event _eventLeft = INPUT_BROKER_NONE;
input_broker_event _eventRight = INPUT_BROKER_NONE;
input_broker_event _eventPressed = INPUT_BROKER_NONE;
+ input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
const char *_originName;
TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE;
};
diff --git a/src/input/TrackballInterruptImpl1.cpp b/src/input/TrackballInterruptImpl1.cpp
index 896238f38..594facdeb 100644
--- a/src/input/TrackballInterruptImpl1.cpp
+++ b/src/input/TrackballInterruptImpl1.cpp
@@ -13,11 +13,12 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe
input_broker_event eventLeft = INPUT_BROKER_LEFT;
input_broker_event eventRight = INPUT_BROKER_RIGHT;
input_broker_event eventPressed = INPUT_BROKER_SELECT;
+ input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
TrackballInterruptBase::init(pinDown, pinUp, pinLeft, pinRight, pinPress, eventDown, eventUp, eventLeft, eventRight,
- eventPressed, TrackballInterruptImpl1::handleIntDown, TrackballInterruptImpl1::handleIntUp,
- TrackballInterruptImpl1::handleIntLeft, TrackballInterruptImpl1::handleIntRight,
- TrackballInterruptImpl1::handleIntPressed);
+ eventPressed, eventPressedLong, TrackballInterruptImpl1::handleIntDown,
+ TrackballInterruptImpl1::handleIntUp, TrackballInterruptImpl1::handleIntLeft,
+ TrackballInterruptImpl1::handleIntRight, TrackballInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
}
diff --git a/src/main.cpp b/src/main.cpp
index 9e46021c9..23ffa6b6d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -191,6 +191,8 @@ ScanI2C::DeviceAddress cardkb_found = ScanI2C::ADDRESS_NONE;
uint8_t kb_model;
// global bool to record that a kb is present
bool kb_found = false;
+// global bool to record that on-screen keyboard (OSK) is present
+bool osk_found = false;
// The I2C address of the RTC Module (if found)
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
@@ -304,7 +306,6 @@ void setup()
Wire.begin(48, 47);
io.pinMode(PCA_PIN_EINK_EN, OUTPUT);
io.pinMode(PCA_PIN_POWER_EN, OUTPUT);
- io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
io.digitalWrite(PCA_PIN_POWER_EN, HIGH);
// io.pinMode(C2_PIN, OUTPUT);
#endif
@@ -326,8 +327,12 @@ void setup()
#ifdef BLE_LED
pinMode(BLE_LED, OUTPUT);
+#ifdef BLE_LED_INVERTED
+ digitalWrite(BLE_LED, HIGH);
+#else
digitalWrite(BLE_LED, LOW);
#endif
+#endif
#if defined(T_DECK)
// GPIO10 manages all peripheral power supplies
@@ -1409,6 +1414,10 @@ void setup()
#endif
#endif
+#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2)
+ osk_found = true;
+#endif
+
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
// Start web server thread.
webServerThread = new WebServerThread();
@@ -1559,7 +1568,13 @@ void loop()
#endif
service->loop();
-
+#if defined(LGFX_SDL)
+ if (screen) {
+ auto dispdev = screen->getDisplayDevice();
+ if (dispdev)
+ static_cast(dispdev)->sdlLoop();
+ }
+#endif
long delayMsec = mainController.runOrDelay();
// We want to sleep as long as possible here - because it saves power
diff --git a/src/main.h b/src/main.h
index 3568daad2..2ddd4862f 100644
--- a/src/main.h
+++ b/src/main.h
@@ -32,6 +32,7 @@ extern ScanI2C::DeviceAddress screen_found;
extern ScanI2C::DeviceAddress cardkb_found;
extern uint8_t kb_model;
extern bool kb_found;
+extern bool osk_found;
extern ScanI2C::DeviceAddress rtc_found;
extern ScanI2C::DeviceAddress accelerometer_found;
extern ScanI2C::FoundDevice rgb_found;
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 142ada806..dbd458b61 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -47,8 +47,10 @@ void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
{
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
- config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
+ config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE &&
+ p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
+ // But only LoRa packets should be able to trigger this.
if (Router::cancelSending(p->from, p->id))
txRelayCanceled++;
}
diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp
index a20db808e..a0d992c42 100644
--- a/src/mesh/LR11x0Interface.cpp
+++ b/src/mesh/LR11x0Interface.cpp
@@ -6,6 +6,10 @@
#include "mesh/NodeDB.h"
#ifdef LR11X0_DIO_AS_RF_SWITCH
#include "rfswitch.h"
+#elif ARCH_PORTDUINO
+#include "PortduinoGlue.h"
+#define rfswitch_dio_pins portduino_config.rfswitch_dio_pins
+#define rfswitch_table portduino_config.rfswitch_table
#else
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
@@ -14,10 +18,6 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
};
#endif
-#ifdef ARCH_PORTDUINO
-#include "PortduinoGlue.h"
-#endif
-
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
#if ARCH_PORTDUINO
@@ -117,17 +117,14 @@ template bool LR11x0Interface::init()
#ifdef LR11X0_DIO_AS_RF_SWITCH
bool dioAsRfSwitch = true;
#elif defined(ARCH_PORTDUINO)
- bool dioAsRfSwitch = false;
- if (settingsMap[dio2_as_rf_switch]) {
- dioAsRfSwitch = true;
- }
+ bool dioAsRfSwitch = portduino_config.has_rfswitch_table;
#else
bool dioAsRfSwitch = false;
#endif
if (dioAsRfSwitch) {
lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
- LOG_DEBUG("Set DIO RF switch", res);
+ LOG_DEBUG("Set DIO RF switch");
}
if (res == RADIOLIB_ERR_NONE) {
diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp
index c5748a560..409c52179 100644
--- a/src/mesh/MeshModule.cpp
+++ b/src/mesh/MeshModule.cpp
@@ -85,8 +85,11 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e
return r;
}
-void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
+void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char *specificModule)
{
+ if (specificModule) {
+ LOG_DEBUG("Calling specific module: %s", specificModule);
+ }
// LOG_DEBUG("In call modules");
bool moduleFound = false;
@@ -104,6 +107,11 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
for (auto i = modules->begin(); i != modules->end(); ++i) {
auto &pi = **i;
+ // If specificModule is provided, only call that specific module
+ if (specificModule && (!pi.name || strcmp(pi.name, specificModule) != 0)) {
+ continue;
+ }
+
pi.currentRequest = ∓
/// We only call modules that are interested in the packet (and the message is destined to us or we are promiscious)
diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h
index eda3f8881..bf735439f 100644
--- a/src/mesh/MeshModule.h
+++ b/src/mesh/MeshModule.h
@@ -73,7 +73,7 @@ class MeshModule
/** For use only by MeshService
*/
- static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO);
+ static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO, const char *specificModule = nullptr);
static std::vector GetMeshModulesWithUIFrames(int startIndex);
static void observeUIEvents(Observer *observer);
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index b54cdae86..18014eb02 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -225,7 +225,11 @@ NodeDB::NodeDB()
memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end));
myNodeInfo.device_id.size = 16;
// Uncomment below to print the device id
-
+#elif ARCH_PORTDUINO
+ if (portduino_config.has_device_id) {
+ memcpy(myNodeInfo.device_id.bytes, portduino_config.device_id, 16);
+ myNodeInfo.device_id.size = 16;
+ }
#else
// FIXME - implement for other platforms
#endif
@@ -1631,24 +1635,33 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
printBytes("Incoming Pubkey: ", p.public_key.bytes, 32);
// Alert the user if a remote node is advertising public key that matches our own
- if (owner.public_key.size == 32 && memcmp(p.public_key.bytes, owner.public_key.bytes, 32) == 0 && !duplicateWarned) {
- duplicateWarned = true;
- char warning[] = "Remote device %s has advertised your public key. This may indicate a compromised key. You may need "
- "to regenerate your public keys.";
- LOG_WARN(warning, p.long_name);
- meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
- cn->level = meshtastic_LogRecord_Level_WARNING;
- cn->time = getValidTime(RTCQualityFromNet);
- sprintf(cn->message, warning, p.long_name);
- service->sendClientNotification(cn);
+ if (owner.public_key.size == 32 && memcmp(p.public_key.bytes, owner.public_key.bytes, 32) == 0) {
+ if (!duplicateWarned) {
+ duplicateWarned = true;
+ char warning[] =
+ "Remote device %s has advertised your public key. This may indicate a compromised key. You may need "
+ "to regenerate your public keys.";
+ LOG_WARN(warning, p.long_name);
+ meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
+ cn->level = meshtastic_LogRecord_Level_WARNING;
+ cn->time = getValidTime(RTCQualityFromNet);
+ sprintf(cn->message, warning, p.long_name);
+ service->sendClientNotification(cn);
+ }
+ return false;
}
}
- if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one
+ if (info->user.public_key.size == 32) { // if we have a key for this user already, don't overwrite with a new one
+ // if the key doesn't match, don't update nodeDB at all.
+ if (p.public_key.size != 32 || (memcmp(p.public_key.bytes, info->user.public_key.bytes, 32) != 0)) {
+ LOG_WARN("Public Key mismatch, dropping NodeInfo");
+ return false;
+ }
LOG_INFO("Public Key set for node, not updating!");
// we copy the key into the incoming packet, to prevent overwrite
p.public_key.size = 32;
memcpy(p.public_key.bytes, info->user.public_key.bytes, 32);
- } else if (p.public_key.size > 0) {
+ } else if (p.public_key.size == 32) {
LOG_INFO("Update Node Pubkey!");
}
#endif
@@ -1867,7 +1880,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
uint8_t keyHash[32] = {0};
memcpy(keyHash, keyToTest.bytes, keyToTest.size);
crypto->hash(keyHash, 32);
- for (int i = 0; i < sizeof(LOW_ENTROPY_HASHES) / sizeof(LOW_ENTROPY_HASHES[0]); i++) {
+ for (uint16_t i = 0; i < sizeof(LOW_ENTROPY_HASHES) / sizeof(LOW_ENTROPY_HASHES[0]); i++) {
if (memcmp(keyHash, LOW_ENTROPY_HASHES[i], sizeof(LOW_ENTROPY_HASHES[0])) == 0) {
return true;
}
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 305689fff..a3a8a2087 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -192,12 +192,6 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
size_t PhoneAPI::getFromRadio(uint8_t *buf)
{
- if (!available()) {
- return 0;
- }
- // In case we send a FromRadio packet
- memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
-
// Respond to heartbeat by sending queue status
if (heartbeatReceived) {
memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
@@ -209,6 +203,12 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
return numbytes;
}
+ if (!available()) {
+ return 0;
+ }
+ // In case we send a FromRadio packet
+ memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
+
// Advance states as needed
switch (state) {
case STATE_SEND_NOTHING:
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 7590ac34d..c210d5d48 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -170,11 +170,10 @@ const RegionInfo regions[] = {
*/
RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 10, true, false, false), RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 30, true, false, true),
-
/*
Nepal
- 865β―MHz to 868β―MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use, specifically in non-cellular mode.
- https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf
+ 865β―MHz to 868β―MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use,
+ specifically in non-cellular mode. https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf
*/
RDEF(NP_865, 865.0f, 868.0f, 100, 0, 30, true, false, false),
@@ -336,8 +335,9 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
{
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
- std::string out = DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
- p->from, p->to, p->want_ack, p->hop_limit, p->channel);
+ std::string out =
+ DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
+ p->from, p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->channel);
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
auto &s = p->decoded;
@@ -666,8 +666,10 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
void RadioInterface::deliverToReceiver(meshtastic_MeshPacket *p)
{
- if (router)
+ if (router) {
+ p->transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;
router->enqueueReceivedMessage(p);
+ }
}
/***
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 48205cc0f..cceacfe9e 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -523,8 +523,10 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
// is not in the local nodedb
// First, only PKC encrypt packets we are originating
if (isFromUs(p) &&
- // Don't use PKC with simulator
- radioType != SIM_RADIO &&
+#if ARCH_PORTDUINO
+ // Sim radio via the cli flag skips PKC
+ !portduino_config.force_simradio &&
+#endif
// Don't use PKC with Ham mode
!owner.is_licensed &&
// Don't use PKC if it's not explicitly requested and a non-primary channel is requested
@@ -652,7 +654,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
}
// call modules here
- if (!skipHandle) {
+ // If this could be a spoofed packet, don't let the modules see it.
+ if (!skipHandle && p->from != nodeDB->getNodeNum()) {
MeshModule::callModules(*p, src);
#if !MESHTASTIC_EXCLUDE_MQTT
@@ -666,6 +669,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
!isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel);
#endif
+ } else if (p->from == nodeDB->getNodeNum() && !skipHandle) {
+ MeshModule::callModules(*p, src, ROUTING_MODULE);
}
packetPool.release(p_encrypted); // Release the encrypted packet
diff --git a/src/mesh/api/PacketAPI.cpp b/src/mesh/api/PacketAPI.cpp
index 1d6df855f..ab380d696 100644
--- a/src/mesh/api/PacketAPI.cpp
+++ b/src/mesh/api/PacketAPI.cpp
@@ -59,6 +59,7 @@ bool PacketAPI::receivePacket(void)
switch (mr->which_payload_variant) {
case meshtastic_ToRadio_packet_tag: {
meshtastic_MeshPacket *mp = &mr->packet;
+ mp->transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_API;
printPacket("PACKET FROM QUEUE", mp);
service->handleToRadio(*mp);
break;
diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h
index d4e0eaa8c..9650668a8 100644
--- a/src/mesh/udp/UdpMulticastHandler.h
+++ b/src/mesh/udp/UdpMulticastHandler.h
@@ -50,6 +50,7 @@ class UdpMulticastHandler final
LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength);
#endif
meshtastic_MeshPacket mp;
+ mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength);
bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp);
if (isPacketDecoded && router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
@@ -78,6 +79,9 @@ class UdpMulticastHandler final
return false;
}
#endif
+ if (mp->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP) {
+ LOG_ERROR("Attempt to send UDP sourced packet over UDP");
+ }
LOG_DEBUG("Broadcasting packet over UDP (id=%u)", mp->id);
uint8_t buffer[meshtastic_MeshPacket_size];
size_t encodedLength = pb_encode_to_bytes(buffer, sizeof(buffer), &meshtastic_MeshPacket_msg, mp);
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index b6cb1b0e3..76b950322 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -13,12 +13,16 @@
#include "detect/ScanI2C.h"
#include "graphics/Screen.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/draw/NotificationRenderer.h"
#include "graphics/emotes.h"
#include "graphics/images.h"
#include "main.h" // for cardkb_found
#include "mesh/generated/meshtastic/cannedmessages.pb.h"
#include "modules/AdminModule.h"
#include "modules/ExternalNotificationModule.h" // for buzzer control
+#if HAS_TRACKBALL
+#include "input/TrackballInterruptImpl1.h"
+#endif
#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
#endif
@@ -38,6 +42,7 @@
extern ScanI2C::DeviceAddress cardkb_found;
extern bool graphics::isMuted;
+extern bool osk_found;
static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto";
static NodeNum lastDest = NODENUM_BROADCAST;
@@ -78,16 +83,15 @@ void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChan
lastDestSet = true;
// Rest of function unchanged...
- // Always select the first real canned message on activation
- int firstRealMsgIdx = 0;
+ // Upon activation, highlight "[Select Destination]"
+ int selectDestination = 0;
for (int i = 0; i < messagesCount; ++i) {
- if (strcmp(messages[i], "[Select Destination]") != 0 && strcmp(messages[i], "[Exit]") != 0 &&
- strcmp(messages[i], "[---- Free Text ----]") != 0) {
- firstRealMsgIdx = i;
+ if (strcmp(messages[i], "[Select Destination]") == 0) {
+ selectDestination = i;
break;
}
}
- currentMessageIndex = firstRealMsgIdx;
+ currentMessageIndex = selectDestination;
// This triggers the canned message list
runState = CANNED_MESSAGE_RUN_STATE_ACTIVE;
@@ -152,10 +156,13 @@ int CannedMessageModule::splitConfiguredMessages()
int tempCount = 0;
// Insert at position 0 (top)
tempMessages[tempCount++] = "[Select Destination]";
-
#if defined(USE_VIRTUAL_KEYBOARD)
- // Add a "Free Text" entry at the top if using a keyboard
+ // Add a "Free Text" entry at the top if using a touch screen virtual keyboard
tempMessages[tempCount++] = "[-- Free Text --]";
+#else
+ if (osk_found && screen) {
+ tempMessages[tempCount++] = "[-- Free Text --]";
+ }
#endif
// First message always starts at buffer start
@@ -342,6 +349,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
case CANNED_MESSAGE_RUN_STATE_FREETEXT:
return handleFreeTextInput(event); // All allowed input for this state
+ // Virtual keyboard mode: Show virtual keyboard and handle input
+
// If sending, block all input except global/system (handled above)
case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE:
return 1;
@@ -628,6 +637,56 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo
notifyObservers(&e);
return true;
}
+#else
+ if (strcmp(current, "[-- Free Text --]") == 0) {
+ if (osk_found && screen) {
+ char headerBuffer[64];
+ if (this->dest == NODENUM_BROADCAST) {
+ snprintf(headerBuffer, sizeof(headerBuffer), "To: Broadcast@%s", channels.getName(this->channel));
+ } else {
+ snprintf(headerBuffer, sizeof(headerBuffer), "To: %s", getNodeName(this->dest));
+ }
+ screen->showTextInput(headerBuffer, "", 300000, [this](const std::string &text) {
+ if (!text.empty()) {
+ this->freetext = text.c_str();
+ this->payload = CANNED_MESSAGE_RUN_STATE_FREETEXT;
+ runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;
+ currentMessageIndex = -1;
+
+ UIFrameEvent e;
+ e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
+ this->notifyObservers(&e);
+ screen->forceDisplay();
+
+ setIntervalFromNow(500);
+ return;
+ } else {
+ // Don't delete virtual keyboard immediately - it might still be executing
+ // Instead, just clear the callback and reset banner to stop input processing
+ graphics::NotificationRenderer::textInputCallback = nullptr;
+ graphics::NotificationRenderer::resetBanner();
+
+ // Return to inactive state
+ this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ this->currentMessageIndex = -1;
+ this->freetext = "";
+ this->cursor = 0;
+
+ // Force display update to show normal screen
+ UIFrameEvent e;
+ e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
+ this->notifyObservers(&e);
+ screen->forceDisplay();
+
+ // Schedule cleanup for next loop iteration to ensure safe deletion
+ setIntervalFromNow(50);
+ return;
+ }
+ });
+
+ return true;
+ }
+ }
#endif
// Normal canned message selection
@@ -944,12 +1003,54 @@ int32_t CannedMessageModule::runOnce()
// Normal module disable/idle handling
if ((this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED) || (this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE)) {
+ // 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;
+ }
+
temporaryMessage = "";
return INT32_MAX;
}
+ // Handle delayed virtual keyboard message sending
+ if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
+ // Virtual keyboard message sending case - text was not empty
+ if (this->freetext.length() > 0) {
+ LOG_INFO("Processing delayed virtual keyboard send: '%s'", this->freetext.c_str());
+ sendText(this->dest, this->channel, this->freetext.c_str(), true);
+
+ // 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::NotificationRenderer::resetBanner();
+ }
+
+ // Clear payload to indicate virtual keyboard processing is complete
+ // But keep SENDING_ACTIVE state to show "Sending..." screen for 2 seconds
+ this->payload = 0;
+ } else {
+ // Empty message, just go inactive
+ LOG_INFO("Empty freetext detected in delayed processing, returning to inactive state");
+ this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ }
+
+ UIFrameEvent e;
+ e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
+ this->currentMessageIndex = -1;
+ this->freetext = "";
+ this->cursor = 0;
+ this->notifyObservers(&e);
+ return 2000;
+ }
+
UIFrameEvent e;
- if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) ||
+ if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload != 0 &&
+ this->payload != CANNED_MESSAGE_RUN_STATE_FREETEXT) ||
(this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) ||
(this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION)) {
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
@@ -959,6 +1060,18 @@ int32_t CannedMessageModule::runOnce()
this->freetext = "";
this->cursor = 0;
this->notifyObservers(&e);
+ }
+ // Handle SENDING_ACTIVE state transition after virtual keyboard message
+ else if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload == 0) {
+ // This happens after virtual keyboard message sending is complete
+ LOG_INFO("Virtual keyboard message sending completed, returning to inactive state");
+ this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ temporaryMessage = "";
+ e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
+ this->currentMessageIndex = -1;
+ this->freetext = "";
+ this->cursor = 0;
+ this->notifyObservers(&e);
} else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) &&
!Throttle::isWithinTimespanMs(this->lastTouchMillis, INACTIVATE_AFTER_MS)) {
// Reset module on inactivity
@@ -967,9 +1080,23 @@ int32_t CannedMessageModule::runOnce()
this->freetext = "";
this->cursor = 0;
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+
+ // 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::NotificationRenderer::resetBanner();
+ }
+
this->notifyObservers(&e);
} else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) {
- if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
+ if (this->payload == 0) {
+ // [Exit] button pressed - return to inactive state
+ LOG_INFO("Processing [Exit] action - returning to inactive state");
+ this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ } else if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
if (this->freetext.length() > 0) {
sendText(this->dest, this->channel, this->freetext.c_str(), true);
this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;
@@ -999,17 +1126,16 @@ int32_t CannedMessageModule::runOnce()
this->notifyObservers(&e);
return 2000;
}
- // Always highlight the first real canned message when entering the message list
+ // Highlight [Select Destination] initially when entering the message list
else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) {
- int firstRealMsgIdx = 0;
+ int selectDestination = 0;
for (int i = 0; i < this->messagesCount; ++i) {
- if (strcmp(this->messages[i], "[Select Destination]") != 0 && strcmp(this->messages[i], "[Exit]") != 0 &&
- strcmp(this->messages[i], "[---- Free Text ----]") != 0) {
- firstRealMsgIdx = i;
+ if (strcmp(this->messages[i], "[Select Destination]") == 0) {
+ selectDestination = i;
break;
}
}
- this->currentMessageIndex = firstRealMsgIdx;
+ this->currentMessageIndex = selectDestination;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE;
} else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_UP) {
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index b6fee7703..0060e99fa 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -14,6 +14,10 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
{
auto p = *pptr;
+ if (mp.from == nodeDB->getNodeNum()) {
+ LOG_WARN("Ignoring packet supposed to be from our own node: %08x", mp.from);
+ return false;
+ }
if (p.is_licensed != owner.is_licensed) {
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
return true;
diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp
index e7e92c79a..b10413cc8 100644
--- a/src/modules/RoutingModule.cpp
+++ b/src/modules/RoutingModule.cpp
@@ -73,7 +73,7 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit
return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit
}
-RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
+RoutingModule::RoutingModule() : ProtobufModule(ROUTING_MODULE, meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
{
isPromiscuous = true;
diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h
index c047f6e29..7b43a6e98 100644
--- a/src/modules/RoutingModule.h
+++ b/src/modules/RoutingModule.h
@@ -2,6 +2,8 @@
#include "Channels.h"
#include "ProtobufModule.h"
+static const char *ROUTING_MODULE = "routing";
+
/**
* Routing module for router control messages
*/
diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp
index 39b297965..866497ecc 100644
--- a/src/modules/SerialModule.cpp
+++ b/src/modules/SerialModule.cpp
@@ -60,7 +60,7 @@
SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio;
-#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
+#if defined(TTGO_T_ECHO) || defined(T_ECHO_LITE) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
defined(ELECROW_ThinkNode_M5)
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
static Print *serialPrint = &Serial;
@@ -179,8 +179,8 @@ int32_t SerialModule::runOnce()
Serial.begin(baud);
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
}
-#elif !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \
- !defined(ELECROW_ThinkNode_M5)
+#elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
+ !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
#ifdef ARCH_RP2040
Serial2.setFIFOSize(RX_BUFFER);
@@ -236,8 +236,8 @@ int32_t SerialModule::runOnce()
}
}
-#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \
- !defined(ELECROW_ThinkNode_M5)
+#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
+ !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
processWXSerial();
@@ -496,8 +496,8 @@ ParsedLine parseLine(const char *line)
*/
void SerialModule::processWXSerial()
{
-#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && \
- !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
+#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \
+ !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
static double dir_sum_sin = 0;
diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index f4eccd667..d7df90bb5 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -602,7 +602,7 @@ void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
int start = 0;
int newlinePos = resultText.indexOf('\n', start);
- while (newlinePos != -1 || start < resultText.length()) {
+ while (newlinePos != -1 || start < static_cast(resultText.length())) {
String segment;
if (newlinePos != -1) {
segment = resultText.substring(start, newlinePos);
@@ -624,7 +624,7 @@ void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
int lastGoodBreak = -1;
bool lineComplete = false;
- for (int i = 0; i < remaining.length(); i++) {
+ for (int i = 0; i < static_cast(remaining.length()); i++) {
char ch = remaining.charAt(i);
String testLine = tempLine + ch;
diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 21d4a8fa0..7f7a9d511 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -95,6 +95,7 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
p->hop_start = e.packet->hop_start;
p->want_ack = e.packet->want_ack;
p->via_mqtt = true; // Mark that the packet was received via MQTT
+ p->transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MQTT;
p->which_payload_variant = e.packet->which_payload_variant;
memcpy(&p->decoded, &e.packet->decoded, std::max(sizeof(p->decoded), sizeof(p->encrypted)));
@@ -278,6 +279,8 @@ struct PubSubConfig {
// Defaults
static constexpr uint16_t defaultPort = 1883;
+ static constexpr uint16_t defaultPortTls = 8883;
+
uint16_t serverPort = defaultPort;
String serverAddr = default_mqtt_address;
const char *mqttUsername = default_mqtt_username;
@@ -640,7 +643,7 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC
}
const bool defaultServer = isDefaultServer(parsed.serverAddr);
- if (defaultServer && parsed.serverPort != PubSubConfig::defaultPort) {
+ if (defaultServer && !IS_ONE_OF(parsed.serverPort, PubSubConfig::defaultPort, PubSubConfig::defaultPortTls)) {
const char *warning = "Invalid MQTT config: default server address must not have a port specified";
LOG_ERROR(warning);
#if !IS_RUNNING_TESTS
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 834184292..95e191c8e 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -223,9 +223,12 @@ void NimbleBluetooth::deinit()
LOG_INFO("Disable bluetooth until reboot");
#ifdef BLE_LED
+#ifdef BLE_LED_INVERTED
+ digitalWrite(BLE_LED, HIGH);
+#else
digitalWrite(BLE_LED, LOW);
#endif
-
+#endif
NimBLEDevice::deinit();
#endif
}
diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index 1bbdd77e0..064bd8ef0 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -53,10 +53,15 @@
#define HW_VENDOR meshtastic_HardwareModel_WISMESH_TAG
#elif defined(GAT562_MESH_TRIAL_TRACKER)
#define HW_VENDOR meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER
+#elif defined(NOMADSTAR_METEOR_PRO)
+#define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO
+// MAke sure all custom RAK4630 boards are defined before the generic RAK4630
#elif defined(RAK4630)
#define HW_VENDOR meshtastic_HardwareModel_RAK4631
#elif defined(TTGO_T_ECHO)
#define HW_VENDOR meshtastic_HardwareModel_T_ECHO
+#elif defined(T_ECHO_LITE)
+#define HW_VENDOR meshtastic_HardwareModel_T_ECHO_LITE
#elif defined(ELECROW_ThinkNode_M1)
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1
#elif defined(NANO_G2_ULTRA)
@@ -89,8 +94,6 @@
#define HW_VENDOR meshtastic_HardwareModel_SEEED_SOLAR_NODE
#elif defined(HELTEC_MESH_POCKET)
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_POCKET
-#elif defined(NOMADSTAR_METEOR_PRO)
-#define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO
#elif defined(SEEED_WIO_TRACKER_L1_EINK)
#define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK
#elif defined(SEEED_WIO_TRACKER_L1)
diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp
index 1bf9a39fd..590d2f0ae 100644
--- a/src/platform/nrf52/main-nrf52.cpp
+++ b/src/platform/nrf52/main-nrf52.cpp
@@ -282,10 +282,14 @@ void cpuDeepSleep(uint32_t msecToWake)
#if SPI_INTERFACES_COUNT > 1
SPI1.end();
#endif
- // This may cause crashes as debug messages continue to flow.
- Serial.end();
+ if (Serial) // Another check in case of disabled default serial, does nothing bad
+ Serial.end(); // This may cause crashes as debug messages continue to flow.
+
+ // This causes troubles with waking up on nrf52 (on pro-micro in particular):
+ // we have no Serial1 in use on nrf52, check Serial and GPS modules.
#ifdef PIN_SERIAL1_RX
- Serial1.end();
+ if (Serial1) // A straightforward solution to the wake from deepsleep problem
+ Serial1.end();
#endif
setBluetoothEnable(false);
@@ -362,6 +366,7 @@ void cpuDeepSleep(uint32_t msecToWake)
// Resume on user button press
// https://github.com/lyusupov/SoftRF/blob/81c519ca75693b696752235d559e881f2e0511ee/software/firmware/source/SoftRF/src/platform/nRF52.cpp#L1738
constexpr uint32_t DFU_MAGIC_SKIP = 0x6d;
+ sd_power_gpregret_clr(0, 0xFF); // Clear the register before setting a new values in it for stability reasons
sd_power_gpregret_set(0, DFU_MAGIC_SKIP); // Equivalent NRF_POWER->GPREGRET = DFU_MAGIC_SKIP
// FIXME, use system off mode with ram retention for key state?
@@ -378,6 +383,12 @@ void cpuDeepSleep(uint32_t msecToWake)
nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1);
#endif
+#ifdef PROMICRO_DIY_TCXO
+ nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin
+ nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge
+ nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep
+#endif
+
auto ok = sd_power_system_off();
if (ok != NRF_SUCCESS) {
LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!");
diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index 5f99ec2c3..929a45d09 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -10,6 +10,7 @@
#include "linux/gpio/LinuxGPIOPin.h"
#include "meshUtils.h"
#include "yaml-cpp/yaml.h"
+#include
#include
#include
#include
@@ -29,11 +30,11 @@
std::map settingsMap;
std::map settingsStrings;
+portduino_config_struct portduino_config;
std::ofstream traceFile;
Ch341Hal *ch341Hal = nullptr;
char *configPath = nullptr;
char *optionMac = nullptr;
-bool forceSimulated = false;
bool verboseEnabled = false;
const char *argp_program_version = optstr(APP_VERSION);
@@ -66,7 +67,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
configPath = arg;
break;
case 's':
- forceSimulated = true;
+ portduino_config.force_simradio = true;
break;
case 'h':
optionMac = arg;
@@ -189,7 +190,7 @@ void portduinoSetup()
YAML::Node yamlConfig;
- if (forceSimulated == true) {
+ if (portduino_config.force_simradio == true) {
settingsMap[use_simradio] = true;
} else if (configPath != nullptr) {
if (loadConfig(configPath)) {
@@ -253,16 +254,95 @@ void portduinoSetup()
std::cout << "autoconf: Could not locate CH341 device" << std::endl;
}
// Try Pi HAT+
- std::cout << "autoconf: Looking for Pi HAT+..." << std::endl;
- if (access("/proc/device-tree/hat/product", R_OK) == 0) {
- std::ifstream hatProductFile("/proc/device-tree/hat/product");
- if (hatProductFile.is_open()) {
- hatProductFile.read(autoconf_product, 95);
- hatProductFile.close();
+ if (strlen(autoconf_product) < 6) {
+ std::cout << "autoconf: Looking for Pi HAT+..." << std::endl;
+ if (access("/proc/device-tree/hat/product", R_OK) == 0) {
+ std::ifstream hatProductFile("/proc/device-tree/hat/product");
+ if (hatProductFile.is_open()) {
+ hatProductFile.read(autoconf_product, 95);
+ hatProductFile.close();
+ }
+ std::cout << "autoconf: Found Pi HAT+ " << autoconf_product << " at /proc/device-tree/hat/product" << std::endl;
+ } else {
+ std::cout << "autoconf: Could not locate Pi HAT+ at /proc/device-tree/hat/product" << std::endl;
+ }
+ }
+ // attempt to load autoconf data from an EEPROM on 0x50
+ // RAK6421-13300-S1:aabbcc123456:5ba85807d92138b7519cfb60460573af:3061e8d8
+ // :mac address :<16 random unique bytes in hexidecimal> : crc32
+ // crc32 is calculated on the eeprom string up to but not including the final colon
+ if (strlen(autoconf_product) < 6) {
+ try {
+ char *mac_start = nullptr;
+ char *devID_start = nullptr;
+ char *crc32_start = nullptr;
+ Wire.begin();
+ Wire.beginTransmission(0x50);
+ Wire.write(0x0);
+ Wire.write(0x0);
+ Wire.endTransmission();
+ Wire.requestFrom((uint8_t)0x50, (uint8_t)75);
+ uint8_t i = 0;
+ delay(100);
+ std::string autoconf_raw;
+ while (Wire.available() && i < sizeof(autoconf_product)) {
+ autoconf_product[i] = Wire.read();
+ if (autoconf_product[i] == 0xff) {
+ autoconf_product[i] = 0x0;
+ break;
+ }
+ autoconf_raw += autoconf_product[i];
+ if (autoconf_product[i] == ':') {
+ autoconf_product[i] = 0x0;
+ if (mac_start == nullptr) {
+ mac_start = autoconf_product + i + 1;
+ } else if (devID_start == nullptr) {
+ devID_start = autoconf_product + i + 1;
+ } else if (crc32_start == nullptr) {
+ crc32_start = autoconf_product + i + 1;
+ }
+ }
+ i++;
+ }
+ if (crc32_start != nullptr && strlen(crc32_start) == 8) {
+ std::string crc32_str(crc32_start);
+ uint32_t crc32_value = 0;
+
+ // convert crc32 ascii to raw uint32
+ for (int j = 0; j < 4; j++) {
+ crc32_value += std::stoi(crc32_str.substr(j * 2, 2), nullptr, 16) << (3 - j) * 8;
+ }
+ std::cout << "autoconf: Found eeprom crc " << crc32_start << std::endl;
+
+ // set the autoconf string to blank and short circuit
+ if (crc32_value != crc32Buffer(autoconf_raw.c_str(), i - 9)) {
+ std::cout << "autoconf: crc32 mismatch, dropping " << std::endl;
+ autoconf_product[0] = 0x0;
+ } else {
+ std::cout << "autoconf: Found eeprom data " << autoconf_raw << std::endl;
+ if (mac_start != nullptr) {
+ std::cout << "autoconf: Found mac data " << mac_start << std::endl;
+ if (strlen(mac_start) == 12)
+ settingsStrings[mac_address] = std::string(mac_start);
+ }
+ if (devID_start != nullptr) {
+ std::cout << "autoconf: Found deviceid data " << devID_start << std::endl;
+ if (strlen(devID_start) == 32) {
+ std::string devID_str(devID_start);
+ for (int j = 0; j < 16; j++) {
+ portduino_config.device_id[j] = std::stoi(devID_str.substr(j * 2, 2), nullptr, 16);
+ }
+ portduino_config.has_device_id = true;
+ }
+ }
+ }
+ } else {
+ std::cout << "autoconf: crc32 missing " << std::endl;
+ autoconf_product[0] = 0x0;
+ }
+ } catch (...) {
+ std::cout << "autoconf: Could not locate EEPROM" << std::endl;
}
- std::cout << "autoconf: Found Pi HAT+ " << autoconf_product << " at /proc/device-tree/hat/product" << std::endl;
- } else {
- std::cout << "autoconf: Could not locate Pi HAT+ at /proc/device-tree/hat/product" << std::endl;
}
// Load the config file based on the product string
if (strlen(autoconf_product) > 0) {
@@ -553,6 +633,48 @@ bool loadConfig(const char *configPath)
}
}
}
+ if (yamlConfig["Lora"]["rfswitch_table"]) {
+ portduino_config.has_rfswitch_table = true;
+ portduino_config.rfswitch_table[0].mode = LR11x0::MODE_STBY;
+ portduino_config.rfswitch_table[1].mode = LR11x0::MODE_RX;
+ portduino_config.rfswitch_table[2].mode = LR11x0::MODE_TX;
+ portduino_config.rfswitch_table[3].mode = LR11x0::MODE_TX_HP;
+ portduino_config.rfswitch_table[4].mode = LR11x0::MODE_TX_HF;
+ portduino_config.rfswitch_table[5].mode = LR11x0::MODE_GNSS;
+ portduino_config.rfswitch_table[6].mode = LR11x0::MODE_WIFI;
+ portduino_config.rfswitch_table[7] = END_OF_MODE_TABLE;
+
+ for (int i = 0; i < 5; i++) {
+
+ // set up the pin array first
+ if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO5")
+ portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO5;
+ if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO6")
+ portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO6;
+ if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO7")
+ portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO7;
+ if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO8")
+ portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO8;
+ if (yamlConfig["Lora"]["rfswitch_table"]["pins"][i].as("") == "DIO10")
+ portduino_config.rfswitch_dio_pins[i] = RADIOLIB_LR11X0_DIO10;
+
+ // now fill in the table
+ if (yamlConfig["Lora"]["rfswitch_table"]["MODE_STBY"][i].as("") == "HIGH")
+ portduino_config.rfswitch_table[0].values[i] = HIGH;
+ if (yamlConfig["Lora"]["rfswitch_table"]["MODE_RX"][i].as("") == "HIGH")
+ portduino_config.rfswitch_table[1].values[i] = HIGH;
+ if (yamlConfig["Lora"]["rfswitch_table"]["MODE_TX"][i].as("") == "HIGH")
+ portduino_config.rfswitch_table[2].values[i] = HIGH;
+ if (yamlConfig["Lora"]["rfswitch_table"]["MODE_TX_HP"][i].as("") == "HIGH")
+ portduino_config.rfswitch_table[3].values[i] = HIGH;
+ if (yamlConfig["Lora"]["rfswitch_table"]["MODE_TX_HF"][i].as("") == "HIGH")
+ portduino_config.rfswitch_table[4].values[i] = HIGH;
+ if (yamlConfig["Lora"]["rfswitch_table"]["MODE_GNSS"][i].as("") == "HIGH")
+ portduino_config.rfswitch_table[5].values[i] = HIGH;
+ if (yamlConfig["Lora"]["rfswitch_table"]["MODE_WIFI"][i].as("") == "HIGH")
+ portduino_config.rfswitch_table[6].values[i] = HIGH;
+ }
+ }
}
if (yamlConfig["GPIO"]) {
settingsMap[userButtonPin] = yamlConfig["GPIO"]["User"].as(RADIOLIB_NC);
diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h
index 288870eef..8c36a1180 100644
--- a/src/platform/portduino/PortduinoGlue.h
+++ b/src/platform/portduino/PortduinoGlue.h
@@ -3,16 +3,21 @@
#include