diff --git a/.env.example b/.env.example
new file mode 100644
index 000000000..72d95970a
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,4 @@
+# Absolute path to the local meshtastic config.yaml file
+CONFIG_PATH=/path/to/meshtastic/config.yaml
+# USB device to passthrough (`lsusb -t`: look for `ch341`)
+USB_DEVICE=/dev/bus/usb/001/037
diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml
new file mode 100644
index 000000000..13817a8cf
--- /dev/null
+++ b/.github/workflows/build_docker.yml
@@ -0,0 +1,51 @@
+name: Build Docker
+
+on: workflow_call
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ build-native:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ ref: ${{github.event.pull_request.head.ref}}
+ repository: ${{github.event.pull_request.head.repo.full_name}}
+
+ - name: Get release version string
+ run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+ id: version
+
+ - name: Docker login
+ if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
+ uses: docker/login-action@v3
+ with:
+ username: meshtastic
+ password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
+
+ - name: Docker setup
+ if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
+ uses: docker/setup-buildx-action@v3
+
+ - name: Docker build and push tagged versions
+ if: ${{ github.event_name == 'workflow_dispatch' }}
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ tags: meshtastic/meshtasticd:${{ steps.version.outputs.version }}
+
+ - name: Docker build and push
+ if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ tags: meshtastic/meshtasticd:latest
diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml
index b1b012705..74bc074aa 100644
--- a/.github/workflows/build_native.yml
+++ b/.github/workflows/build_native.yml
@@ -14,7 +14,7 @@ jobs:
shell: bash
run: |
sudo apt-get update --fix-missing
- sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev
+ sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
- name: Checkout code
uses: actions/checkout@v4
@@ -50,37 +50,3 @@ jobs:
path: |
release/meshtasticd_linux_x86_64
bin/config-dist.yaml
-
- - name: Docker login
- if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
- uses: docker/login-action@v3
- continue-on-error: true # FIXME: Failing docker login auth
- with:
- logout: true
- username: meshtastic
- password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
-
- - name: Docker setup
- if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
- continue-on-error: true
- uses: docker/setup-buildx-action@v3
-
- - name: Docker build and push tagged versions
- if: ${{ github.event_name == 'workflow_dispatch' }}
- continue-on-error: true
- uses: docker/build-push-action@v6
- with:
- context: .
- file: ./Dockerfile
- push: true
- tags: meshtastic/device-simulator:${{ steps.version.outputs.version }}
-
- - name: Docker build and push
- if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
- continue-on-error: true
- uses: docker/build-push-action@v6
- with:
- context: .
- file: ./Dockerfile
- push: true
- tags: meshtastic/device-simulator:latest
diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml
index 1826504f0..ac63dfea4 100644
--- a/.github/workflows/build_raspbian.yml
+++ b/.github/workflows/build_raspbian.yml
@@ -14,7 +14,7 @@ jobs:
shell: bash
run: |
sudo apt-get update -y --fix-missing
- sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev
+ sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
- name: Checkout code
uses: actions/checkout@v4
diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml
index fd53585a5..565d9a0dc 100644
--- a/.github/workflows/build_raspbian_armv7l.yml
+++ b/.github/workflows/build_raspbian_armv7l.yml
@@ -14,7 +14,7 @@ jobs:
shell: bash
run: |
sudo apt-get update -y --fix-missing
- sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev
+ sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
- name: Checkout code
uses: actions/checkout@v4
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 86fb6e699..0109bef1a 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -137,6 +137,11 @@ jobs:
package-native:
uses: ./.github/workflows/package_amd64.yml
+ build-docker:
+ if: ${{ github.event_name == 'workflow_dispatch' }}
+ uses: ./.github/workflows/build_docker.yml
+ secrets: inherit
+
after-checks:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }}
diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml
index 782ef479b..c6e82e1be 100644
--- a/.github/workflows/package_amd64.yml
+++ b/.github/workflows/package_amd64.yml
@@ -79,7 +79,7 @@ jobs:
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
arch: amd64
- depends: libyaml-cpp0.7, openssl, libulfius2.7
+ depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v4
@@ -87,4 +87,4 @@ jobs:
name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
overwrite: true
path: |
- ./*.deb
\ No newline at end of file
+ ./*.deb
diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml
index aef8905f8..a4cd49573 100644
--- a/.github/workflows/package_raspbian.yml
+++ b/.github/workflows/package_raspbian.yml
@@ -79,7 +79,7 @@ jobs:
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
arch: arm64
- depends: libyaml-cpp0.7, openssl, libulfius2.7
+ depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v4
@@ -87,4 +87,4 @@ jobs:
name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
overwrite: true
path: |
- ./*.deb
\ No newline at end of file
+ ./*.deb
diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml
index ddb84d4a7..c4cc5c673 100644
--- a/.github/workflows/package_raspbian_armv7l.yml
+++ b/.github/workflows/package_raspbian_armv7l.yml
@@ -79,7 +79,7 @@ jobs:
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
arch: armhf
- depends: libyaml-cpp0.7, openssl, libulfius2.7
+ depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v4
@@ -87,4 +87,4 @@ jobs:
name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
overwrite: true
path: |
- ./*.deb
\ No newline at end of file
+ ./*.deb
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 241598fd0..ae9f82543 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,12 +8,15 @@ on:
jobs:
test-simulator:
runs-on: ubuntu-latest
+ env:
+ LCOV_CAPTURE_FLAGS: --quiet --capture --include "${PWD}/src/*" --exclude '*/src/mesh/generated/*' --directory .pio/build/coverage/src --base-directory "${PWD}"
steps:
- - name: Install libbluetooth
+ - name: Install libs needed for native build
shell: bash
run: |
sudo apt-get update --fix-missing
- sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
+ sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
+ sudo apt-get install -y lcov
- name: Checkout code
uses: actions/checkout@v4
@@ -24,7 +27,7 @@ jobs:
shell: bash
run: |
python -m pip install --upgrade pip
- pip install -U platformio adafruit-nrfutil
+ pip install -U platformio adafruit-nrfutil dotmap
pip install -U meshtastic --pre
- name: Upgrade platformio
@@ -36,17 +39,25 @@ jobs:
run: bin/build-native.sh
# We now run integration test before other build steps (to quickly see runtime failures)
- - name: Build for native
- run: platformio run -e native
+ - name: Build for native/coverage
+ run: |
+ platformio run -e coverage
+ lcov ${{ env.LCOV_CAPTURE_FLAGS }} --initial --output-file coverage_base.info
- name: Integration test
run: |
- .pio/build/native/program & sleep 10 # 5 seconds was not enough
+ .pio/build/coverage/program &
+ PID=$!
+ timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
echo "Simulator started, launching python test..."
python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
+ wait
+ lcov ${{ env.LCOV_CAPTURE_FLAGS }} --test-name integration --output-file coverage_integration.info
- name: PlatformIO Tests
- run: platformio test -e native --junit-output-path testreport.xml
+ run: |
+ platformio test -e coverage --junit-output-path testreport.xml
+ lcov ${{ env.LCOV_CAPTURE_FLAGS }} --test-name tests --output-file coverage_tests.info
- name: Test Report
uses: dorny/test-reporter@v1.9.1
@@ -56,6 +67,19 @@ jobs:
path: testreport.xml
reporter: java-junit
+ - name: Generate Code Coverage Report
+ run: |
+ lcov --quiet --add-tracefile coverage_base.info --add-tracefile coverage_integration.info --add-tracefile coverage_tests.info --output-file coverage_src.info
+ mkdir code-coverage-report
+ genhtml --quiet --legend --prefix "${PWD}" coverage_src.info --output-directory code-coverage-report
+ mv coverage_*.info code-coverage-report
+
+ - name: Save Code Coverage Report
+ uses: actions/upload-artifact@v4
+ with:
+ name: code-coverage-report
+ path: code-coverage-report
+
hardware-tests:
runs-on: test-runner
steps:
diff --git a/Dockerfile b/Dockerfile
index fc34fbd4c..f3b294a5b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,32 +1,29 @@
-FROM debian:bookworm-slim AS builder
+# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
+# trunk-ignore-all(hadolint/DL3008): Use latest version of apt packages for buildchain
+# trunk-ignore-all(trivy/DS002): We must run as root for this container
+# trunk-ignore-all(checkov/CKV_DOCKER_8): We must run as root for this container
+# trunk-ignore-all(hadolint/DL3002): We must run as root for this container
+FROM python:3.12-bookworm AS builder
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC
-# http://bugs.python.org/issue19846
-# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
-ENV LANG C.UTF-8
-
-# Install build deps
-USER root
-
-# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue
-# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
-RUN apt-get update && apt-get install --no-install-recommends -y wget python3 python3-pip python3-wheel python3-venv g++ zip git \
- ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev \
- libulfius-dev liborcania-dev libssl-dev pkg-config && \
- apt-get clean && rm -rf /var/lib/apt/lists/* && mkdir /tmp/firmware
-
-RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh && chown mesh:mesh /tmp/firmware
-USER mesh
+# Install Dependencies
+ENV PIP_ROOT_USER_ACTION=ignore
+RUN apt-get update && apt-get install --no-install-recommends -y wget g++ zip git ca-certificates \
+ libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev \
+ libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev pkg-config && \
+ apt-get clean && rm -rf /var/lib/apt/lists/* && \
+ pip install --no-cache-dir -U platformio==6.1.16 && \
+ mkdir /tmp/firmware
+# Copy source code
WORKDIR /tmp/firmware
-RUN python3 -m venv /tmp/firmware
-RUN bash -o pipefail -c "source bin/activate; pip3 install --no-cache-dir -U platformio==6.1.15"
-# trunk-ignore(terrascan/AC_DOCKER_00024): We would actually like these files to be owned by mesh tyvm
-COPY --chown=mesh:mesh . /tmp/firmware
-RUN bash -o pipefail -c "source ./bin/activate && bash ./bin/build-native.sh"
-RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd"
+COPY . /tmp/firmware
+
+# Build
+RUN bash ./bin/build-native.sh && \
+ cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd"
##### PRODUCTION BUILD #############
@@ -35,20 +32,25 @@ FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC
-# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue
-# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
-RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libulfius2.7 liborcania2.3 libssl3 && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
+# nosemgrep: dockerfile.security.last-user-is-root.last-user-is-root
+USER root
-RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
-USER mesh
+RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 && \
+ apt-get clean && rm -rf /var/lib/apt/lists/* \
+ && mkdir -p /var/lib/meshtasticd \
+ && mkdir -p /etc/meshtasticd/config.d
-WORKDIR /home/mesh
-COPY --from=builder /tmp/firmware/release/meshtasticd /home/mesh/
+# Fetch compiled binary from the builder
+COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
+# Copy config templates
+COPY ./bin/config.d /etc/meshtasticd/available.d
-RUN mkdir data
-VOLUME /home/mesh/data
+WORKDIR /var/lib/meshtasticd
+VOLUME /var/lib/meshtasticd
-CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ]
+# Expose Meshtastic TCP API port from the host
+EXPOSE 4403
-HEALTHCHECK NONE
+CMD [ "sh", "-cx", "meshtasticd -d /var/lib/meshtasticd" ]
+
+HEALTHCHECK NONE
\ No newline at end of file
diff --git a/alpine.Dockerfile b/alpine.Dockerfile
new file mode 100644
index 000000000..115602b3b
--- /dev/null
+++ b/alpine.Dockerfile
@@ -0,0 +1,42 @@
+# trunk-ignore-all(trivy/DS002): We must run as root for this container
+# trunk-ignore-all(checkov/CKV_DOCKER_8): We must run as root for this container
+# trunk-ignore-all(hadolint/DL3002): We must run as root for this container
+
+FROM python:3.12-alpine3.21 AS builder
+
+ENV PIP_ROOT_USER_ACTION=ignore
+RUN apk add bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
+ libusb-dev i2c-tools-dev openssl-dev pkgconf argp-standalone && \
+ pip install --no-cache-dir -U platformio==6.1.16 && \
+ mkdir /tmp/firmware
+
+WORKDIR /tmp/firmware
+COPY . /tmp/firmware
+
+# Create small package (no debugging symbols)
+# Add `argp` for musl
+ENV PLATFORMIO_BUILD_FLAGS="-Os -ffunction-sections -fdata-sections -Wl,--gc-sections -largp"
+
+RUN bash ./bin/build-native.sh && \
+ cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd"
+
+# ##### PRODUCTION BUILD #############
+
+FROM alpine:3.21
+
+# nosemgrep: dockerfile.security.last-user-is-root.last-user-is-root
+USER root
+
+RUN apk add libstdc++ libgpiod yaml-cpp libusb i2c-tools \
+ && mkdir -p /var/lib/meshtasticd \
+ && mkdir -p /etc/meshtasticd/config.d
+COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
+
+WORKDIR /var/lib/meshtasticd
+VOLUME /var/lib/meshtasticd
+
+EXPOSE 4403
+
+CMD [ "sh", "-cx", "meshtasticd --fsdir=/var/lib/meshtasticd" ]
+
+HEALTHCHECK NONE
\ No newline at end of file
diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini
index bbafef4da..aa1150e9a 100644
--- a/arch/portduino/portduino.ini
+++ b/arch/portduino/portduino.ini
@@ -1,6 +1,6 @@
; The Portduino based 'native' environment. Currently supported on Linux targets with real LoRa hardware (or simulated).
[portduino_base]
-platform = https://github.com/meshtastic/platform-native.git#73bd1a21183ca8b00c4ea58bb21315df31a50dff
+platform = https://github.com/meshtastic/platform-native.git#562d189828f09fbf4c4093b3c0104bae9d8e9ff9
framework = arduino
build_src_filter =
@@ -26,6 +26,7 @@ lib_deps =
${radiolib_base.lib_deps}
rweather/Crypto@^0.4.0
https://github.com/lovyan03/LovyanGFX.git#1401c28a47646fe00538d487adcb2eb3c72de805
+ https://github.com/pine64/libch341-spi-userspace#a9b17e3452f7fb747000d9b4ad4409155b39f6ef
build_flags =
${arduino_base.build_flags}
@@ -33,7 +34,10 @@ build_flags =
-Isrc/platform/portduino
-DRADIOLIB_EEPROM_UNSUPPORTED
-DPORTDUINO_LINUX_HARDWARE
+ -lpthread
-lstdc++fs
-lbluetooth
-lgpiod
-lyaml-cpp
+ -li2c
+ -std=c++17
\ No newline at end of file
diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml
index ec262536f..49de1675b 100644
--- a/bin/config-dist.yaml
+++ b/bin/config-dist.yaml
@@ -12,13 +12,6 @@ Lora:
# IRQ: 17
# Reset: 22
-# Module: sx1262 # pinedio
-# CS: 0
-# IRQ: 10
-# Busy: 11
-# DIO2_AS_RF_SWITCH: true
-# spidev: spidev0.1
-
# Module: RF95 # Adafruit RFM9x
# Reset: 25
# CS: 7
@@ -50,8 +43,6 @@ Lora:
# TXen: x # TX and RX enable pins
# RXen: x
-# ch341_quirk: true # Uncomment this to use the chunked SPI transfer that seems to fix the ch341
-
# spiSpeed: 2000000
### Set gpio chip to use in /dev/. Defaults to 0.
diff --git a/bin/config.d/femtofox/femtofox_EByte-E22-900M30S_Ebyte-E22-900M22S.yaml b/bin/config.d/femtofox/femtofox_EByte-E22-900M30S_Ebyte-E22-900M22S.yaml
deleted file mode 100644
index 6c88b1eb2..000000000
--- a/bin/config.d/femtofox/femtofox_EByte-E22-900M30S_Ebyte-E22-900M22S.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
----
-Lora:
-## Ebyte E22-900M30S, E22-900M22S with no external RF switching setup
-## Will work with any module without RF switching, and with TCXO
- Module: sx1262
- gpiochip: 1 # subtract 32 from the gpio numbers
- DIO2_AS_RF_SWITCH: true
- DIO3_TCXO_VOLTAGE: true
- CS: 16 #pin6 / GPIO48 1C0
- IRQ: 23 #pin17 / GPIO55 1C7
- Busy: 22 #pin16 / GPIO54 1C6
- Reset: 25 #pin13 / GPIO57 1D1
- RXen: 24 #pin12 / GPIO56 1D0
- #TXen: bridge to DIO2 on E22 module
- spidev: spidev0.0
- spiSpeed: 2000000
\ No newline at end of file
diff --git a/bin/config.d/femtofox/femtofox_EByte-E22-900MM22S.yaml b/bin/config.d/femtofox/femtofox_EByte-E22-900MM22S.yaml
deleted file mode 100644
index 451d5d3f4..000000000
--- a/bin/config.d/femtofox/femtofox_EByte-E22-900MM22S.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
----
-Lora:
-## Ebyte E22-900MM22S with no external RF switching setup
-## Will work with any module without RF switching and no TCXO
- Module: sx1262
- gpiochip: 1 # subtract 32 from the gpio numbers
- DIO2_AS_RF_SWITCH: true
- DIO3_TCXO_VOLTAGE: true
- CS: 16 #pin6 / GPIO48 1C0
- IRQ: 23 #pin17 / GPIO55 1C7
- Busy: 22 #pin16 / GPIO54 1C6
- Reset: 25 #pin13 / GPIO57 1D1
- RXen: 24 #pin12 / GPIO56 1D0
- #TXen: bridge to DIO2 on E22 module
- spidev: spidev0.0
- spiSpeed: 2000000
\ No newline at end of file
diff --git a/bin/config.d/femtofox/femtofox_Heltec-HT-RA62_Seeed-WIO-SX1262.yaml b/bin/config.d/femtofox/femtofox_Heltec-HT-RA62_Seeed-WIO-SX1262.yaml
deleted file mode 100644
index d5f02b42c..000000000
--- a/bin/config.d/femtofox/femtofox_Heltec-HT-RA62_Seeed-WIO-SX1262.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
----
-Lora:
-## Heltec HT-RA62, Seeed WIO SX1262
-## Will work with any module with automatic RF switching, and with TCXO
- Module: sx1262
- gpiochip: 1 # subtract 32 from the gpio numbers
- DIO2_AS_RF_SWITCH: true
- DIO3_TCXO_VOLTAGE: true
- CS: 16 #pin6 (GPIO pin 48 1C0)
- IRQ: 23 #pin17 (GPIO pin 55 1C7)
- Reset: 25 #pin13 (GPIO pin 57 1D1)
- Busy: 22 #pin16 (GPIO pin 54 1C6)
- spidev: spidev0.0 #pins are (CS=6, CLK=7, MOSI=8, MISO=9)
- spiSpeed: 2000000
\ No newline at end of file
diff --git a/bin/config.d/femtofox/femtofox_LR1121_TCXO.yaml b/bin/config.d/femtofox/femtofox_LR1121_TCXO.yaml
new file mode 100644
index 000000000..7aa860f61
--- /dev/null
+++ b/bin/config.d/femtofox/femtofox_LR1121_TCXO.yaml
@@ -0,0 +1,20 @@
+---
+Lora:
+## Ebyte E80-900M22S
+## This is a bit experimental
+##
+##
+ Module: lr1121
+ gpiochip: 1 # subtract 32 from the gpio numbers
+ DIO3_TCXO_VOLTAGE: 1.8
+ CS: 16 #pin6 / GPIO48 1C0
+ IRQ: 23 #pin17 / GPIO55 1C7
+ Busy: 22 #pin16 / GPIO54 1C6
+ Reset: 25 #pin13 / GPIO57 1D1
+
+
+ spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
+ spiSpeed: 2000000
+
+General:
+ MACAddressSource: eth0
diff --git a/bin/config.d/femtofox/femtofox_SX1262_TCXO.yaml b/bin/config.d/femtofox/femtofox_SX1262_TCXO.yaml
new file mode 100644
index 000000000..a4dec870a
--- /dev/null
+++ b/bin/config.d/femtofox/femtofox_SX1262_TCXO.yaml
@@ -0,0 +1,21 @@
+---
+Lora:
+## Ebyte E22-900M30S, E22-900M22S with or without external RF switching setup
+## HT-RA62 (Has internal switching, but whatever)
+## Seeed WIO SX1262 (already has TXEN-DIO2 link, but needs RXEN)
+## Will work with any module with or without RF switching, and with TCXO
+ Module: sx1262
+ gpiochip: 1 # subtract 32 from the gpio numbers
+ DIO2_AS_RF_SWITCH: true
+ DIO3_TCXO_VOLTAGE: true
+ CS: 16 #pin6 / GPIO48 1C0
+ IRQ: 23 #pin17 / GPIO55 1C7
+ Busy: 22 #pin16 / GPIO54 1C6
+ Reset: 25 #pin13 / GPIO57 1D1
+ RXen: 24 #pin12 / GPIO56 1D0 # Not strictly needed for auto-switching, but why complicate things?
+# TXen: bridge to DIO2 on E22 module
+ spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
+ spiSpeed: 2000000
+
+General:
+ MACAddressSource: eth0
diff --git a/bin/config.d/femtofox/femtofox_SX1262_XTAL.yaml b/bin/config.d/femtofox/femtofox_SX1262_XTAL.yaml
new file mode 100644
index 000000000..6b956f3e3
--- /dev/null
+++ b/bin/config.d/femtofox/femtofox_SX1262_XTAL.yaml
@@ -0,0 +1,21 @@
+---
+Lora:
+## Ebyte E22-900MM22S with no external RF switching setup
+## Waveshare SX126X XXXM, AI Thinker RA-01SH
+## Will work with any module with or without RF switching and no TCXO
+
+ Module: sx1262
+ gpiochip: 1 # subtract 32 from the gpio numbers
+ DIO2_AS_RF_SWITCH: true
+ DIO3_TCXO_VOLTAGE: false
+ CS: 16 #pin6 / GPIO48 1C0
+ IRQ: 23 #pin17 / GPIO55 1C7
+ Busy: 22 #pin16 / GPIO54 1C6
+ Reset: 25 #pin13 / GPIO57 1D1
+ RXen: 24 #pin12 / GPIO56 1D0 # Not strictly needed for auto-switching, but why complicate things?
+# TXen: bridge to DIO2 on E22 module
+ spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
+ spiSpeed: 2000000
+
+General:
+ MACAddressSource: eth0
diff --git a/bin/config.d/femtofox/femtofox_Waveshare-SX126X-XXXM_AI-Thinker-RA-01SH.yaml b/bin/config.d/femtofox/femtofox_Waveshare-SX126X-XXXM_AI-Thinker-RA-01SH.yaml
deleted file mode 100644
index 23834adec..000000000
--- a/bin/config.d/femtofox/femtofox_Waveshare-SX126X-XXXM_AI-Thinker-RA-01SH.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
----
-Lora:
-## Waveshare SX126X XXXM, AI Thinker RA-01SH
-## Will work with any module with automatic RF switching, and with no TCXO
- Module: sx1262
- gpiochip: 1 # subtract 32 from the gpio numbers
- DIO2_AS_RF_SWITCH: true
- CS: 16 #pin6 (GPIO pin 48 1C0)
- IRQ: 23 #pin17 (GPIO pin 55 1C7)
- Reset: 25 #pin13 (GPIO pin 57 1D1)
- Busy: 22 #pin16 (GPIO pin 54 1C6)
- spidev: spidev0.0 #pins are (CS=6, CLK=7, MOSI=8, MISO=9)
- spiSpeed: 2000000
diff --git a/bin/config.d/lora-meshstick-1262.yaml b/bin/config.d/lora-meshstick-1262.yaml
new file mode 100644
index 000000000..3f8d6c617
--- /dev/null
+++ b/bin/config.d/lora-meshstick-1262.yaml
@@ -0,0 +1,11 @@
+Lora:
+ Module: sx1262
+ CS: 0
+ IRQ: 6
+ Reset: 2
+ Busy: 4
+ spidev: ch341
+ DIO3_TCXO_VOLTAGE: true
+# USB_Serialnum: 12345678
+ USB_PID: 0x5512
+ USB_VID: 0x1A86
diff --git a/bin/config.d/lora-pinedio-usb-sx1262.yaml b/bin/config.d/lora-pinedio-usb-sx1262.yaml
new file mode 100644
index 000000000..6b8a9fc95
--- /dev/null
+++ b/bin/config.d/lora-pinedio-usb-sx1262.yaml
@@ -0,0 +1,5 @@
+Lora:
+ Module: sx1262
+ CS: 0
+ IRQ: 10
+ spidev: ch341
\ No newline at end of file
diff --git a/boards/esp32-s3-zero.json b/boards/esp32-s3-zero.json
new file mode 100644
index 000000000..76cb34fa0
--- /dev/null
+++ b/boards/esp32-s3-zero.json
@@ -0,0 +1,41 @@
+{
+ "build": {
+ "arduino": {
+ "partitions": "default.csv",
+ "memory_type": "qio_qspi"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DARDUINO_ESP32S3_DEV",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=1",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DBOARD_HAS_PSRAM"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "psram_type": "qio",
+ "hwids": [["0x303A", "0x1001"]],
+ "mcu": "esp32s3",
+ "variant": "esp32s3"
+ },
+ "connectivity": ["wifi", "bluetooth"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "platforms": ["espressif32"],
+ "name": "Espressif ESP32-S3-FH4R2 (4 MB QD, 2MB PSRAM)",
+ "upload": {
+ "flash_size": "4MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 4194304,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html",
+ "vendor": "Espressif"
+}
diff --git a/boards/mesh-tab.json b/boards/mesh-tab.json
new file mode 100644
index 000000000..52c65bf77
--- /dev/null
+++ b/boards/mesh-tab.json
@@ -0,0 +1,42 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld",
+ "partitions": "default_16MB.csv",
+ "memory_type": "qio_qspi"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DBOARD_HAS_PSRAM",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DARDUINO_USB_MODE=0",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=1"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [["0x303A", "0x80D6"]],
+ "mcu": "esp32s3",
+ "variant": "mesh-tab"
+ },
+ "connectivity": ["wifi", "bluetooth", "lora"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "ESP32-S3 WROOM-1 N16R2 (16 MB FLASH, 2 MB PSRAM)",
+ "upload": {
+ "flash_size": "16MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 16777216,
+ "use_1200bps_touch": true,
+ "wait_for_upload_port": true,
+ "require_upload_port": true,
+ "speed": 460800
+ },
+ "url": "https://github.com/valzzu/Mesh-Tab",
+ "vendor": "Espressif"
+}
diff --git a/boards/seeed-sensecap-indicator.json b/boards/seeed-sensecap-indicator.json
index 3fc57126f..0a02fc882 100644
--- a/boards/seeed-sensecap-indicator.json
+++ b/boards/seeed-sensecap-indicator.json
@@ -15,10 +15,12 @@
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
+ "f_boot": "120000000L",
+ "boot": "qio",
"flash_mode": "qio",
"hwids": [["0x1A86", "0x7523"]],
"mcu": "esp32s3",
- "variant": "esp32s3r8"
+ "variant": "esp32s3"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
@@ -32,9 +34,9 @@
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
- "require_upload_port": true,
+ "require_upload_port": false,
"use_1200bps_touch": true,
- "wait_for_upload_port": true,
+ "wait_for_upload_port": false,
"speed": 921600
},
"url": "https://www.seeedstudio.com/Indicator-for-Meshtastic.html",
diff --git a/boards/t-deck.json b/boards/t-deck.json
index d62ec48e6..b112921b9 100644
--- a/boards/t-deck.json
+++ b/boards/t-deck.json
@@ -10,7 +10,7 @@
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
- "-DARDUINO_EVENT_RUNNING_CORE=0"
+ "-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
diff --git a/docker-compose.yml b/docker-compose.yml
index 82f2647e8..4aac318c5 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,13 +1,26 @@
-version: "3.7"
+# USB-Based Meshtastic container-node!
+
+# Copy .env.example to .env and set the USB_DEVICE and CONFIG_PATH variables
services:
meshtastic-node:
build: .
- deploy:
- mode: replicated
- replicas: 4
- networks:
- - mesh
+ container_name: meshtasticd
-networks:
- mesh:
+ # Pass USB device through to the container
+ devices:
+ - "${USB_DEVICE}"
+
+ # Mount local config file and named volume for data persistence
+ volumes:
+ - "${CONFIG_PATH}:/etc/meshtasticd/config.yaml:ro"
+ - meshtastic_data:/var/lib/meshtasticd
+
+ # Forward the container’s port 4403 to the host
+ ports:
+ - 4403:4403
+
+ restart: unless-stopped
+
+volumes:
+ meshtastic_data:
diff --git a/platformio.ini b/platformio.ini
index 08d21665f..bf50b7646 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -81,7 +81,8 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_LORAWAN=1
-DMESHTASTIC_EXCLUDE_DROPZONE=1
-DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1
- -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
+ -DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
+ -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
#-DBUILD_EPOCH=$UNIX_TIME
;-D OLED_PL
@@ -153,11 +154,14 @@ lib_deps =
sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@1.2.13
ClosedCube OPT3001@1.1.2
emotibit/EmotiBit MLX90632@1.0.8
- sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2
adafruit/Adafruit MLX90614 Library@2.1.5
https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502
boschsensortec/BME68x Sensor Library@1.1.40407
https://github.com/KodinLanewave/INA3221@1.0.1
mprograms/QMC5883LCompass@1.2.3
dfrobot/DFRobot_RTU@1.0.3
- https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d
\ No newline at end of file
+ https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d
+ robtillaart/INA226@0.6.0
+
+ ; Health Sensor Libraries
+ sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2
\ No newline at end of file
diff --git a/protobufs b/protobufs
index 2cffaf53e..c55f120a9 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 2cffaf53e3faf1b6e41a8b8f05312f2f893be413
+Subproject commit c55f120a9c1ce90c85e4826907a0b9bcb2d5f5a2
diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h
index 55453ea1e..7987e7fa1 100644
--- a/src/DebugConfiguration.h
+++ b/src/DebugConfiguration.h
@@ -45,7 +45,7 @@
#define LOG_CRIT(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else
-#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) && !defined(PIO_UNIT_TESTING)
+#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
#define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__)
#define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__)
#define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__)
diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp
index 6cd17dac8..df46c1941 100644
--- a/src/FSCommon.cpp
+++ b/src/FSCommon.cpp
@@ -55,6 +55,15 @@ extern "C" void lfs_assert(const char *reason)
{
LOG_ERROR("LFS assert: %s", reason);
lfs_assert_failed = true;
+
+#ifndef ARCH_PORTDUINO
+#ifdef FSCom
+ // CORRUPTED FILESYSTEM. This causes bootloop so
+ // might as well try formatting now.
+ LOG_ERROR("Trying FSCom.format()");
+ FSCom.format();
+#endif
+#endif
}
/**
diff --git a/src/Power.cpp b/src/Power.cpp
index a354b74e2..ae0908ec6 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -72,8 +72,9 @@ static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
-INA260Sensor ina260Sensor;
INA219Sensor ina219Sensor;
+INA226Sensor ina226Sensor;
+INA260Sensor ina260Sensor;
INA3221Sensor ina3221Sensor;
#endif
@@ -413,7 +414,20 @@ class AnalogBatteryLevel : public HasBatteryLevel
#ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#else
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && \
+ !defined(DISABLE_INA_CHARGING_DETECTION)
+ if (hasINA()) {
+ // get current flow from INA sensor - negative value means power flowing into the battery
+ // default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD
+ LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
+#if defined(INA_CHARGING_DETECTION_INVERT)
+ return getINACurrent() > 0;
+#else
+ return getINACurrent() < 0;
+#endif
+ }
return isBatteryConnect() && isVbusIn();
+#endif
#endif
}
@@ -450,6 +464,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
{
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
return ina219Sensor.getBusVoltageMv();
+ } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA226].first ==
+ config.power.device_battery_ina_address) {
+ return ina226Sensor.getBusVoltageMv();
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
config.power.device_battery_ina_address) {
return ina260Sensor.getBusVoltageMv();
@@ -460,6 +477,20 @@ class AnalogBatteryLevel : public HasBatteryLevel
return 0;
}
+ int16_t getINACurrent()
+ {
+ if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
+ return ina219Sensor.getCurrentMa();
+ } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA226].first ==
+ config.power.device_battery_ina_address) {
+ return ina226Sensor.getCurrentMa();
+ } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first ==
+ config.power.device_battery_ina_address) {
+ return ina3221Sensor.getCurrentMa();
+ }
+ return 0;
+ }
+
bool hasINA()
{
if (!config.power.device_battery_ina_address) {
@@ -469,6 +500,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
if (!ina219Sensor.isInitialized())
return ina219Sensor.runOnce() > 0;
return ina219Sensor.isRunning();
+ } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA226].first ==
+ config.power.device_battery_ina_address) {
+ if (!ina226Sensor.isInitialized())
+ return ina226Sensor.runOnce() > 0;
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
config.power.device_battery_ina_address) {
if (!ina260Sensor.isInitialized())
@@ -1154,4 +1189,4 @@ bool Power::lipoInit()
{
return false;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/configuration.h b/src/configuration.h
index b5727508d..994f1e72e 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -250,6 +250,9 @@ along with this program. If not, see .
#ifndef HAS_SCREEN
#define HAS_SCREEN 0
#endif
+#ifndef HAS_TFT
+#define HAS_TFT 0
+#endif
#ifndef HAS_WIRE
#define HAS_WIRE 0
#endif
@@ -312,6 +315,7 @@ along with this program. If not, see .
#define MESHTASTIC_EXCLUDE_AUDIO 1
#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1
+#define MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY 1
#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1
#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1
#define MESHTASTIC_EXCLUDE_POWER_TELEMETRY 1
@@ -361,4 +365,4 @@ along with this program. If not, see .
#endif
#include "DebugConfiguration.h"
-#include "RF95Configuration.h"
\ No newline at end of file
+#include "RF95Configuration.h"
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 7fe3aac89..2561a8e17 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -63,7 +63,9 @@ class ScanI2C
MAX30102,
TPS65233,
MPR121KB,
- CGRADSENS
+ CGRADSENS,
+ INA226,
+ NXP_SE050,
} DeviceType;
// typedef uint8_t DeviceAddress;
@@ -127,4 +129,4 @@ class ScanI2C
private:
bool shouldSuppressScreen = false;
-};
\ No newline at end of file
+};
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 551b87d27..a786f874d 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -154,9 +154,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
i2cBus->beginTransmission(addr.address);
#ifdef ARCH_PORTDUINO
- if (i2cBus->read() != -1)
- err = 0;
- else
+ err = 2;
+ if ((addr.address >= 0x30 && addr.address <= 0x37) || (addr.address >= 0x50 && addr.address <= 0x5F)) {
+ if (i2cBus->read() != -1)
+ err = 0;
+ } else {
+ err = i2cBus->writeQuick((uint8_t)0);
+ }
+ if (err != 0)
err = 2;
#else
err = i2cBus->endTransmission();
@@ -250,8 +255,16 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
LOG_DEBUG("Register MFG_UID: 0x%x", registerValue);
if (registerValue == 0x5449) {
- logFoundDevice("INA260", (uint8_t)addr.address);
- type = INA260;
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 2);
+ LOG_DEBUG("Register DIE_UID: 0x%x", registerValue);
+
+ if (registerValue == 0x2260) {
+ logFoundDevice("INA226", (uint8_t)addr.address);
+ type = INA226;
+ } else {
+ logFoundDevice("INA260", (uint8_t)addr.address);
+ type = INA260;
+ }
} else { // Assume INA219 if INA260 ID is not found
logFoundDevice("INA219", (uint8_t)addr.address);
type = INA219;
@@ -388,7 +401,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
- SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
#ifdef HAS_TPS65233
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
@@ -436,6 +448,26 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
+ case 0x48: {
+ i2cBus->beginTransmission(addr.address);
+ uint8_t getInfo[] = {0x5A, 0xC0, 0x00, 0xFF, 0xFC};
+ uint8_t expectedInfo[] = {0xa5, 0xE0, 0x00, 0x3F, 0x19};
+ uint8_t info[5];
+ size_t len = 0;
+ i2cBus->write(getInfo, 5);
+ i2cBus->endTransmission();
+ len = i2cBus->readBytes(info, 5);
+ if (len == 5 && memcmp(expectedInfo, info, len) == 0) {
+ LOG_INFO("NXP SE050 crypto chip found");
+ type = NXP_SE050;
+
+ } else {
+ LOG_INFO("FT6336U touchscreen found");
+ type = FT6336U;
+ }
+ break;
+ }
+
default:
LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
}
@@ -478,4 +510,4 @@ void ScanI2CTwoWire::logFoundDevice(const char *device, uint8_t address)
{
LOG_INFO("%s found at address 0x%x", device, address);
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 2ab413bc5..31647c92d 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -126,7 +126,7 @@ static bool heartbeat = false;
/// Check if the display can render a string (detect special chars; emoji)
static bool haveGlyphs(const char *str)
{
-#if defined(OLED_PL) || defined(OLED_UA) || defined(OLED_RU)
+#if defined(OLED_PL) || defined(OLED_UA) || defined(OLED_RU) || defined(OLED_CS)
// Don't want to make any assumptions about custom language support
return true;
#endif
@@ -1015,7 +1015,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - devil_height) / 2 + 2 + 5, devil_width, devil_height, devil);
} else if (strcmp(msg, "♥️") == 0 || strcmp(msg, "\U0001F9E1") == 0 || strcmp(msg, "\U00002763") == 0 ||
strcmp(msg, "\U00002764") == 0 || strcmp(msg, "\U0001F495") == 0 || strcmp(msg, "\U0001F496") == 0 ||
- strcmp(msg, "\U0001F497") == 0 || strcmp(msg, "\U0001F496") == 0) {
+ strcmp(msg, "\U0001F497") == 0 || strcmp(msg, "\U0001F498") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - heart_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - heart_height) / 2 + 2 + 5, heart_width, heart_height, heart);
} else {
@@ -1718,7 +1718,7 @@ void Screen::setup()
#endif
serialSinceMsec = millis();
-#if ARCH_PORTDUINO
+#if ARCH_PORTDUINO && !HAS_TFT
if (settingsMap[touchscreenModule]) {
touchScreenImpl1 =
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch);
@@ -2756,4 +2756,4 @@ int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg)
} // namespace graphics
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
-#endif // HAS_SCREEN
+#endif // HAS_SCREEN
\ No newline at end of file
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 00884c5af..3cb39e8ec 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -427,6 +427,86 @@ class Screen : public concurrency::OSThread
if (ch == 0xC2 || ch == 0xC3 || ch == 0x82 || ch == 0xD0 || ch == 0xD1)
return (uint8_t)0;
+#endif
+
+#if defined(OLED_CS)
+
+ switch (last) {
+ case 0xC2: {
+ SKIPREST = false;
+ return (uint8_t)ch;
+ }
+
+ case 0xC3: {
+ SKIPREST = false;
+ return (uint8_t)(ch | 0xC0);
+ }
+
+ case 0xC4: {
+ SKIPREST = false;
+ if (ch == 140)
+ return (uint8_t)(129); // Č
+ if (ch == 141)
+ return (uint8_t)(138); // č
+ if (ch == 142)
+ return (uint8_t)(130); // Ď
+ if (ch == 143)
+ return (uint8_t)(139); // ď
+ if (ch == 154)
+ return (uint8_t)(131); // Ě
+ if (ch == 155)
+ return (uint8_t)(140); // ě
+ // Slovak specific glyphs
+ if (ch == 185)
+ return (uint8_t)(147); // Ĺ
+ if (ch == 186)
+ return (uint8_t)(148); // ĺ
+ if (ch == 189)
+ return (uint8_t)(149); // Ľ
+ if (ch == 190)
+ return (uint8_t)(150); // ľ
+ break;
+ }
+
+ case 0xC5: {
+ SKIPREST = false;
+ if (ch == 135)
+ return (uint8_t)(132); // Ň
+ if (ch == 136)
+ return (uint8_t)(141); // ň
+ if (ch == 152)
+ return (uint8_t)(133); // Ř
+ if (ch == 153)
+ return (uint8_t)(142); // ř
+ if (ch == 160)
+ return (uint8_t)(134); // Š
+ if (ch == 161)
+ return (uint8_t)(143); // š
+ if (ch == 164)
+ return (uint8_t)(135); // Ť
+ if (ch == 165)
+ return (uint8_t)(144); // ť
+ if (ch == 174)
+ return (uint8_t)(136); // Ů
+ if (ch == 175)
+ return (uint8_t)(145); // ů
+ if (ch == 189)
+ return (uint8_t)(137); // Ž
+ if (ch == 190)
+ return (uint8_t)(146); // ž
+ // Slovak specific glyphs
+ if (ch == 148)
+ return (uint8_t)(151); // Ŕ
+ if (ch == 149)
+ return (uint8_t)(152); // ŕ
+ break;
+ }
+ }
+
+ // We want to strip out prefix chars for two-byte char formats
+ if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5)
+ return (uint8_t)0;
+
#endif
// If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the
diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h
index 032348d54..81eb717cd 100644
--- a/src/graphics/ScreenFonts.h
+++ b/src/graphics/ScreenFonts.h
@@ -12,6 +12,10 @@
#include "graphics/fonts/OLEDDisplayFontsUA.h"
#endif
+#ifdef OLED_CS
+#include "graphics/fonts/OLEDDisplayFontsCS.h"
+#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(DISPLAY_FORCE_SMALL_FONTS)
@@ -29,21 +33,33 @@
#ifdef OLED_UA
#define FONT_SMALL ArialMT_Plain_10_UA // Height: 13
#else
+#ifdef OLED_CS
+#define FONT_SMALL ArialMT_Plain_10_CS
+#else
#define FONT_SMALL ArialMT_Plain_10 // Height: 13
#endif
#endif
#endif
+#endif
#ifdef OLED_UA
#define FONT_MEDIUM ArialMT_Plain_16_UA // Height: 19
#else
+#ifdef OLED_CS
+#define FONT_MEDIUM ArialMT_Plain_16_CS
+#else
#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
#endif
+#endif
#ifdef OLED_UA
#define FONT_LARGE ArialMT_Plain_24_UA // Height: 28
#else
+#ifdef OLED_CS
+#define FONT_LARGE ArialMT_Plain_24_CS // Height: 28
+#else
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
#endif
#endif
+#endif
#define _fontHeight(font) ((font)[1] + 1) // height is position 1
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 87c3f7de9..4f2af670b 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -347,7 +347,7 @@ static LGFX *tft = nullptr;
#include // Graphics and font library for ILI9342 driver chip
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
-#elif ARCH_PORTDUINO && HAS_SCREEN != 0
+#elif ARCH_PORTDUINO && HAS_SCREEN != 0 && !HAS_TFT
#include // Graphics and font library for ST7735 driver chip
class LGFX : public lgfx::LGFX_Device
diff --git a/src/graphics/fonts/OLEDDisplayFontsCS.cpp b/src/graphics/fonts/OLEDDisplayFontsCS.cpp
new file mode 100644
index 000000000..5c17e9177
--- /dev/null
+++ b/src/graphics/fonts/OLEDDisplayFontsCS.cpp
@@ -0,0 +1,1863 @@
+#include "OLEDDisplayFontsCS.h"
+
+// Font generated or edited with the glyphEditor
+const uint8_t ArialMT_Plain_10_CS[] PROGMEM = {
+ 0x0A, // Width: 10
+ 0x0D, // Height: 13
+ 0x20, // First char: 32
+ 0xE0, // Number of chars: 224
+ // Jump Table:
+ 0xFF, 0xFF, 0x00, 0x0A, // 32
+ 0x00, 0x00, 0x04, 0x03, // 33
+ 0x00, 0x04, 0x05, 0x04, // 34
+ 0x00, 0x09, 0x09, 0x06, // 35
+ 0x00, 0x12, 0x0A, 0x06, // 36
+ 0x00, 0x1C, 0x10, 0x09, // 37
+ 0x00, 0x2C, 0x0E, 0x08, // 38
+ 0x00, 0x3A, 0x01, 0x02, // 39
+ 0x00, 0x3B, 0x06, 0x04, // 40
+ 0x00, 0x41, 0x06, 0x04, // 41
+ 0x00, 0x47, 0x05, 0x04, // 42
+ 0x00, 0x4C, 0x09, 0x06, // 43
+ 0x00, 0x55, 0x04, 0x03, // 44
+ 0x00, 0x59, 0x03, 0x03, // 45
+ 0x00, 0x5C, 0x04, 0x03, // 46
+ 0x00, 0x60, 0x05, 0x04, // 47
+ 0x00, 0x65, 0x0A, 0x06, // 48
+ 0x00, 0x6F, 0x08, 0x05, // 49
+ 0x00, 0x77, 0x0A, 0x06, // 50
+ 0x00, 0x81, 0x0A, 0x06, // 51
+ 0x00, 0x8B, 0x0B, 0x07, // 52
+ 0x00, 0x96, 0x0A, 0x06, // 53
+ 0x00, 0xA0, 0x0A, 0x06, // 54
+ 0x00, 0xAA, 0x09, 0x06, // 55
+ 0x00, 0xB3, 0x0A, 0x06, // 56
+ 0x00, 0xBD, 0x0A, 0x06, // 57
+ 0x00, 0xC7, 0x04, 0x03, // 58
+ 0x00, 0xCB, 0x04, 0x03, // 59
+ 0x00, 0xCF, 0x0A, 0x06, // 60
+ 0x00, 0xD9, 0x09, 0x06, // 61
+ 0x00, 0xE2, 0x09, 0x06, // 62
+ 0x00, 0xEB, 0x0B, 0x07, // 63
+ 0x00, 0xF6, 0x14, 0x0B, // 64
+ 0x01, 0x0A, 0x0E, 0x08, // 65
+ 0x01, 0x18, 0x0C, 0x07, // 66
+ 0x01, 0x24, 0x0C, 0x07, // 67
+ 0x01, 0x30, 0x0B, 0x07, // 68
+ 0x01, 0x3B, 0x0C, 0x07, // 69
+ 0x01, 0x47, 0x09, 0x06, // 70
+ 0x01, 0x50, 0x0D, 0x08, // 71
+ 0x01, 0x5D, 0x0C, 0x07, // 72
+ 0x01, 0x69, 0x04, 0x03, // 73
+ 0x01, 0x6D, 0x08, 0x05, // 74
+ 0x01, 0x75, 0x0E, 0x08, // 75
+ 0x01, 0x83, 0x0C, 0x07, // 76
+ 0x01, 0x8F, 0x10, 0x09, // 77
+ 0x01, 0x9F, 0x0C, 0x07, // 78
+ 0x01, 0xAB, 0x0E, 0x08, // 79
+ 0x01, 0xB9, 0x0B, 0x07, // 80
+ 0x01, 0xC4, 0x0E, 0x08, // 81
+ 0x01, 0xD2, 0x0C, 0x07, // 82
+ 0x01, 0xDE, 0x0C, 0x07, // 83
+ 0x01, 0xEA, 0x0B, 0x07, // 84
+ 0x01, 0xF5, 0x0C, 0x07, // 85
+ 0x02, 0x01, 0x0D, 0x08, // 86
+ 0x02, 0x0E, 0x11, 0x0A, // 87
+ 0x02, 0x1F, 0x0E, 0x08, // 88
+ 0x02, 0x2D, 0x0D, 0x08, // 89
+ 0x02, 0x3A, 0x0C, 0x07, // 90
+ 0x02, 0x46, 0x06, 0x04, // 91
+ 0x02, 0x4C, 0x06, 0x04, // 92
+ 0x02, 0x52, 0x04, 0x03, // 93
+ 0x02, 0x56, 0x09, 0x06, // 94
+ 0x02, 0x5F, 0x0C, 0x07, // 95
+ 0x02, 0x6B, 0x03, 0x03, // 96
+ 0x02, 0x6E, 0x0A, 0x06, // 97
+ 0x02, 0x78, 0x0A, 0x06, // 98
+ 0x02, 0x82, 0x0A, 0x06, // 99
+ 0x02, 0x8C, 0x0A, 0x06, // 100
+ 0x02, 0x96, 0x0A, 0x06, // 101
+ 0x02, 0xA0, 0x05, 0x04, // 102
+ 0x02, 0xA5, 0x0A, 0x06, // 103
+ 0x02, 0xAF, 0x0A, 0x06, // 104
+ 0x02, 0xB9, 0x04, 0x03, // 105
+ 0x02, 0xBD, 0x04, 0x03, // 106
+ 0x02, 0xC1, 0x08, 0x05, // 107
+ 0x02, 0xC9, 0x04, 0x03, // 108
+ 0x02, 0xCD, 0x10, 0x09, // 109
+ 0x02, 0xDD, 0x0A, 0x06, // 110
+ 0x02, 0xE7, 0x0A, 0x06, // 111
+ 0x02, 0xF1, 0x0A, 0x06, // 112
+ 0x02, 0xFB, 0x0A, 0x06, // 113
+ 0x03, 0x05, 0x05, 0x04, // 114
+ 0x03, 0x0A, 0x08, 0x05, // 115
+ 0x03, 0x12, 0x06, 0x04, // 116
+ 0x03, 0x18, 0x0A, 0x06, // 117
+ 0x03, 0x22, 0x09, 0x06, // 118
+ 0x03, 0x2B, 0x0E, 0x08, // 119
+ 0x03, 0x39, 0x0A, 0x06, // 120
+ 0x03, 0x43, 0x09, 0x06, // 121
+ 0x03, 0x4C, 0x0A, 0x06, // 122
+ 0x03, 0x56, 0x06, 0x04, // 123
+ 0x03, 0x5C, 0x04, 0x03, // 124
+ 0x03, 0x60, 0x05, 0x04, // 125
+ 0x03, 0x65, 0x09, 0x06, // 126
+ 0xFF, 0xFF, 0x00, 0x0A, // 127
+ 0xFF, 0xFF, 0x00, 0x0A, // 128
+ 0x03, 0x6E, 0x0C, 0x07, // 129
+ 0x03, 0x7A, 0x0B, 0x07, // 130
+ 0x03, 0x85, 0x0C, 0x07, // 131
+ 0x03, 0x91, 0x0C, 0x07, // 132
+ 0x03, 0x9D, 0x0C, 0x07, // 133
+ 0x03, 0xA9, 0x0C, 0x07, // 134
+ 0x03, 0xB5, 0x0B, 0x07, // 135
+ 0x03, 0xC0, 0x0C, 0x07, // 136
+ 0x03, 0xCC, 0x0C, 0x07, // 137
+ 0x03, 0xD8, 0x0A, 0x06, // 138
+ 0x03, 0xE2, 0x0D, 0x08, // 139
+ 0x03, 0xEF, 0x0A, 0x06, // 140
+ 0x03, 0xF9, 0x0A, 0x06, // 141
+ 0x04, 0x03, 0x07, 0x05, // 142
+ 0x04, 0x0A, 0x08, 0x05, // 143
+ 0x04, 0x12, 0x07, 0x05, // 144
+ 0x04, 0x19, 0x0A, 0x06, // 145
+ 0x04, 0x23, 0x0A, 0x06, // 146
+ 0x04, 0x2D, 0x0C, 0x07, // 147
+ 0x04, 0x39, 0x05, 0x04, // 148
+ 0x04, 0x3E, 0x0C, 0x07, // 149
+ 0x04, 0x4A, 0x07, 0x05, // 150
+ 0x04, 0x51, 0x0C, 0x07, // 151
+ 0x04, 0x5D, 0x07, 0x05, // 152
+ 0xFF, 0xFF, 0x00, 0x0A, // 153
+ 0xFF, 0xFF, 0x00, 0x0A, // 154
+ 0xFF, 0xFF, 0x00, 0x0A, // 155
+ 0xFF, 0xFF, 0x00, 0x0A, // 156
+ 0xFF, 0xFF, 0x00, 0x0A, // 157
+ 0xFF, 0xFF, 0x00, 0x0A, // 158
+ 0xFF, 0xFF, 0x00, 0x0A, // 159
+ 0xFF, 0xFF, 0x00, 0x0A, // 160
+ 0x04, 0x64, 0x04, 0x03, // 161
+ 0x04, 0x68, 0x0A, 0x06, // 162
+ 0x04, 0x72, 0x0C, 0x07, // 163
+ 0x04, 0x7E, 0x0A, 0x06, // 164
+ 0x04, 0x88, 0x0A, 0x06, // 165
+ 0x04, 0x92, 0x04, 0x03, // 166
+ 0x04, 0x96, 0x0A, 0x06, // 167
+ 0x04, 0xA0, 0x05, 0x04, // 168
+ 0x04, 0xA5, 0x0D, 0x08, // 169
+ 0x04, 0xB2, 0x07, 0x05, // 170
+ 0x04, 0xB9, 0x0A, 0x06, // 171
+ 0x04, 0xC3, 0x09, 0x06, // 172
+ 0x04, 0xCC, 0x03, 0x03, // 173
+ 0x04, 0xCF, 0x0D, 0x08, // 174
+ 0x04, 0xDC, 0x0B, 0x07, // 175
+ 0x04, 0xE7, 0x07, 0x05, // 176
+ 0x04, 0xEE, 0x0A, 0x06, // 177
+ 0x04, 0xF8, 0x05, 0x04, // 178
+ 0x04, 0xFD, 0x05, 0x04, // 179
+ 0x05, 0x02, 0x05, 0x04, // 180
+ 0x05, 0x07, 0x0A, 0x06, // 181
+ 0x05, 0x11, 0x09, 0x06, // 182
+ 0x05, 0x1A, 0x03, 0x03, // 183
+ 0x05, 0x1D, 0x06, 0x04, // 184
+ 0x05, 0x23, 0x05, 0x04, // 185
+ 0x05, 0x28, 0x07, 0x05, // 186
+ 0x05, 0x2F, 0x0A, 0x06, // 187
+ 0x05, 0x39, 0x10, 0x09, // 188
+ 0x05, 0x49, 0x10, 0x09, // 189
+ 0x05, 0x59, 0x10, 0x09, // 190
+ 0x05, 0x69, 0x0A, 0x06, // 191
+ 0x05, 0x73, 0x0E, 0x08, // 192
+ 0x05, 0x81, 0x0E, 0x08, // 193
+ 0x05, 0x8F, 0x0E, 0x08, // 194
+ 0x05, 0x9D, 0x0E, 0x08, // 195
+ 0x05, 0xAB, 0x0E, 0x08, // 196
+ 0x05, 0xB9, 0x0E, 0x08, // 197
+ 0x05, 0xC7, 0x12, 0x0A, // 198
+ 0x05, 0xD9, 0x0C, 0x07, // 199
+ 0x05, 0xE5, 0x0C, 0x07, // 200
+ 0x05, 0xF1, 0x0C, 0x07, // 201
+ 0x05, 0xFD, 0x0C, 0x07, // 202
+ 0x06, 0x09, 0x0C, 0x07, // 203
+ 0x06, 0x15, 0x05, 0x04, // 204
+ 0x06, 0x1A, 0x04, 0x03, // 205
+ 0x06, 0x1E, 0x04, 0x03, // 206
+ 0x06, 0x22, 0x05, 0x04, // 207
+ 0x06, 0x27, 0x0B, 0x07, // 208
+ 0x06, 0x32, 0x0C, 0x07, // 209
+ 0x06, 0x3E, 0x0E, 0x08, // 210
+ 0x06, 0x4C, 0x0E, 0x08, // 211
+ 0x06, 0x5A, 0x0E, 0x08, // 212
+ 0x06, 0x68, 0x0E, 0x08, // 213
+ 0x06, 0x76, 0x0E, 0x08, // 214
+ 0x06, 0x84, 0x0A, 0x06, // 215
+ 0x06, 0x8E, 0x0D, 0x08, // 216
+ 0x06, 0x9B, 0x0C, 0x07, // 217
+ 0x06, 0xA7, 0x0C, 0x07, // 218
+ 0x06, 0xB3, 0x0C, 0x07, // 219
+ 0x06, 0xBF, 0x0C, 0x07, // 220
+ 0x06, 0xCB, 0x0D, 0x08, // 221
+ 0x06, 0xD8, 0x0B, 0x07, // 222
+ 0x06, 0xE3, 0x0C, 0x07, // 223
+ 0x06, 0xEF, 0x0A, 0x06, // 224
+ 0x06, 0xF9, 0x0A, 0x06, // 225
+ 0x07, 0x03, 0x0A, 0x06, // 226
+ 0x07, 0x0D, 0x0A, 0x06, // 227
+ 0x07, 0x17, 0x0A, 0x06, // 228
+ 0x07, 0x21, 0x0A, 0x06, // 229
+ 0x07, 0x2B, 0x10, 0x09, // 230
+ 0x07, 0x3B, 0x0A, 0x06, // 231
+ 0x07, 0x45, 0x0A, 0x06, // 232
+ 0x07, 0x4F, 0x0A, 0x06, // 233
+ 0x07, 0x59, 0x0A, 0x06, // 234
+ 0x07, 0x63, 0x0A, 0x06, // 235
+ 0x07, 0x6D, 0x05, 0x04, // 236
+ 0x07, 0x72, 0x04, 0x03, // 237
+ 0x07, 0x76, 0x05, 0x04, // 238
+ 0x07, 0x7B, 0x05, 0x04, // 239
+ 0x07, 0x80, 0x0A, 0x06, // 240
+ 0x07, 0x8A, 0x0A, 0x06, // 241
+ 0x07, 0x94, 0x0A, 0x06, // 242
+ 0x07, 0x9E, 0x0A, 0x06, // 243
+ 0x07, 0xA8, 0x0A, 0x06, // 244
+ 0x07, 0xB2, 0x0A, 0x06, // 245
+ 0x07, 0xBC, 0x0A, 0x06, // 246
+ 0x07, 0xC6, 0x09, 0x06, // 247
+ 0x07, 0xCF, 0x0A, 0x06, // 248
+ 0x07, 0xD9, 0x0A, 0x06, // 249
+ 0x07, 0xE3, 0x0A, 0x06, // 250
+ 0x07, 0xED, 0x0A, 0x06, // 251
+ 0x07, 0xF7, 0x0A, 0x06, // 252
+ 0x08, 0x01, 0x09, 0x06, // 253
+ 0x08, 0x0A, 0x0A, 0x06, // 254
+ 0x08, 0x14, 0x09, 0x06, // 255
+ // Font Data:
+ 0x00, 0x00, 0xF8, 0x02, // 33
+ 0x38, 0x00, 0x00, 0x00, 0x38, // 34
+ 0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35
+ 0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36
+ 0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37
+ 0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38
+ 0x38, // 39
+ 0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40
+ 0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41
+ 0x28, 0x00, 0x18, 0x00, 0x28, // 42
+ 0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43
+ 0x00, 0x00, 0x00, 0x06, // 44
+ 0x80, 0x00, 0x80, // 45
+ 0x00, 0x00, 0x00, 0x02, // 46
+ 0x00, 0x03, 0xE0, 0x00, 0x18, // 47
+ 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48
+ 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49
+ 0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50
+ 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51
+ 0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52
+ 0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53
+ 0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54
+ 0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55
+ 0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56
+ 0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57
+ 0x00, 0x00, 0x20, 0x02, // 58
+ 0x00, 0x00, 0x20, 0x06, // 59
+ 0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60
+ 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61
+ 0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62
+ 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63
+ 0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0, 0x04, // 64
+ 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65
+ 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66
+ 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67
+ 0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68
+ 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69
+ 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70
+ 0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71
+ 0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72
+ 0x00, 0x00, 0xF8, 0x03, // 73
+ 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74
+ 0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75
+ 0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76
+ 0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77
+ 0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78
+ 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79
+ 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80
+ 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81
+ 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82
+ 0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83
+ 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84
+ 0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85
+ 0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86
+ 0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87
+ 0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88
+ 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89
+ 0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90
+ 0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91
+ 0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92
+ 0x08, 0x08, 0xF8, 0x0F, // 93
+ 0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94
+ 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95
+ 0x08, 0x00, 0x10, // 96
+ 0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97
+ 0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98
+ 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99
+ 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100
+ 0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101
+ 0x20, 0x00, 0xF0, 0x03, 0x28, // 102
+ 0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103
+ 0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104
+ 0x00, 0x00, 0xE8, 0x03, // 105
+ 0x00, 0x08, 0xE8, 0x07, // 106
+ 0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107
+ 0x00, 0x00, 0xF8, 0x03, // 108
+ 0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109
+ 0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110
+ 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111
+ 0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112
+ 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113
+ 0x00, 0x00, 0xE0, 0x03, 0x20, // 114
+ 0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115
+ 0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116
+ 0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117
+ 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118
+ 0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119
+ 0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120
+ 0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121
+ 0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122
+ 0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123
+ 0x00, 0x00, 0xF8, 0x0F, // 124
+ 0x08, 0x08, 0x78, 0x0F, 0x80, // 125
+ 0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126
+ 0x00, 0x00, 0xF0, 0x01, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x10, 0x01, // 129
+ 0x00, 0x00, 0xF8, 0x03, 0x09, 0x02, 0x0A, 0x02, 0x11, 0x01, 0xE0, // 130
+ 0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 131
+ 0x00, 0x00, 0xF8, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 132
+ 0x00, 0x00, 0xF8, 0x03, 0x49, 0x00, 0x4A, 0x00, 0xC9, 0x00, 0x30, 0x03, // 133
+ 0x00, 0x00, 0x30, 0x01, 0x49, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x90, 0x01, // 134
+ 0x00, 0x00, 0x08, 0x00, 0x09, 0x00, 0xFA, 0x03, 0x09, 0x00, 0x08, // 135
+ 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x05, 0x02, 0x02, 0x02, 0xF8, 0x01, // 136
+ 0x08, 0x03, 0x88, 0x02, 0xC9, 0x02, 0x6A, 0x02, 0x39, 0x02, 0x18, 0x02, // 137
+ 0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0x44, 0x01, // 138
+ 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, 0x00, 0x00, 0x18, // 139
+ 0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC4, 0x02, // 140
+ 0x00, 0x00, 0xE0, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 141
+ 0x00, 0x00, 0xE4, 0x03, 0x28, 0x00, 0x04, // 142
+ 0x40, 0x02, 0xA4, 0x02, 0xA8, 0x02, 0x24, 0x01, // 143
+ 0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x18, // 144
+ 0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x14, 0x02, 0xE8, 0x03, // 145
+ 0x20, 0x02, 0x24, 0x03, 0xA8, 0x02, 0x64, 0x02, 0x20, 0x02, // 146
+ 0x00, 0x00, 0xFA, 0x03, 0x01, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 147
+ 0x00, 0x00, 0xFA, 0x03, 0x01, // 148
+ 0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x18, 0x02, 0x00, 0x02, // 149
+ 0x00, 0x00, 0xF8, 0x03, 0x00, 0x00, 0x18, // 150
+ 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x4A, 0x00, 0xC9, 0x00, 0x30, 0x03, // 151
+ 0x00, 0x00, 0xE0, 0x03, 0x28, 0x00, 0x04, // 152
+ 0x00, 0x00, 0xA0, 0x0F, // 161
+ 0x00, 0x00, 0xC0, 0x01, 0xA0, 0x0F, 0x78, 0x02, 0x40, 0x01, // 162
+ 0x40, 0x02, 0x70, 0x03, 0xC8, 0x02, 0x48, 0x02, 0x08, 0x02, 0x10, 0x02, // 163
+ 0x00, 0x00, 0xE0, 0x01, 0x20, 0x01, 0x20, 0x01, 0xE0, 0x01, // 164
+ 0x48, 0x01, 0x70, 0x01, 0xC0, 0x03, 0x70, 0x01, 0x48, 0x01, // 165
+ 0x00, 0x00, 0x38, 0x0F, // 166
+ 0xD0, 0x04, 0x28, 0x09, 0x48, 0x09, 0x48, 0x0A, 0x90, 0x05, // 167
+ 0x08, 0x00, 0x00, 0x00, 0x08, // 168
+ 0xE0, 0x00, 0x10, 0x01, 0x48, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0x10, 0x01, 0xE0, // 169
+ 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x78, // 170
+ 0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, // 171
+ 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xE0, // 172
+ 0x80, 0x00, 0x80, // 173
+ 0xE0, 0x00, 0x10, 0x01, 0xE8, 0x02, 0x68, 0x02, 0xC8, 0x02, 0x10, 0x01, 0xE0, // 174
+ 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 175
+ 0x00, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, // 176
+ 0x40, 0x02, 0x40, 0x02, 0xF0, 0x03, 0x40, 0x02, 0x40, 0x02, // 177
+ 0x48, 0x00, 0x68, 0x00, 0x58, // 178
+ 0x48, 0x00, 0x58, 0x00, 0x68, // 179
+ 0x00, 0x00, 0x10, 0x00, 0x08, // 180
+ 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 181
+ 0x70, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0xF8, 0x0F, 0x08, // 182
+ 0x00, 0x00, 0x40, // 183
+ 0x00, 0x00, 0x00, 0x14, 0x00, 0x18, // 184
+ 0x00, 0x00, 0x10, 0x00, 0x78, // 185
+ 0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 186
+ 0x00, 0x00, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, // 187
+ 0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0xC0, 0x00, 0x20, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 188
+ 0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0x80, 0x00, 0x60, 0x00, 0x50, 0x02, 0x48, 0x03, 0xC0, 0x02, // 189
+ 0x48, 0x00, 0x58, 0x00, 0x68, 0x03, 0x80, 0x00, 0x60, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 190
+ 0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0xA0, 0x09, 0x00, 0x04, // 191
+ 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 192
+ 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 193
+ 0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 194
+ 0x00, 0x02, 0xC2, 0x01, 0xB1, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 195
+ 0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x88, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 196
+ 0x00, 0x02, 0xC0, 0x01, 0xBE, 0x00, 0x8A, 0x00, 0xBE, 0x00, 0xC0, 0x01, 0x00, 0x02, // 197
+ 0x00, 0x03, 0xC0, 0x00, 0xE0, 0x00, 0x98, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 198
+ 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x16, 0x08, 0x1A, 0x10, 0x01, // 199
+ 0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 200
+ 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 201
+ 0x00, 0x00, 0xFA, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 202
+ 0x00, 0x00, 0xF8, 0x03, 0x4A, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x48, 0x02, // 203
+ 0x00, 0x00, 0xF9, 0x03, 0x02, // 204
+ 0x02, 0x00, 0xF9, 0x03, // 205
+ 0x01, 0x00, 0xFA, 0x03, // 206
+ 0x02, 0x00, 0xF8, 0x03, 0x02, // 207
+ 0x40, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x10, 0x01, 0xE0, // 208
+ 0x00, 0x00, 0xFA, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 209
+ 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 210
+ 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x08, 0x02, 0xF0, 0x01, // 211
+ 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0xF0, 0x01, // 212
+ 0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 213
+ 0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 214
+ 0x10, 0x01, 0xA0, 0x00, 0xE0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 215
+ 0x00, 0x00, 0xF0, 0x02, 0x08, 0x03, 0xC8, 0x02, 0x28, 0x02, 0x18, 0x03, 0xE8, // 216
+ 0x00, 0x00, 0xF8, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0xF8, 0x01, // 217
+ 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0xF8, 0x01, // 218
+ 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0xF8, 0x01, // 219
+ 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0xF8, 0x01, // 220
+ 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC2, 0x03, 0x21, 0x00, 0x10, 0x00, 0x08, // 221
+ 0x00, 0x00, 0xF8, 0x03, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222
+ 0x00, 0x00, 0xF0, 0x03, 0x08, 0x01, 0x48, 0x02, 0xB0, 0x02, 0x80, 0x01, // 223
+ 0x00, 0x00, 0x00, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE0, 0x03, // 224
+ 0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE0, 0x03, // 225
+ 0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE8, 0x03, // 226
+ 0x00, 0x00, 0x08, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE4, 0x03, // 227
+ 0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA0, 0x02, 0xE8, 0x03, // 228
+ 0x00, 0x00, 0x00, 0x03, 0xAE, 0x02, 0xAA, 0x02, 0xEE, 0x03, // 229
+ 0x00, 0x00, 0x40, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 230
+ 0x00, 0x00, 0xC0, 0x01, 0x20, 0x16, 0x20, 0x1A, 0x40, 0x01, // 231
+ 0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC0, 0x02, // 232
+ 0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC0, 0x02, // 233
+ 0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC8, 0x02, // 234
+ 0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA0, 0x02, 0xC8, 0x02, // 235
+ 0x00, 0x00, 0xE4, 0x03, 0x08, // 236
+ 0x08, 0x00, 0xE4, 0x03, // 237
+ 0x08, 0x00, 0xE4, 0x03, 0x08, // 238
+ 0x08, 0x00, 0xE0, 0x03, 0x08, // 239
+ 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x38, 0x02, 0xE0, 0x01, // 240
+ 0x00, 0x00, 0xE8, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 241
+ 0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC0, 0x01, // 242
+ 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC0, 0x01, // 243
+ 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC8, 0x01, // 244
+ 0x00, 0x00, 0xC8, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC4, 0x01, // 245
+ 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x20, 0x02, 0xC8, 0x01, // 246
+ 0x40, 0x00, 0x40, 0x00, 0x50, 0x01, 0x40, 0x00, 0x40, // 247
+ 0x00, 0x00, 0xC0, 0x02, 0xA0, 0x03, 0x60, 0x02, 0xA0, 0x01, // 248
+ 0x00, 0x00, 0xE0, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 249
+ 0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x04, 0x02, 0xE0, 0x03, // 250
+ 0x00, 0x00, 0xE8, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 251
+ 0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x00, 0x02, 0xE8, 0x03, // 252
+ 0x20, 0x00, 0xC0, 0x09, 0x08, 0x06, 0xC4, 0x01, 0x20, // 253
+ 0x00, 0x00, 0xF8, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 254
+ 0x20, 0x00, 0xC8, 0x09, 0x00, 0x06, 0xC8, 0x01, 0x20, // 255
+};
+
+const uint8_t ArialMT_Plain_16_CS[] PROGMEM = {
+ 0x10, // Width: 16
+ 0x13, // Height: 19
+ 0x20, // First char: 32
+ 0xE0, // Number of chars: 224
+ // Jump Table:
+ 0xFF, 0xFF, 0x00, 0x10, // 32
+ 0x00, 0x00, 0x08, 0x04, // 33
+ 0x00, 0x08, 0x0D, 0x06, // 34
+ 0x00, 0x15, 0x1A, 0x0A, // 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, 0x05, // 47
+ 0x00, 0xEE, 0x17, 0x09, // 48
+ 0x01, 0x05, 0x11, 0x07, // 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, 0x03, // 58
+ 0x01, 0xD2, 0x06, 0x03, // 59
+ 0x01, 0xD8, 0x17, 0x09, // 60
+ 0x01, 0xEF, 0x17, 0x09, // 61
+ 0x02, 0x06, 0x17, 0x09, // 62
+ 0x02, 0x1D, 0x16, 0x09, // 63
+ 0x02, 0x33, 0x2F, 0x11, // 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, 0x0A, // 84
+ 0x04, 0x8B, 0x1D, 0x0B, // 85
+ 0x04, 0xA8, 0x1C, 0x0B, // 86
+ 0x04, 0xC4, 0x2B, 0x10, // 87
+ 0x04, 0xEF, 0x20, 0x0C, // 88
+ 0x05, 0x0F, 0x19, 0x0A, // 89
+ 0x05, 0x28, 0x1A, 0x0A, // 90
+ 0x05, 0x42, 0x0C, 0x05, // 91
+ 0x05, 0x4E, 0x0B, 0x05, // 92
+ 0x05, 0x59, 0x09, 0x04, // 93
+ 0x05, 0x62, 0x14, 0x08, // 94
+ 0x05, 0x76, 0x1B, 0x0A, // 95
+ 0x05, 0x91, 0x07, 0x04, // 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, 0x05, // 102
+ 0x06, 0x12, 0x17, 0x09, // 103
+ 0x06, 0x29, 0x14, 0x08, // 104
+ 0x06, 0x3D, 0x05, 0x03, // 105
+ 0x06, 0x42, 0x06, 0x03, // 106
+ 0x06, 0x48, 0x17, 0x09, // 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, 0x06, // 114
+ 0x06, 0xEE, 0x14, 0x08, // 115
+ 0x07, 0x02, 0x0B, 0x05, // 116
+ 0x07, 0x0D, 0x14, 0x08, // 117
+ 0x07, 0x21, 0x13, 0x08, // 118
+ 0x07, 0x34, 0x1F, 0x0C, // 119
+ 0x07, 0x53, 0x14, 0x08, // 120
+ 0x07, 0x67, 0x13, 0x08, // 121
+ 0x07, 0x7A, 0x14, 0x08, // 122
+ 0x07, 0x8E, 0x0F, 0x06, // 123
+ 0x07, 0x9D, 0x06, 0x03, // 124
+ 0x07, 0xA3, 0x0E, 0x06, // 125
+ 0x07, 0xB1, 0x17, 0x09, // 126
+ 0xFF, 0xFF, 0x00, 0x10, // 127
+ 0xFF, 0xFF, 0x00, 0x10, // 128
+ 0x07, 0xC8, 0x20, 0x0C, // 129
+ 0x07, 0xE8, 0x20, 0x0C, // 130
+ 0x08, 0x08, 0x1D, 0x0B, // 131
+ 0x08, 0x25, 0x1D, 0x0B, // 132
+ 0x08, 0x42, 0x1D, 0x0B, // 133
+ 0x08, 0x5F, 0x1D, 0x0B, // 134
+ 0x08, 0x7C, 0x19, 0x0A, // 135
+ 0x08, 0x95, 0x1D, 0x0B, // 136
+ 0x08, 0xB2, 0x1A, 0x0A, // 137
+ 0x08, 0xCC, 0x14, 0x08, // 138
+ 0x08, 0xE0, 0x1C, 0x0B, // 139
+ 0x08, 0xFC, 0x17, 0x09, // 140
+ 0x09, 0x13, 0x14, 0x08, // 141
+ 0x09, 0x27, 0x0D, 0x06, // 142
+ 0x09, 0x34, 0x14, 0x08, // 143
+ 0x09, 0x48, 0x10, 0x07, // 144
+ 0x09, 0x58, 0x14, 0x08, // 145
+ 0x09, 0x6C, 0x14, 0x08, // 146
+ 0x09, 0x80, 0x17, 0x09, // 147
+ 0x09, 0x97, 0x07, 0x04, // 148
+ 0x09, 0x9E, 0x17, 0x09, // 149
+ 0x09, 0xB5, 0x0A, 0x05, // 150
+ 0x09, 0xBF, 0x1D, 0x0B, // 151
+ 0x09, 0xDC, 0x0D, 0x06, // 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
+ 0x09, 0xE9, 0x09, 0x04, // 161
+ 0x09, 0xF2, 0x17, 0x09, // 162
+ 0x0A, 0x09, 0x17, 0x09, // 163
+ 0x0A, 0x20, 0x14, 0x08, // 164
+ 0x0A, 0x34, 0x1A, 0x0A, // 165
+ 0x0A, 0x4E, 0x06, 0x03, // 166
+ 0x0A, 0x54, 0x17, 0x09, // 167
+ 0x0A, 0x6B, 0x07, 0x04, // 168
+ 0x0A, 0x72, 0x23, 0x0D, // 169
+ 0x0A, 0x95, 0x0E, 0x06, // 170
+ 0x0A, 0xA3, 0x14, 0x08, // 171
+ 0x0A, 0xB7, 0x17, 0x09, // 172
+ 0x0A, 0xCE, 0x0B, 0x05, // 173
+ 0x0A, 0xD9, 0x23, 0x0D, // 174
+ 0x0A, 0xFC, 0x19, 0x0A, // 175
+ 0x0B, 0x15, 0x0D, 0x06, // 176
+ 0x0B, 0x22, 0x17, 0x09, // 177
+ 0x0B, 0x39, 0x0E, 0x06, // 178
+ 0x0B, 0x47, 0x0D, 0x06, // 179
+ 0x0B, 0x54, 0x0A, 0x05, // 180
+ 0x0B, 0x5E, 0x17, 0x09, // 181
+ 0x0B, 0x75, 0x19, 0x0A, // 182
+ 0x0B, 0x8E, 0x08, 0x04, // 183
+ 0x0B, 0x96, 0x0C, 0x05, // 184
+ 0x0B, 0xA2, 0x0B, 0x05, // 185
+ 0x0B, 0xAD, 0x0D, 0x06, // 186
+ 0x0B, 0xBA, 0x17, 0x09, // 187
+ 0x0B, 0xD1, 0x26, 0x0E, // 188
+ 0x0B, 0xF7, 0x26, 0x0E, // 189
+ 0x0C, 0x1D, 0x26, 0x0E, // 190
+ 0x0C, 0x43, 0x1A, 0x0A, // 191
+ 0x0C, 0x5D, 0x1D, 0x0B, // 192
+ 0x0C, 0x7A, 0x1D, 0x0B, // 193
+ 0x0C, 0x97, 0x1D, 0x0B, // 194
+ 0x0C, 0xB4, 0x1D, 0x0B, // 195
+ 0x0C, 0xD1, 0x1D, 0x0B, // 196
+ 0x0C, 0xEE, 0x1D, 0x0B, // 197
+ 0x0D, 0x0B, 0x2C, 0x10, // 198
+ 0x0D, 0x37, 0x20, 0x0C, // 199
+ 0x0D, 0x57, 0x1D, 0x0B, // 200
+ 0x0D, 0x74, 0x1D, 0x0B, // 201
+ 0x0D, 0x91, 0x1D, 0x0B, // 202
+ 0x0D, 0xAE, 0x1D, 0x0B, // 203
+ 0x0D, 0xCB, 0x05, 0x03, // 204
+ 0x0D, 0xD0, 0x07, 0x04, // 205
+ 0x0D, 0xD7, 0x0A, 0x05, // 206
+ 0x0D, 0xE1, 0x07, 0x04, // 207
+ 0x0D, 0xE8, 0x20, 0x0C, // 208
+ 0x0E, 0x08, 0x1D, 0x0B, // 209
+ 0x0E, 0x25, 0x20, 0x0C, // 210
+ 0x0E, 0x45, 0x20, 0x0C, // 211
+ 0x0E, 0x65, 0x20, 0x0C, // 212
+ 0x0E, 0x85, 0x20, 0x0C, // 213
+ 0x0E, 0xA5, 0x20, 0x0C, // 214
+ 0x0E, 0xC5, 0x17, 0x09, // 215
+ 0x0E, 0xDC, 0x20, 0x0C, // 216
+ 0x0E, 0xFC, 0x1D, 0x0B, // 217
+ 0x0F, 0x19, 0x1D, 0x0B, // 218
+ 0x0F, 0x36, 0x1D, 0x0B, // 219
+ 0x0F, 0x53, 0x1D, 0x0B, // 220
+ 0x0F, 0x70, 0x19, 0x0A, // 221
+ 0x0F, 0x89, 0x1D, 0x0B, // 222
+ 0x0F, 0xA6, 0x17, 0x09, // 223
+ 0x0F, 0xBD, 0x17, 0x09, // 224
+ 0x0F, 0xD4, 0x17, 0x09, // 225
+ 0x0F, 0xEB, 0x17, 0x09, // 226
+ 0x10, 0x02, 0x17, 0x09, // 227
+ 0x10, 0x19, 0x17, 0x09, // 228
+ 0x10, 0x30, 0x17, 0x09, // 229
+ 0x10, 0x47, 0x29, 0x0F, // 230
+ 0x10, 0x70, 0x14, 0x08, // 231
+ 0x10, 0x84, 0x17, 0x09, // 232
+ 0x10, 0x9B, 0x17, 0x09, // 233
+ 0x10, 0xB2, 0x17, 0x09, // 234
+ 0x10, 0xC9, 0x17, 0x09, // 235
+ 0x10, 0xE0, 0x05, 0x03, // 236
+ 0x10, 0xE5, 0x07, 0x04, // 237
+ 0x10, 0xEC, 0x0A, 0x05, // 238
+ 0x10, 0xF6, 0x07, 0x04, // 239
+ 0x10, 0xFD, 0x17, 0x09, // 240
+ 0x11, 0x14, 0x14, 0x08, // 241
+ 0x11, 0x28, 0x17, 0x09, // 242
+ 0x11, 0x3F, 0x17, 0x09, // 243
+ 0x11, 0x56, 0x17, 0x09, // 244
+ 0x11, 0x6D, 0x17, 0x09, // 245
+ 0x11, 0x84, 0x17, 0x09, // 246
+ 0x11, 0x9B, 0x17, 0x09, // 247
+ 0x11, 0xB2, 0x17, 0x09, // 248
+ 0x11, 0xC9, 0x14, 0x08, // 249
+ 0x11, 0xDD, 0x14, 0x08, // 250
+ 0x11, 0xF1, 0x14, 0x08, // 251
+ 0x12, 0x05, 0x14, 0x08, // 252
+ 0x12, 0x19, 0x13, 0x08, // 253
+ 0x12, 0x2C, 0x17, 0x09, // 254
+ 0x12, 0x43, 0x13, 0x08, // 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, 0x10, 0x22, 0x00,
+ 0x20, 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, 0x10, 0x43, 0x00,
+ 0xE0, 0x40, // 50
+ 0x00, 0x00, 0x00, 0x20, 0x10, 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, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x78, 0x00, 0x08, 0x07, 0x00, 0xC8, 0x00, 0x00, 0x28, 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, 0x4F, // 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, 0x20, 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, 0xE0, 0x01, 0x00, 0x38, 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, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x09, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x0A, 0x40, 0x00,
+ 0x09, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, // 129
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x09, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x0A, 0x40, 0x00,
+ 0x09, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 130
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x09, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x0A, 0x41, 0x00,
+ 0x09, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 131
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x10, 0x00, 0x00, 0x60, 0x00, 0x00, 0x81, 0x00, 0x00, 0x02, 0x03, 0x00, 0x02, 0x04, 0x00,
+ 0x01, 0x18, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x7F, // 132
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x09, 0x02, 0x00, 0x0A, 0x02, 0x00, 0x0A, 0x06, 0x00,
+ 0x09, 0x1A, 0x00, 0x10, 0x21, 0x00, 0xE0, 0x40, // 133
+ 0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x90, 0x20, 0x00, 0x08, 0x41, 0x00, 0x09, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x0A, 0x42, 0x00,
+ 0x09, 0x42, 0x00, 0x10, 0x22, 0x00, 0x20, 0x1C, // 134
+ 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x09, 0x00, 0x00, 0xFA, 0x7F, 0x00, 0x0A, 0x00, 0x00, 0x09, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x08, // 135
+ 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x06, 0x40, 0x00, 0x09, 0x40, 0x00, 0x09, 0x40, 0x00,
+ 0x06, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 136
+ 0x00, 0x40, 0x00, 0x08, 0x60, 0x00, 0x08, 0x58, 0x00, 0x09, 0x44, 0x00, 0x0A, 0x43, 0x00, 0x8A, 0x40, 0x00, 0x69, 0x40, 0x00,
+ 0x18, 0x40, 0x00, 0x08, 0x40, // 137
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x48, 0x40, 0x00, 0x50, 0x40, 0x00, 0x50, 0x40, 0x00, 0x88, 0x20, // 138
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0xF8, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x38, // 139
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x48, 0x44, 0x00, 0x50, 0x44, 0x00, 0x50, 0x44, 0x00, 0x88, 0x24, 0x00,
+ 0x00, 0x17, // 140
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x88, 0x00, 0x00, 0x50, 0x00, 0x00, 0x50, 0x00, 0x00, 0x48, 0x00, 0x00, 0x80, 0x7F, // 141
+ 0x00, 0x00, 0x00, 0xC8, 0x7F, 0x00, 0x90, 0x00, 0x00, 0x50, 0x00, 0x00, 0x48, // 142
+ 0x00, 0x00, 0x00, 0x80, 0x23, 0x00, 0x48, 0x44, 0x00, 0x50, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x80, 0x38, // 143
+ 0x40, 0x00, 0x00, 0xF0, 0x7F, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x70, // 144
+ 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x0C, 0x40, 0x00, 0x12, 0x40, 0x00, 0x12, 0x40, 0x00, 0x0C, 0x20, 0x00, 0xC0, 0x7F, // 145
+ 0x40, 0x40, 0x00, 0x40, 0x60, 0x00, 0x48, 0x58, 0x00, 0x50, 0x44, 0x00, 0x50, 0x43, 0x00, 0xC8, 0x40, 0x00, 0x40, 0x40, // 146
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x02, 0x40, 0x00, 0x01, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, // 147
+ 0x00, 0x00, 0x00, 0xFA, 0x7F, 0x00, 0x01, // 148
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x38, 0x40, 0x00,
+ 0x00, 0x40, // 149
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x38, // 150
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x0A, 0x02, 0x00, 0x09, 0x06, 0x00,
+ 0x08, 0x1A, 0x00, 0x10, 0x21, 0x00, 0xE0, 0x40, // 151
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x50, 0x00, 0x00, 0x48, // 152
+ 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, 0xE0, 0x31, 0x00, 0x10, 0x2F, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 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
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // 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, 0x00, 0x02, 0x00, 0x80, 0x02, 0x00, 0x00, 0x03, // 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, 0xE0, 0x00, 0x00, 0x10, 0x01, 0x00, 0x08, 0x02, 0x40, 0x07, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x01, 0x00, 0xC0, // 191
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x71, 0x04, 0x00, 0x0A, 0x04, 0x00, 0x70, 0x04, 0x00,
+ 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 192
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x70, 0x04, 0x00, 0x0A, 0x04, 0x00, 0x71, 0x04, 0x00,
+ 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 193
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x72, 0x04, 0x00, 0x09, 0x04, 0x00, 0x71, 0x04, 0x00,
+ 0x82, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 194
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x72, 0x04, 0x00, 0x09, 0x04, 0x00, 0x72, 0x04, 0x00,
+ 0x81, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 195
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x72, 0x04, 0x00, 0x08, 0x04, 0x00, 0x72, 0x04, 0x00,
+ 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 196
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x7E, 0x04, 0x00, 0x0A, 0x04, 0x00, 0x7E, 0x04, 0x00,
+ 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 197
+ 0x00, 0x60, 0x00, 0x00, 0x18, 0x00, 0x00, 0x06, 0x00, 0x80, 0x05, 0x00, 0x60, 0x04, 0x00, 0x18, 0x04, 0x00, 0x08, 0x04, 0x00,
+ 0x08, 0x04, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00,
+ 0x08, 0x41, // 198
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x02, 0x08, 0xC0, 0x02,
+ 0x08, 0x40, 0x03, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, // 199
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x09, 0x41, 0x00, 0x0A, 0x41, 0x00,
+ 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 200
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x09, 0x41, 0x00,
+ 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 201
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x09, 0x41, 0x00, 0x09, 0x41, 0x00,
+ 0x0A, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 202
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x08, 0x41, 0x00, 0x0A, 0x41, 0x00,
+ 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 203
+ 0x01, 0x00, 0x00, 0xFA, 0x7F, // 204
+ 0x00, 0x00, 0x00, 0xFA, 0x7F, 0x00, 0x01, // 205
+ 0x02, 0x00, 0x00, 0xF9, 0x7F, 0x00, 0x01, 0x00, 0x00, 0x02, // 206
+ 0x02, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x02, // 207
+ 0x00, 0x02, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x40, 0x00,
+ 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 208
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x10, 0x00, 0x00, 0x60, 0x00, 0x00, 0x82, 0x00, 0x00, 0x01, 0x03, 0x00, 0x02, 0x04, 0x00,
+ 0x01, 0x18, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x7F, // 209
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x09, 0x40, 0x00, 0x0A, 0x40, 0x00,
+ 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 210
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x09, 0x40, 0x00,
+ 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 211
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x0A, 0x40, 0x00, 0x09, 0x40, 0x00, 0x09, 0x40, 0x00,
+ 0x0A, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 212
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x0A, 0x40, 0x00, 0x09, 0x40, 0x00, 0x0A, 0x40, 0x00,
+ 0x09, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 213
+ 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x08, 0x40, 0x00,
+ 0x0A, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 214
+ 0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, 0x07, 0x00, 0x00, 0x05, 0x00, 0x80, 0x08, 0x00,
+ 0x40, 0x10, // 215
+ 0x00, 0x00, 0x00, 0xC0, 0x4F, 0x00, 0x20, 0x30, 0x00, 0x10, 0x30, 0x00, 0x08, 0x4C, 0x00, 0x08, 0x42, 0x00, 0x08, 0x41, 0x00,
+ 0xC8, 0x40, 0x00, 0x30, 0x20, 0x00, 0x30, 0x10, 0x00, 0xC8, 0x0F, // 216
+ 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x01, 0x40, 0x00, 0x02, 0x40, 0x00, 0x00, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 217
+ 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x02, 0x40, 0x00, 0x01, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 218
+ 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x02, 0x40, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00,
+ 0x02, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 219
+ 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x02, 0x40, 0x00, 0x00, 0x40, 0x00, 0x02, 0x40, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 220
+ 0x08, 0x00, 0x00, 0x30, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x01, 0x00, 0x02, 0x7E, 0x00, 0x81, 0x01, 0x00, 0x40, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x08, // 221
+ 0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00,
+ 0x20, 0x10, 0x00, 0x40, 0x08, 0x00, 0x80, 0x07, // 222
+ 0x00, 0x00, 0x00, 0xE0, 0x7F, 0x00, 0x10, 0x00, 0x00, 0x08, 0x20, 0x00, 0x88, 0x43, 0x00, 0x70, 0x42, 0x00, 0x00, 0x44, 0x00,
+ 0x00, 0x38, // 223
+ 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x48, 0x44, 0x00, 0x50, 0x42, 0x00, 0x40, 0x22, 0x00,
+ 0x80, 0x7F, // 224
+ 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x42, 0x00, 0x40, 0x22, 0x00,
+ 0x80, 0x7F, // 225
+ 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x48, 0x42, 0x00, 0x50, 0x22, 0x00,
+ 0x80, 0x7F, // 226
+ 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x50, 0x42, 0x00, 0x48, 0x22, 0x00,
+ 0x80, 0x7F, // 227
+ 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x50, 0x44, 0x00, 0x40, 0x44, 0x00, 0x50, 0x42, 0x00, 0x40, 0x22, 0x00,
+ 0x80, 0x7F, // 228
+ 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x5C, 0x44, 0x00, 0x54, 0x44, 0x00, 0x5C, 0x42, 0x00, 0x40, 0x22, 0x00,
+ 0x80, 0x7F, // 229
+ 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x42, 0x00, 0x40, 0x22, 0x00,
+ 0x80, 0x3F, 0x00, 0x80, 0x24, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x24, 0x00, 0x00, 0x17, // 230
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x02, 0x40, 0xC0, 0x02, 0x40, 0x40, 0x03, 0x80, 0x20, // 231
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x48, 0x44, 0x00, 0x50, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x24, 0x00,
+ 0x00, 0x17, // 232
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x40, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x80, 0x24, 0x00,
+ 0x00, 0x17, // 233
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x48, 0x44, 0x00, 0x90, 0x24, 0x00,
+ 0x00, 0x17, // 234
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x50, 0x44, 0x00, 0x40, 0x44, 0x00, 0x50, 0x44, 0x00, 0x80, 0x24, 0x00,
+ 0x00, 0x17, // 235
+ 0x08, 0x00, 0x00, 0xD0, 0x7F, // 236
+ 0x00, 0x00, 0x00, 0xD0, 0x7F, 0x00, 0x08, // 237
+ 0x10, 0x00, 0x00, 0xC8, 0x7F, 0x00, 0x08, 0x00, 0x00, 0x10, // 238
+ 0x10, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x10, // 239
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0xA0, 0x20, 0x00, 0x68, 0x40, 0x00, 0x58, 0x40, 0x00, 0x70, 0x40, 0x00, 0xE8, 0x20, 0x00,
+ 0x00, 0x1F, // 240
+ 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x90, 0x00, 0x00, 0x48, 0x00, 0x00, 0x50, 0x00, 0x00, 0x48, 0x00, 0x00, 0x80, 0x7F, // 241
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x48, 0x40, 0x00, 0x50, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0x00, 0x1F, // 242
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x50, 0x40, 0x00, 0x48, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0x00, 0x1F, // 243
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x50, 0x40, 0x00, 0x48, 0x40, 0x00, 0x48, 0x40, 0x00, 0x90, 0x20, 0x00,
+ 0x00, 0x1F, // 244
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x50, 0x40, 0x00, 0x48, 0x40, 0x00, 0x50, 0x40, 0x00, 0x88, 0x20, 0x00,
+ 0x00, 0x1F, // 245
+ 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x50, 0x40, 0x00, 0x40, 0x40, 0x00, 0x50, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0x00, 0x1F, // 246
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x80, 0x0A, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x02, // 247
+ 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x80, 0x30, 0x00, 0x40, 0x48, 0x00, 0x40, 0x44, 0x00, 0x40, 0x42, 0x00, 0x80, 0x21, 0x00,
+ 0x40, 0x1F, // 248
+ 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x40, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x7F, // 249
+ 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x10, 0x40, 0x00, 0x08, 0x20, 0x00, 0xC0, 0x7F, // 250
+ 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x10, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0xC0, 0x7F, // 251
+ 0x00, 0x00, 0x00, 0xD0, 0x3F, 0x00, 0x00, 0x40, 0x00, 0x10, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x7F, // 252
+ 0xC0, 0x01, 0x00, 0x00, 0x06, 0x02, 0x00, 0x38, 0x02, 0x10, 0xE0, 0x01, 0x08, 0x38, 0x00, 0x00, 0x07, 0x00, 0xC0, // 253
+ 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x03, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00,
+ 0x00, 0x1F, // 254
+ 0xC0, 0x01, 0x00, 0x00, 0x06, 0x02, 0x10, 0x38, 0x02, 0x00, 0xE0, 0x01, 0x10, 0x38, 0x00, 0x00, 0x07, 0x00, 0xC0, // 255
+};
+
+const uint8_t ArialMT_Plain_24_CS[] PROGMEM = {
+ 0x18, // Width: 24
+ 0x1C, // Height: 28
+ 0x20, // First char: 32
+ 0xE0, // Number of chars: 224
+ // Jump Table:
+ 0xFF, 0xFF, 0x00, 0x18, // 32
+ 0x00, 0x00, 0x13, 0x06, // 33
+ 0x00, 0x13, 0x1A, 0x08, // 34
+ 0x00, 0x2D, 0x33, 0x0E, // 35
+ 0x00, 0x60, 0x2F, 0x0D, // 36
+ 0x00, 0x8F, 0x4F, 0x15, // 37
+ 0x00, 0xDE, 0x3B, 0x10, // 38
+ 0x01, 0x19, 0x0A, 0x04, // 39
+ 0x01, 0x23, 0x1C, 0x08, // 40
+ 0x01, 0x3F, 0x1B, 0x08, // 41
+ 0x01, 0x5A, 0x21, 0x0A, // 42
+ 0x01, 0x7B, 0x32, 0x0E, // 43
+ 0x01, 0xAD, 0x10, 0x05, // 44
+ 0x01, 0xBD, 0x1B, 0x08, // 45
+ 0x01, 0xD8, 0x0F, 0x05, // 46
+ 0x01, 0xE7, 0x19, 0x08, // 47
+ 0x02, 0x00, 0x2F, 0x0D, // 48
+ 0x02, 0x2F, 0x23, 0x0A, // 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, 0x05, // 58
+ 0x03, 0xD7, 0x10, 0x05, // 59
+ 0x03, 0xE7, 0x2F, 0x0D, // 60
+ 0x04, 0x16, 0x2F, 0x0D, // 61
+ 0x04, 0x45, 0x2E, 0x0D, // 62
+ 0x04, 0x73, 0x2E, 0x0D, // 63
+ 0x04, 0xA1, 0x5B, 0x18, // 64
+ 0x04, 0xFC, 0x3B, 0x10, // 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, 0x12, // 71
+ 0x06, 0xA3, 0x3B, 0x10, // 72
+ 0x06, 0xDE, 0x0F, 0x05, // 73
+ 0x06, 0xED, 0x27, 0x0B, // 74
+ 0x07, 0x14, 0x3F, 0x11, // 75
+ 0x07, 0x53, 0x2F, 0x0D, // 76
+ 0x07, 0x82, 0x43, 0x12, // 77
+ 0x07, 0xC5, 0x3B, 0x10, // 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, 0x0F, // 84
+ 0x09, 0x77, 0x3B, 0x10, // 85
+ 0x09, 0xB2, 0x39, 0x10, // 86
+ 0x09, 0xEB, 0x59, 0x18, // 87
+ 0x0A, 0x44, 0x3B, 0x10, // 88
+ 0x0A, 0x7F, 0x3D, 0x11, // 89
+ 0x0A, 0xBC, 0x37, 0x0F, // 90
+ 0x0A, 0xF3, 0x14, 0x06, // 91
+ 0x0B, 0x07, 0x1B, 0x08, // 92
+ 0x0B, 0x22, 0x18, 0x07, // 93
+ 0x0B, 0x3A, 0x2A, 0x0C, // 94
+ 0x0B, 0x64, 0x34, 0x0E, // 95
+ 0x0B, 0x98, 0x11, 0x06, // 96
+ 0x0B, 0xA9, 0x2F, 0x0D, // 97
+ 0x0B, 0xD8, 0x33, 0x0E, // 98
+ 0x0C, 0x0B, 0x2B, 0x0C, // 99
+ 0x0C, 0x36, 0x2F, 0x0D, // 100
+ 0x0C, 0x65, 0x2F, 0x0D, // 101
+ 0x0C, 0x94, 0x1A, 0x08, // 102
+ 0x0C, 0xAE, 0x2F, 0x0D, // 103
+ 0x0C, 0xDD, 0x2F, 0x0D, // 104
+ 0x0D, 0x0C, 0x0F, 0x05, // 105
+ 0x0D, 0x1B, 0x10, 0x05, // 106
+ 0x0D, 0x2B, 0x2F, 0x0D, // 107
+ 0x0D, 0x5A, 0x0F, 0x05, // 108
+ 0x0D, 0x69, 0x47, 0x13, // 109
+ 0x0D, 0xB0, 0x2F, 0x0D, // 110
+ 0x0D, 0xDF, 0x2F, 0x0D, // 111
+ 0x0E, 0x0E, 0x33, 0x0E, // 112
+ 0x0E, 0x41, 0x30, 0x0D, // 113
+ 0x0E, 0x71, 0x1E, 0x09, // 114
+ 0x0E, 0x8F, 0x2B, 0x0C, // 115
+ 0x0E, 0xBA, 0x1B, 0x08, // 116
+ 0x0E, 0xD5, 0x2F, 0x0D, // 117
+ 0x0F, 0x04, 0x2A, 0x0C, // 118
+ 0x0F, 0x2E, 0x42, 0x12, // 119
+ 0x0F, 0x70, 0x2B, 0x0C, // 120
+ 0x0F, 0x9B, 0x2A, 0x0C, // 121
+ 0x0F, 0xC5, 0x2B, 0x0C, // 122
+ 0x0F, 0xF0, 0x1C, 0x08, // 123
+ 0x10, 0x0C, 0x10, 0x05, // 124
+ 0x10, 0x1C, 0x1B, 0x08, // 125
+ 0x10, 0x37, 0x32, 0x0E, // 126
+ 0xFF, 0xFF, 0x00, 0x18, // 127
+ 0xFF, 0xFF, 0x00, 0x18, // 128
+ 0x10, 0x69, 0x3F, 0x11, // 129
+ 0x10, 0xA8, 0x3F, 0x11, // 130
+ 0x10, 0xE7, 0x3B, 0x10, // 131
+ 0x11, 0x22, 0x3B, 0x10, // 132
+ 0x11, 0x5D, 0x3F, 0x11, // 133
+ 0x11, 0x9C, 0x3B, 0x10, // 134
+ 0x11, 0xD7, 0x35, 0x0F, // 135
+ 0x12, 0x0C, 0x3B, 0x10, // 136
+ 0x12, 0x47, 0x37, 0x0F, // 137
+ 0x12, 0x7E, 0x2B, 0x0C, // 138
+ 0x12, 0xA9, 0x3A, 0x10, // 139
+ 0x12, 0xE3, 0x2F, 0x0D, // 140
+ 0x13, 0x12, 0x2F, 0x0D, // 141
+ 0x13, 0x41, 0x1E, 0x09, // 142
+ 0x13, 0x5F, 0x2B, 0x0C, // 143
+ 0x13, 0x8A, 0x26, 0x0B, // 144
+ 0x13, 0xB0, 0x2F, 0x0D, // 145
+ 0x13, 0xDF, 0x2B, 0x0C, // 146
+ 0x14, 0x0A, 0x2F, 0x0D, // 147
+ 0x14, 0x39, 0x15, 0x07, // 148
+ 0x14, 0x4E, 0x2F, 0x0D, // 149
+ 0x14, 0x7D, 0x1A, 0x08, // 150
+ 0x14, 0x97, 0x3F, 0x11, // 151
+ 0x14, 0xD6, 0x1E, 0x09, // 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, 0x18, // 160
+ 0x14, 0xF4, 0x14, 0x06, // 161
+ 0x15, 0x08, 0x2B, 0x0C, // 162
+ 0x15, 0x33, 0x2F, 0x0D, // 163
+ 0x15, 0x62, 0x33, 0x0E, // 164
+ 0x15, 0x95, 0x31, 0x0E, // 165
+ 0x15, 0xC6, 0x10, 0x05, // 166
+ 0x15, 0xD6, 0x2F, 0x0D, // 167
+ 0x16, 0x05, 0x19, 0x08, // 168
+ 0x16, 0x1E, 0x46, 0x13, // 169
+ 0x16, 0x64, 0x1A, 0x08, // 170
+ 0x16, 0x7E, 0x27, 0x0B, // 171
+ 0x16, 0xA5, 0x2F, 0x0D, // 172
+ 0x16, 0xD4, 0x1B, 0x08, // 173
+ 0x16, 0xEF, 0x46, 0x13, // 174
+ 0x17, 0x35, 0x31, 0x0E, // 175
+ 0x17, 0x66, 0x1E, 0x09, // 176
+ 0x17, 0x84, 0x33, 0x0E, // 177
+ 0x17, 0xB7, 0x1A, 0x08, // 178
+ 0x17, 0xD1, 0x1A, 0x08, // 179
+ 0x17, 0xEB, 0x19, 0x08, // 180
+ 0x18, 0x04, 0x2F, 0x0D, // 181
+ 0x18, 0x33, 0x31, 0x0E, // 182
+ 0x18, 0x64, 0x12, 0x06, // 183
+ 0x18, 0x76, 0x18, 0x07, // 184
+ 0x18, 0x8E, 0x16, 0x07, // 185
+ 0x18, 0xA4, 0x1E, 0x09, // 186
+ 0x18, 0xC2, 0x2E, 0x0D, // 187
+ 0x18, 0xF0, 0x4F, 0x15, // 188
+ 0x19, 0x3F, 0x4B, 0x14, // 189
+ 0x19, 0x8A, 0x4B, 0x14, // 190
+ 0x19, 0xD5, 0x33, 0x0E, // 191
+ 0x1A, 0x08, 0x3B, 0x10, // 192
+ 0x1A, 0x43, 0x3B, 0x10, // 193
+ 0x1A, 0x7E, 0x3B, 0x10, // 194
+ 0x1A, 0xB9, 0x3B, 0x10, // 195
+ 0x1A, 0xF4, 0x3B, 0x10, // 196
+ 0x1B, 0x2F, 0x3B, 0x10, // 197
+ 0x1B, 0x6A, 0x5B, 0x18, // 198
+ 0x1B, 0xC5, 0x3F, 0x11, // 199
+ 0x1C, 0x04, 0x3B, 0x10, // 200
+ 0x1C, 0x3F, 0x3B, 0x10, // 201
+ 0x1C, 0x7A, 0x3B, 0x10, // 202
+ 0x1C, 0xB5, 0x3B, 0x10, // 203
+ 0x1C, 0xF0, 0x11, 0x06, // 204
+ 0x1D, 0x01, 0x11, 0x06, // 205
+ 0x1D, 0x12, 0x15, 0x07, // 206
+ 0x1D, 0x27, 0x15, 0x07, // 207
+ 0x1D, 0x3C, 0x3F, 0x11, // 208
+ 0x1D, 0x7B, 0x3B, 0x10, // 209
+ 0x1D, 0xB6, 0x47, 0x13, // 210
+ 0x1D, 0xFD, 0x47, 0x13, // 211
+ 0x1E, 0x44, 0x47, 0x13, // 212
+ 0x1E, 0x8B, 0x47, 0x13, // 213
+ 0x1E, 0xD2, 0x47, 0x13, // 214
+ 0x1F, 0x19, 0x2B, 0x0C, // 215
+ 0x1F, 0x44, 0x47, 0x13, // 216
+ 0x1F, 0x8B, 0x3B, 0x10, // 217
+ 0x1F, 0xC6, 0x3B, 0x10, // 218
+ 0x20, 0x01, 0x3B, 0x10, // 219
+ 0x20, 0x3C, 0x3B, 0x10, // 220
+ 0x20, 0x77, 0x3D, 0x11, // 221
+ 0x20, 0xB4, 0x3A, 0x10, // 222
+ 0x20, 0xEE, 0x37, 0x0F, // 223
+ 0x21, 0x25, 0x2F, 0x0D, // 224
+ 0x21, 0x54, 0x2F, 0x0D, // 225
+ 0x21, 0x83, 0x2F, 0x0D, // 226
+ 0x21, 0xB2, 0x2F, 0x0D, // 227
+ 0x21, 0xE1, 0x2F, 0x0D, // 228
+ 0x22, 0x10, 0x2F, 0x0D, // 229
+ 0x22, 0x3F, 0x53, 0x16, // 230
+ 0x22, 0x92, 0x2B, 0x0C, // 231
+ 0x22, 0xBD, 0x2F, 0x0D, // 232
+ 0x22, 0xEC, 0x2F, 0x0D, // 233
+ 0x23, 0x1B, 0x2F, 0x0D, // 234
+ 0x23, 0x4A, 0x2F, 0x0D, // 235
+ 0x23, 0x79, 0x11, 0x06, // 236
+ 0x23, 0x8A, 0x11, 0x06, // 237
+ 0x23, 0x9B, 0x15, 0x07, // 238
+ 0x23, 0xB0, 0x15, 0x07, // 239
+ 0x23, 0xC5, 0x2F, 0x0D, // 240
+ 0x23, 0xF4, 0x2F, 0x0D, // 241
+ 0x24, 0x23, 0x2F, 0x0D, // 242
+ 0x24, 0x52, 0x2F, 0x0D, // 243
+ 0x24, 0x81, 0x2F, 0x0D, // 244
+ 0x24, 0xB0, 0x2F, 0x0D, // 245
+ 0x24, 0xDF, 0x2F, 0x0D, // 246
+ 0x25, 0x0E, 0x32, 0x0E, // 247
+ 0x25, 0x40, 0x33, 0x0E, // 248
+ 0x25, 0x73, 0x2F, 0x0D, // 249
+ 0x25, 0xA2, 0x2F, 0x0D, // 250
+ 0x25, 0xD1, 0x2F, 0x0D, // 251
+ 0x26, 0x00, 0x2F, 0x0D, // 252
+ 0x26, 0x2F, 0x2A, 0x0C, // 253
+ 0x26, 0x59, 0x2F, 0x0D, // 254
+ 0x26, 0x88, 0x2A, 0x0C, // 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, 0x38, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0xC1,
+ 0x1F, 0x00, 0x00, 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, 0x20, 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, 0x0F, 0x00, 0x80, 0xE3, 0x1C, 0x00, 0xC0, 0x77, 0x38, 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, 0x1F, 0x00, 0x00, 0xC0, 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, 0x0F, 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, 0x31, 0x00, 0x60, 0xC0, 0x30, 0x00, 0x60, 0x60, 0x30, 0x00, 0xC0, 0x30, 0x30, 0x00, 0xC0, 0x1F,
+ 0x30, 0x00, 0x00, 0x0F, 0x30, // 50
+ 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x0E, 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, 0xE0, 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, 0x6F, 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, 0x60, 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, 0x0C, 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, 0x0F, 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,
+ 0x03, 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, 0x0C, 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, 0x1C, 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, 0x30, 0x30, 0x00, 0x60, 0x70,
+ 0x30, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0xE1, 0x18, 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, 0x03, 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, 0x03, // 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, 0x1F, 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, 0x07, 0x00, 0x00,
+ 0xCF, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xCF, 0x03, 0x00, 0xC0, 0x03,
+ 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, 0xC0, 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, 0x00, 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, 0x0C, 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, 0xE0, 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, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x18,
+ 0x0C, // 99
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 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, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 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, 0x01, 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, 0x0E, 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, 0x04, 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, 0xE0, 0x03, // 112
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 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, 0x3E, 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, 0x7F, 0xFE, 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, 0x7F, 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, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0,
+ 0x00, 0x18, 0x00, 0x62, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x66, 0x00,
+ 0x30, 0x00, 0x62, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0F, 0x00, 0x00, 0x02,
+ 0x03, // 129
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60,
+ 0x00, 0x30, 0x00, 0x62, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x66, 0x00,
+ 0x30, 0x00, 0xE2, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0E, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC,
+ 0x01, // 130
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x62, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x66, 0x30,
+ 0x30, 0x00, 0x62, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 131
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x80,
+ 0x03, 0x00, 0x00, 0x02, 0x0E, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x0C, 0x70, 0x00, 0x00, 0x0C, 0xE0, 0x01, 0x00, 0x06, 0x80,
+ 0x03, 0x00, 0x02, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 132
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60,
+ 0x30, 0x00, 0x00, 0x62, 0x30, 0x00, 0x00, 0x66, 0x30, 0x00, 0x00, 0x6C, 0x70, 0x00, 0x00, 0x6C, 0xF0, 0x00, 0x00, 0x66, 0xF0,
+ 0x03, 0x00, 0x62, 0xB0, 0x07, 0x00, 0xE0, 0x18, 0x1F, 0x00, 0xC0, 0x1F, 0x3C, 0x00, 0x80, 0x0F, 0x30, 0x00, 0x00, 0x00,
+ 0x20, // 133
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0x0F, 0x00, 0xC0, 0x1F, 0x1C, 0x00, 0xC0, 0x18, 0x18, 0x00, 0x62,
+ 0x38, 0x38, 0x00, 0x66, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x62, 0x70,
+ 0x30, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0xE1, 0x18, 0x00, 0x80, 0xC3, 0x0F, 0x00, 0x00, 0x83, 0x07, // 134
+ 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x66,
+ 0x00, 0x00, 0x00, 0xEC, 0xFF, 0x3F, 0x00, 0xEC, 0xFF, 0x3F, 0x00, 0x66, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x60, 0x00,
+ 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 135
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x1B, 0x00, 0x30, 0x00, 0x11, 0x00, 0x30, 0x00, 0x1B, 0x00, 0x30, 0x00, 0x0E, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 136
+ 0x00, 0x00, 0x30, 0x00, 0x60, 0x00, 0x38, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x60, 0x00, 0x37, 0x00, 0x60, 0x80, 0x33, 0x00, 0x62,
+ 0xC0, 0x31, 0x00, 0x66, 0xE0, 0x30, 0x00, 0x6C, 0x38, 0x30, 0x00, 0x6C, 0x1C, 0x30, 0x00, 0x66, 0x0E, 0x30, 0x00, 0x62, 0x07,
+ 0x30, 0x00, 0xE0, 0x01, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, // 137
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x20, 0x0E, 0x38, 0x00, 0x60,
+ 0x06, 0x30, 0x00, 0xC0, 0x06, 0x30, 0x00, 0xC0, 0x06, 0x30, 0x00, 0x60, 0x0E, 0x38, 0x00, 0x20, 0x1C, 0x1C, 0x00, 0x00, 0x18,
+ 0x0C, // 138
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0xE0, 0x01, // 139
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0x20, 0xCE, 0x38, 0x00, 0x60,
+ 0xC6, 0x30, 0x00, 0xC0, 0xC6, 0x30, 0x00, 0xC0, 0xC6, 0x30, 0x00, 0x60, 0xCE, 0x38, 0x00, 0x20, 0xDC, 0x18, 0x00, 0x00, 0xF8,
+ 0x0C, 0x00, 0x00, 0xF0, 0x04, // 140
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x20, 0x18, 0x00, 0x00, 0x60,
+ 0x0C, 0x00, 0x00, 0xC0, 0x06, 0x00, 0x00, 0xC0, 0x06, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x20, 0x0E, 0x00, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 141
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFE, 0x3F, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0xC0, 0x0C, 0x00, 0x00, 0xC0,
+ 0x06, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x20, 0x06, // 142
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x0C, 0x00, 0x00, 0x7C, 0x1C, 0x00, 0x20, 0xEE, 0x38, 0x00, 0x60, 0xC6, 0x30, 0x00, 0xC0,
+ 0xC6, 0x30, 0x00, 0xC0, 0xC6, 0x31, 0x00, 0x60, 0xC6, 0x31, 0x00, 0x20, 0x8E, 0x39, 0x00, 0x00, 0x9C, 0x1F, 0x00, 0x00, 0x18,
+ 0x0F, // 143
+ 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0xE0, 0x01, // 144
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x70,
+ 0x00, 0x30, 0x00, 0xD8, 0x00, 0x30, 0x00, 0x88, 0x00, 0x30, 0x00, 0xD8, 0x00, 0x18, 0x00, 0x70, 0x00, 0x0C, 0x00, 0x00, 0xFE,
+ 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 145
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x06, 0x3E, 0x00, 0x20, 0x06, 0x37, 0x00, 0x60,
+ 0xC6, 0x33, 0x00, 0xC0, 0xE6, 0x30, 0x00, 0xC0, 0x76, 0x30, 0x00, 0x60, 0x3E, 0x30, 0x00, 0x20, 0x1E, 0x30, 0x00, 0x00, 0x06,
+ 0x30, // 146
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE8, 0xFF, 0x3F, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x06,
+ 0x00, 0x30, 0x00, 0x02, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x30, // 147
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE8, 0xFF, 0x3F, 0x00, 0xEE, 0xFF, 0x3F, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x02, // 148
+ 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, 0x60, 0x03, 0x30, 0x00, 0xE0, 0x01,
+ 0x30, 0x00, 0x00, 0x00, 0x30, // 149
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+ 0x03, 0x00, 0x00, 0xE0, 0x01, // 150
+ 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, 0x68, 0x70, 0x00, 0x00, 0x6E, 0xF0, 0x00, 0x00, 0x66, 0xF0,
+ 0x03, 0x00, 0x62, 0xB0, 0x07, 0x00, 0xE0, 0x18, 0x1F, 0x00, 0xC0, 0x1F, 0x3C, 0x00, 0x80, 0x0F, 0x30, 0x00, 0x00, 0x00,
+ 0x20, // 151
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x80, 0x0C, 0x00, 0x00, 0xE0,
+ 0x06, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x20, 0x06, // 152
+ 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, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x9C, 0x07, 0x00, 0x0E, 0x78, 0x00, 0x00,
+ 0x06, 0x3F, 0x00, 0x00, 0xF6, 0x30, 0x00, 0x00, 0x0E, 0x30, 0x00, 0xE0, 0x0D, 0x1C, 0x00, 0x00, 0x1C, 0x0E, 0x00, 0x00, 0x10,
+ 0x06, // 162
+ 0x00, 0x60, 0x10, 0x00, 0x00, 0x60, 0x38, 0x00, 0x00, 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, 0x7E, 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, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0x60, // 168
+ 0x00, 0xF8, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0x07, 0x07, 0x00, 0x80, 0x01, 0x0C, 0x00, 0xC0, 0x79, 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, 0x01, 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, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xC0, 0x02, 0x00, 0x00, 0x80, 0x03, 0x00,
+ 0x00, 0x00, 0x01, // 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, 0x38,
+ 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 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, 0x08,
+ 0x00, 0x60, 0xE0, 0x3F, 0x00, 0x20, 0xE0, 0x3F, 0x00, 0x00, 0x00, 0x08, // 190
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF8, 0x03, 0x00, 0x00, 0x1E, 0x03, 0x00,
+ 0x00, 0x07, 0x07, 0x00, 0xE6, 0x03, 0x06, 0x00, 0xE6, 0x01, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
+ 0x80, 0x03, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xC0, // 191
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x82,
+ 0x8F, 0x01, 0x00, 0xE6, 0x83, 0x01, 0x00, 0x6E, 0x80, 0x01, 0x00, 0xE8, 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, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x80,
+ 0x8F, 0x01, 0x00, 0xE8, 0x83, 0x01, 0x00, 0x6E, 0x80, 0x01, 0x00, 0xE6, 0x83, 0x01, 0x00, 0x82, 0x8F, 0x01, 0x00, 0x00, 0xFE,
+ 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 193
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x88,
+ 0x8F, 0x01, 0x00, 0xEC, 0x83, 0x01, 0x00, 0x66, 0x80, 0x01, 0x00, 0xE6, 0x83, 0x01, 0x00, 0x8C, 0x8F, 0x01, 0x00, 0x08, 0xFE,
+ 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 194
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x0C, 0xFE, 0x01, 0x00, 0x8E,
+ 0x8F, 0x01, 0x00, 0xE6, 0x83, 0x01, 0x00, 0x66, 0x80, 0x01, 0x00, 0xEC, 0x83, 0x01, 0x00, 0x8C, 0x8F, 0x01, 0x00, 0x0E, 0xFE,
+ 0x01, 0x00, 0x06, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 195
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x8C,
+ 0x8F, 0x01, 0x00, 0xEC, 0x83, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x8C, 0x8F, 0x01, 0x00, 0x0C, 0xFE,
+ 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 196
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x9C,
+ 0x8F, 0x01, 0x00, 0xE2, 0x83, 0x01, 0x00, 0x62, 0x80, 0x01, 0x00, 0xE2, 0x83, 0x01, 0x00, 0x9C, 0x8F, 0x01, 0x00, 0x00, 0xFE,
+ 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 197
+ 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00,
+ 0xBC, 0x01, 0x00, 0x00, 0x8F, 0x01, 0x00, 0xC0, 0x83, 0x01, 0x00, 0xE0, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80,
+ 0x01, 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, 0x00, 0x30, // 198
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0,
+ 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x02, 0x60, 0x00, 0x30, 0x02, 0x60, 0x00, 0xF0, 0x02, 0x60, 0x00, 0xB0, 0x03, 0x60, 0x00,
+ 0x30, 0x01, 0x60, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0F, 0x00, 0x00, 0x02,
+ 0x03, // 199
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x62, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x6E, 0x30, 0x30, 0x00, 0x68, 0x30, 0x30, 0x00, 0x60, 0x30,
+ 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 200
+ 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, 0x68, 0x30, 0x30, 0x00, 0x6E, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x62, 0x30,
+ 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 201
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60,
+ 0x30, 0x30, 0x00, 0x68, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x6C, 0x30,
+ 0x30, 0x00, 0x68, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 202
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 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, 0x6C, 0x30,
+ 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 203
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0x3F, 0x00, 0xEE, 0xFF, 0x3F, 0x00, 0x08, // 204
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xEE, 0xFF, 0x3F, 0x00, 0xE6, 0xFF, 0x3F, 0x00, 0x02, // 205
+ 0x08, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0x3F, 0x00, 0xE6, 0xFF, 0x3F, 0x00, 0x0C, 0x00, 0x00, 0x00,
+ 0x08, // 206
+ 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x0C, 0x00, 0x00, 0x00,
+ 0x0C, // 207
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 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, 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, // 208
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x8C,
+ 0x03, 0x00, 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x06, 0x70, 0x00, 0x00, 0x0C, 0xE0, 0x01, 0x00, 0x0C, 0x80,
+ 0x03, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x06, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 209
+ 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, 0x62, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x6E, 0x00, 0x30, 0x00, 0x68, 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, // 210
+ 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, 0x68, 0x00, 0x30, 0x00, 0x6E, 0x00, 0x30, 0x00, 0x66, 0x00,
+ 0x30, 0x00, 0x62, 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, // 211
+ 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, 0x68, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x66, 0x00,
+ 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0xE8, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F,
+ 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 212
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xCC,
+ 0x00, 0x18, 0x00, 0xEE, 0x00, 0x38, 0x00, 0x66, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x6C, 0x00,
+ 0x30, 0x00, 0x6E, 0x00, 0x30, 0x00, 0xE6, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F,
+ 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 213
+ 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, 0x6C, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00,
+ 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0xEC, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F,
+ 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 214
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x8E, 0x03, 0x00, 0x00, 0xDC, 0x01, 0x00, 0x00,
+ 0xF8, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0xDC, 0x01, 0x00, 0x00, 0x8E, 0x03, 0x00, 0x00, 0x06,
+ 0x03, // 215
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x21, 0x00, 0x00, 0xFF, 0x77, 0x00, 0x80, 0x07, 0x3F, 0x00, 0xC0, 0x01, 0x1E, 0x00, 0xC0,
+ 0x00, 0x1F, 0x00, 0xE0, 0x80, 0x3B, 0x00, 0x60, 0xC0, 0x31, 0x00, 0x60, 0xE0, 0x30, 0x00, 0x60, 0x70, 0x30, 0x00, 0x60, 0x38,
+ 0x30, 0x00, 0x60, 0x1C, 0x30, 0x00, 0xE0, 0x0E, 0x38, 0x00, 0xC0, 0x07, 0x18, 0x00, 0xC0, 0x03, 0x1C, 0x00, 0xE0, 0x07, 0x0F,
+ 0x00, 0x70, 0xFF, 0x07, 0x00, 0x20, 0xFC, 0x01, // 216
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x02, 0x00, 0x30, 0x00, 0x06, 0x00, 0x30, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 217
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x06, 0x00, 0x30, 0x00, 0x02, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 218
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x08, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x06, 0x00, 0x30, 0x00, 0x06, 0x00, 0x30, 0x00, 0x0C, 0x00,
+ 0x30, 0x00, 0x08, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 219
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x0C, 0x00,
+ 0x30, 0x00, 0x0C, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 220
+ 0x20, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x08, 0xF0, 0x3F, 0x00, 0x0E, 0xF0, 0x3F, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x02, 0x1E,
+ 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x20, // 221
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00,
+ 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03,
+ 0x06, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x86, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xF8, // 222
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x3F, 0x00, 0xC0, 0xFF, 0x3F, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x08, 0x00, 0x60, 0x00, 0x1C, 0x00, 0x60, 0x00, 0x38, 0x00, 0xE0, 0x78, 0x30, 0x00, 0xC0, 0x7F, 0x30, 0x00, 0x80, 0xC7,
+ 0x30, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x0F, // 223
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x20, 0x86, 0x31, 0x00, 0x60,
+ 0x86, 0x31, 0x00, 0xE0, 0xC6, 0x30, 0x00, 0x80, 0xC6, 0x18, 0x00, 0x00, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8,
+ 0x3F, 0x00, 0x00, 0x00, 0x20, // 224
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x00, 0x86, 0x31, 0x00, 0x80,
+ 0x86, 0x31, 0x00, 0xE0, 0xC6, 0x30, 0x00, 0x60, 0xC6, 0x18, 0x00, 0x20, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8,
+ 0x3F, 0x00, 0x00, 0x00, 0x20, // 225
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x80, 0x8C, 0x39, 0x00, 0xC0, 0x86, 0x31, 0x00, 0x60,
+ 0x86, 0x31, 0x00, 0x60, 0xC6, 0x30, 0x00, 0xC0, 0xC6, 0x18, 0x00, 0x80, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8,
+ 0x3F, 0x00, 0x00, 0x00, 0x20, // 226
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0xC0, 0x1C, 0x1F, 0x00, 0xE0, 0x8C, 0x39, 0x00, 0x60, 0x86, 0x31, 0x00, 0x60,
+ 0x86, 0x31, 0x00, 0xC0, 0xC6, 0x30, 0x00, 0xC0, 0xC6, 0x18, 0x00, 0xE0, 0xCE, 0x0C, 0x00, 0x60, 0xFC, 0x1F, 0x00, 0x00, 0xF8,
+ 0x3F, 0x00, 0x00, 0x00, 0x20, // 227
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0xC0, 0x8C, 0x39, 0x00, 0xC0, 0x86, 0x31, 0x00, 0x00,
+ 0x86, 0x31, 0x00, 0x00, 0xC6, 0x30, 0x00, 0xC0, 0xC6, 0x18, 0x00, 0xC0, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8,
+ 0x3F, 0x00, 0x00, 0x00, 0x20, // 228
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x70, 0x86, 0x31, 0x00, 0x88,
+ 0x86, 0x31, 0x00, 0x88, 0xC6, 0x30, 0x00, 0x88, 0xC6, 0x18, 0x00, 0x70, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8,
+ 0x3F, 0x00, 0x00, 0x00, 0x20, // 229
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0F, 0x00, 0x00, 0x9C, 0x1F, 0x00, 0x00, 0xCC, 0x39, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00,
+ 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0x66, 0x18, 0x00, 0x00, 0x6E, 0x1C, 0x00, 0x00, 0xFC,
+ 0x0F, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xCC, 0x1C, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30,
+ 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xCC, 0x18, 0x00, 0x00, 0xF8, 0x0C, 0x00, 0x00, 0xE0, 0x04, // 230
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x02, 0x00,
+ 0x06, 0x30, 0x02, 0x00, 0x06, 0xF0, 0x02, 0x00, 0x06, 0xB0, 0x03, 0x00, 0x0E, 0x38, 0x01, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x18,
+ 0x0C, // 231
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0x20, 0xCE, 0x38, 0x00, 0x60,
+ 0xC6, 0x30, 0x00, 0xE0, 0xC6, 0x30, 0x00, 0x80, 0xC6, 0x30, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xDC, 0x18, 0x00, 0x00, 0xF8,
+ 0x0C, 0x00, 0x00, 0xF0, 0x04, // 232
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x80,
+ 0xC6, 0x30, 0x00, 0xE0, 0xC6, 0x30, 0x00, 0x60, 0xC6, 0x30, 0x00, 0x20, 0xCE, 0x38, 0x00, 0x00, 0xDC, 0x18, 0x00, 0x00, 0xF8,
+ 0x0C, 0x00, 0x00, 0xF0, 0x04, // 233
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0x80, 0xCE, 0x38, 0x00, 0xC0,
+ 0xC6, 0x30, 0x00, 0x60, 0xC6, 0x30, 0x00, 0x60, 0xC6, 0x30, 0x00, 0xC0, 0xCE, 0x38, 0x00, 0x80, 0xDC, 0x18, 0x00, 0x00, 0xF8,
+ 0x0C, 0x00, 0x00, 0xF0, 0x04, // 234
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0xC0, 0xCE, 0x38, 0x00, 0xC0,
+ 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0xC0, 0xCE, 0x38, 0x00, 0xC0, 0xDC, 0x18, 0x00, 0x00, 0xF8,
+ 0x0C, 0x00, 0x00, 0xF0, 0x04, // 235
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0xE0, 0xFE, 0x3F, 0x00, 0x80, // 236
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xE0, 0xFE, 0x3F, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0x20, // 237
+ 0x80, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0xC0, 0x00, 0x00, 0x00,
+ 0x80, // 238
+ 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0xC0, 0x00, 0x00, 0x00,
+ 0xC0, // 239
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1D, 0x1C, 0x00, 0xA0, 0x0F, 0x38, 0x00, 0xA0,
+ 0x06, 0x30, 0x00, 0xE0, 0x06, 0x30, 0x00, 0xC0, 0x06, 0x30, 0x00, 0xC0, 0x0F, 0x38, 0x00, 0x20, 0x1F, 0x1C, 0x00, 0x00, 0xFC,
+ 0x0F, 0x00, 0x00, 0xE0, 0x07, // 240
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0xC0, 0xFE, 0x3F, 0x00, 0xE0, 0x18, 0x00, 0x00, 0x60,
+ 0x0C, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0xC0, 0x06, 0x00, 0x00, 0xC0, 0x06, 0x00, 0x00, 0xE0, 0x0E, 0x00, 0x00, 0x60, 0xFC,
+ 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 241
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x20, 0x0E, 0x38, 0x00, 0x60,
+ 0x06, 0x30, 0x00, 0xE0, 0x06, 0x30, 0x00, 0x80, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8,
+ 0x0F, 0x00, 0x00, 0xF0, 0x07, // 242
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x80,
+ 0x06, 0x30, 0x00, 0xE0, 0x06, 0x30, 0x00, 0x60, 0x06, 0x30, 0x00, 0x20, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8,
+ 0x0F, 0x00, 0x00, 0xF0, 0x07, // 243
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x80, 0x0E, 0x38, 0x00, 0xC0,
+ 0x06, 0x30, 0x00, 0x60, 0x06, 0x30, 0x00, 0x60, 0x06, 0x30, 0x00, 0xC0, 0x0E, 0x38, 0x00, 0x80, 0x1C, 0x1C, 0x00, 0x00, 0xF8,
+ 0x0F, 0x00, 0x00, 0xF0, 0x07, // 244
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0xC0, 0x1C, 0x1C, 0x00, 0xE0, 0x0E, 0x38, 0x00, 0x60,
+ 0x06, 0x30, 0x00, 0x60, 0x06, 0x30, 0x00, 0xC0, 0x06, 0x30, 0x00, 0xC0, 0x0E, 0x38, 0x00, 0xE0, 0x1C, 0x1C, 0x00, 0x60, 0xF8,
+ 0x0F, 0x00, 0x00, 0xF0, 0x07, // 245
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0xC0, 0x0E, 0x38, 0x00, 0xC0,
+ 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0xC0, 0x0E, 0x38, 0x00, 0xC0, 0x1C, 0x1C, 0x00, 0x00, 0xF8,
+ 0x0F, 0x00, 0x00, 0xF0, 0x07, // 246
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0xB6, 0x01, 0x00, 0x00, 0xB6, 0x01, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30,
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, // 247
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x67, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00,
+ 0x0E, 0x3F, 0x00, 0x00, 0x86, 0x33, 0x00, 0x00, 0xE6, 0x31, 0x00, 0x00, 0x76, 0x30, 0x00, 0x00, 0x3E, 0x38, 0x00, 0x00, 0x1C,
+ 0x1C, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x00, 0xF3, 0x07, // 248
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x20, 0x00, 0x38, 0x00, 0x60,
+ 0x00, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0xFE,
+ 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 249
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00,
+ 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0x60, 0x00, 0x18, 0x00, 0x20, 0x00, 0x0C, 0x00, 0x00, 0xFE,
+ 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 250
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x80, 0x00, 0x38, 0x00, 0xC0,
+ 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x80, 0x00, 0x0C, 0x00, 0x00, 0xFE,
+ 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 251
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0xC0, 0x00, 0x38, 0x00, 0xC0,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x00, 0x0C, 0x00, 0x00, 0xFE,
+ 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 252
+ 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
diff --git a/src/graphics/fonts/OLEDDisplayFontsCS.h b/src/graphics/fonts/OLEDDisplayFontsCS.h
new file mode 100644
index 000000000..322fbc936
--- /dev/null
+++ b/src/graphics/fonts/OLEDDisplayFontsCS.h
@@ -0,0 +1,16 @@
+#ifndef OLEDDISPLAYFONTSCS_h
+#define OLEDDISPLAYFONTSCS_h
+
+#ifdef ARDUINO
+#include
+#elif __MBED__
+#define PROGMEM
+#endif
+
+/**
+ * Localization for Czech and Slovak language containing glyphs with diacritic.
+ */
+extern const uint8_t ArialMT_Plain_10_CS[] PROGMEM;
+extern const uint8_t ArialMT_Plain_16_CS[] PROGMEM;
+extern const uint8_t ArialMT_Plain_24_CS[] PROGMEM;
+#endif
\ No newline at end of file
diff --git a/src/input/MPR121Keyboard.cpp b/src/input/MPR121Keyboard.cpp
index 078d80272..9bca6801d 100644
--- a/src/input/MPR121Keyboard.cpp
+++ b/src/input/MPR121Keyboard.cpp
@@ -29,6 +29,8 @@
#define _MPR121_REG_CONFIG1 0x5C
#define _MPR121_REG_CONFIG2 0x5D
#define _MPR121_REG_ELECTRODE_CONFIG 0x5E
+#define _MPR121_REG_AUTOCONF_CTRL0 0x7B
+#define _MPR121_REG_AUTOCONF_CTRL1 0x7C
#define _MPR121_REG_SOFT_RESET 0x80
#define _KEY_MASK 0x0FFF // Key mask for the first 12 bits
@@ -87,7 +89,7 @@ uint8_t MPR121_KeyMap[12] = {2, 5, 8, 11, 1, 4, 7, 10, 0, 3, 6, 9};
MPR121Keyboard::MPR121Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr)
{
- // LOG_DEBUG("MPR121 @ %02x\n", m_addr);
+ // LOG_DEBUG("MPR121 @ %02x", m_addr);
state = Init;
last_key = -1;
last_tap = 0L;
@@ -132,18 +134,18 @@ void MPR121Keyboard::reset()
writeRegister(_MPR121_REG_ELECTRODE_CONFIG, 0x00);
delay(100);
- LOG_DEBUG("MPR121 Configure");
+ LOG_DEBUG("MPR121 Configuring");
// Set touch release thresholds
for (uint8_t i = 0; i < 12; i++) {
// Set touch threshold
- writeRegister(_MPR121_REG_TOUCH_THRESHOLD + (i * 2), 15);
+ writeRegister(_MPR121_REG_TOUCH_THRESHOLD + (i * 2), 10);
delay(20);
// Set release threshold
- writeRegister(_MPR121_REG_RELEASE_THRESHOLD + (i * 2), 7);
+ writeRegister(_MPR121_REG_RELEASE_THRESHOLD + (i * 2), 5);
delay(20);
}
// Configure filtering and baseline registers
- writeRegister(_MPR121_REG_MAX_HALF_DELTA_RISING, 0x01);
+ writeRegister(_MPR121_REG_MAX_HALF_DELTA_RISING, 0x05);
delay(20);
writeRegister(_MPR121_REG_MAX_HALF_DELTA_FALLING, 0x01);
delay(20);
@@ -153,7 +155,7 @@ void MPR121Keyboard::reset()
delay(20);
writeRegister(_MPR121_REG_NOISE_HALF_DELTA_TOUCHED, 0x00);
delay(20);
- writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_RISING, 0x0e);
+ writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_RISING, 0x05);
delay(20);
writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_FALLING, 0x01);
delay(20);
@@ -165,18 +167,19 @@ void MPR121Keyboard::reset()
delay(20);
writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_TOUCHED, 0x00);
delay(20);
- // Set Debounce to 0x02
- writeRegister(_MPR121_REG_DEBOUNCE, 0x00);
+ writeRegister(_MPR121_REG_AUTOCONF_CTRL0, 0x04); // Auto-config enable
delay(20);
- // Set Filter1 itterations and discharge current 6x and 16uA respectively (0x10)
- writeRegister(_MPR121_REG_CONFIG1, 0x10);
+ writeRegister(_MPR121_REG_AUTOCONF_CTRL1, 0x00); // Ensure no auto-config interrupt
delay(20);
- // Set CDT to 0.5us, Filter2 itterations to 4x, and Sample interval = 0 (0x20)
- writeRegister(_MPR121_REG_CONFIG2, 0x20);
+ writeRegister(_MPR121_REG_DEBOUNCE, 0x02);
+ delay(20);
+ writeRegister(_MPR121_REG_CONFIG1, 0x20);
+ delay(20);
+ writeRegister(_MPR121_REG_CONFIG2, 0x21);
delay(20);
// Enter run mode by Seting partial filter calibration tracking, disable proximity detection, enable 12 channels
writeRegister(_MPR121_REG_ELECTRODE_CONFIG,
- ECR_CALIBRATION_TRACK_FROM_PARTIAL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH);
+ ECR_CALIBRATION_TRACK_FROM_FULL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH);
delay(100);
LOG_DEBUG("MPR121 Run");
state = Idle;
@@ -427,4 +430,4 @@ void MPR121Keyboard::writeRegister(uint8_t reg, uint8_t value)
if (writeCallback) {
writeCallback(m_addr, data[0], &(data[1]), 1);
}
-}
+}
\ No newline at end of file
diff --git a/src/input/TouchScreenBase.cpp b/src/input/TouchScreenBase.cpp
index a63203362..d2f7b54f8 100644
--- a/src/input/TouchScreenBase.cpp
+++ b/src/input/TouchScreenBase.cpp
@@ -113,13 +113,13 @@ int32_t TouchScreenBase::runOnce()
if (_tapped) {
_tapped = false;
e.touchEvent = static_cast(TOUCH_ACTION_TAP);
- LOG_DEBUG("action TAP(%d/%d)\n", _last_x, _last_y);
+ LOG_DEBUG("action TAP(%d/%d)", _last_x, _last_y);
}
} else {
if (_tapped && (time_t(millis()) - _start) > TIME_LONG_PRESS - 50) {
_tapped = false;
e.touchEvent = static_cast(TOUCH_ACTION_TAP);
- LOG_DEBUG("action TAP(%d/%d)\n", _last_x, _last_y);
+ LOG_DEBUG("action TAP(%d/%d)", _last_x, _last_y);
}
}
#else
@@ -156,4 +156,4 @@ void TouchScreenBase::hapticFeedback()
drv.setWaveform(1, 0); // end waveform
drv.go();
#endif
-}
\ No newline at end of file
+}
diff --git a/src/main.cpp b/src/main.cpp
index fc8b8cdf7..bf7671597 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -40,6 +40,7 @@
#include
#ifdef ARCH_ESP32
+#include "freertosinc.h"
#if !MESHTASTIC_EXCLUDE_WEBSERVER
#include "mesh/http/WebServer.h"
#endif
@@ -90,6 +91,7 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#include "linux/LinuxHardwareI2C.h"
#include "mesh/raspihttp/PiWebServer.h"
#include "platform/portduino/PortduinoGlue.h"
+#include "platform/portduino/USBHal.h"
#include
#include
#include
@@ -172,6 +174,8 @@ std::pair nodeTelemetrySensorsMap[_meshtastic_TelemetrySenso
Router *router = NULL; // Users of router don't care what sort of subclass implements that API
+const char *firmware_version = optstr(APP_VERSION_SHORT);
+
const char *getDeviceName()
{
uint8_t dmac[6];
@@ -213,6 +217,9 @@ static OSThread *powerFSMthread;
static OSThread *ambientLightingThread;
RadioInterface *rIf = NULL;
+#ifdef ARCH_PORTDUINO
+RadioLibHal *RadioLibHAL = NULL;
+#endif
/**
* Some platforms (nrf52) might provide an alterate version that suppresses calling delay from sleep.
@@ -237,6 +244,17 @@ void printInfo()
#ifndef PIO_UNIT_TESTING
void setup()
{
+#if defined(T_DECK)
+ // GPIO10 manages all peripheral power supplies
+ // Turn on peripheral power immediately after MUC starts.
+ // If some boards are turned on late, ESP32 will reset due to low voltage.
+ // ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) ,
+ // TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder)
+ pinMode(KB_POWERON, OUTPUT);
+ digitalWrite(KB_POWERON, HIGH);
+ delay(100);
+#endif
+
concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO
SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0);
@@ -409,15 +427,6 @@ void setup()
digitalWrite(AQ_SET_PIN, HIGH);
#endif
-#if defined(T_DECK)
- // enable keyboard
- pinMode(KB_POWERON, OUTPUT);
- digitalWrite(KB_POWERON, HIGH);
- // There needs to be a delay after power on, give LILYGO-KEYBOARD some startup time
- // otherwise keyboard and touch screen will not work
- delay(200);
-#endif
-
// Currently only the tbeam has a PMU
// PMU initialization needs to be placed before i2c scanning
power = new Power();
@@ -573,6 +582,7 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_3XX, meshtastic_TelemetrySensorType_BMP3XX);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
+ scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
@@ -703,12 +713,16 @@ void setup()
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
SPI1.begin(false);
-#else // HW_SPI1_DEVICE
+#else // HW_SPI1_DEVICE
SPI.setSCK(LORA_SCK);
SPI.setTX(LORA_MOSI);
SPI.setRX(LORA_MISO);
SPI.begin(false);
-#endif // HW_SPI1_DEVICE
+#endif // HW_SPI1_DEVICE
+#elif ARCH_PORTDUINO
+ if (settingsStrings[spidev] != "ch341") {
+ SPI.begin();
+ }
#elif !defined(ARCH_ESP32) // ARCH_RP2040
SPI.begin();
#else
@@ -814,8 +828,11 @@ void setup()
if (settingsMap[use_sx1262]) {
if (!rIf) {
LOG_DEBUG("Activate sx1262 radio on SPI port %s", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL =
- new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
+ if (settingsStrings[spidev] == "ch341") {
+ RadioLibHAL = ch341Hal;
+ } else {
+ RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
+ }
rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@@ -829,8 +846,7 @@ void setup()
} else if (settingsMap[use_rf95]) {
if (!rIf) {
LOG_DEBUG("Activate rf95 radio on SPI port %s", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL =
- new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
+ RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@@ -845,7 +861,7 @@ void setup()
} else if (settingsMap[use_sx1280]) {
if (!rIf) {
LOG_DEBUG("Activate sx1280 radio on SPI port %s", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
+ RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@@ -905,7 +921,7 @@ void setup()
} else if (settingsMap[use_sx1268]) {
if (!rIf) {
LOG_DEBUG("Activate sx1268 radio on SPI port %s", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
+ RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new SX1268Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 4bdd9e674..4bc91ce4e 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -318,7 +318,7 @@ bool Channels::anyMqttEnabled()
{
#if USERPREFS_EVENT_MODE
// Don't publish messages on the public MQTT broker if we are in event mode
- if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0) {
+ if (mqtt && mqtt.isUsingDefaultServer()) {
return false;
}
#endif
diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp
index 94b9b6543..1624ab0d5 100644
--- a/src/mesh/CryptoEngine.cpp
+++ b/src/mesh/CryptoEngine.cpp
@@ -58,10 +58,16 @@ void CryptoEngine::clearKeys()
* Encrypt a packet's payload using a key generated with Curve25519 and SHA256
* for a specific node.
*
- * @param bytes is updated in place
+ * @param toNode The MeshPacket `to` field.
+ * @param fromNode The MeshPacket `from` field.
+ * @param remotePublic The remote node's Curve25519 public key.
+ * @param packetId The MeshPacket `id` field.
+ * @param numBytes Number of bytes of plaintext in the bytes buffer.
+ * @param bytes Buffer containing plaintext input.
+ * @param bytesOut Output buffer to be populated with encrypted ciphertext.
*/
bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
- uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut)
+ uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
{
uint8_t *auth;
long extraNonceTmp = random();
@@ -93,14 +99,18 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
* Decrypt a packet's payload using a key generated with Curve25519 and SHA256
* for a specific node.
*
- * @param bytes is updated in place
+ * @param fromNode The MeshPacket `from` field.
+ * @param remotePublic The remote node's Curve25519 public key.
+ * @param packetId The MeshPacket `id` field.
+ * @param numBytes Number of bytes of ciphertext in the bytes buffer.
+ * @param bytes Buffer containing ciphertext input.
+ * @param bytesOut Output buffer to be populated with decrypted plaintext.
*/
bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
- size_t numBytes, uint8_t *bytes, uint8_t *bytesOut)
+ size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
{
- uint8_t *auth; // set to last 8 bytes of text?
- uint32_t extraNonce; // pointer was not really used
- auth = bytes + numBytes - 12;
+ const uint8_t *auth = bytes + numBytes - 12; // set to last 8 bytes of text?
+ uint32_t extraNonce; // pointer was not really used
memcpy(&extraNonce, auth + 8,
sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8);
LOG_INFO("Random nonce value: %d", extraNonce);
diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h
index 32862d95c..6bbcb3b8a 100644
--- a/src/mesh/CryptoEngine.h
+++ b/src/mesh/CryptoEngine.h
@@ -40,9 +40,9 @@ class CryptoEngine
void clearKeys();
void setDHPrivateKey(uint8_t *_private_key);
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
- uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut);
+ uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
virtual bool decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
- size_t numBytes, uint8_t *bytes, uint8_t *bytesOut);
+ size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
virtual bool setDHPublicKey(uint8_t *publicKey);
virtual void hash(uint8_t *bytes, size_t numBytes);
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 2e5df66dd..7caff87c6 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -24,6 +24,7 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
printPacket("Ignore dupe incoming msg", p);
rxDupe++;
+
/* If the original transmitter is doing retransmissions (hopStart equals hopLimit) for a reliable transmission, e.g., when
the ACK got lost, we will handle the packet again to make sure it gets an implicit ACK. */
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
@@ -45,11 +46,15 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
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_REPEATER &&
+ config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
if (Router::cancelSending(p->from, p->id))
txRelayCanceled++;
}
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
+ iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
+ }
}
bool FloodingRouter::isRebroadcaster()
diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h
index d30404b9f..c4af3c4ac 100644
--- a/src/mesh/MemoryPool.h
+++ b/src/mesh/MemoryPool.h
@@ -2,6 +2,8 @@
#include
#include
+#include
+#include
#include "PointerQueue.h"
@@ -9,6 +11,7 @@ template class Allocator
{
public:
+ Allocator() : deleter([this](T *p) { this->release(p); }) {}
virtual ~Allocator() {}
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
@@ -43,12 +46,32 @@ template class Allocator
return p;
}
+ /// Variations of the above methods that return std::unique_ptr instead of raw pointers.
+ using UniqueAllocation = std::unique_ptr &>;
+ /// Return a queable object which has been prefilled with zeros.
+ /// std::unique_ptr wrapped variant of allocZeroed().
+ UniqueAllocation allocUniqueZeroed() { return UniqueAllocation(allocZeroed(), deleter); }
+ /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably
+ /// don't want this version).
+ /// std::unique_ptr wrapped variant of allocZeroed(TickType_t maxWait).
+ UniqueAllocation allocUniqueZeroed(TickType_t maxWait) { return UniqueAllocation(allocZeroed(maxWait), deleter); }
+ /// Return a queable object which is a copy of some other object
+ /// std::unique_ptr wrapped variant of allocCopy(const T &src, TickType_t maxWait).
+ UniqueAllocation allocUniqueCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
+ {
+ return UniqueAllocation(allocCopy(src, maxWait), deleter);
+ }
+
/// Return a buffer for use by others
virtual void release(T *p) = 0;
protected:
// Alloc some storage
virtual T *alloc(TickType_t maxWait) = 0;
+
+ private:
+ // std::unique_ptr Deleter function; calls release().
+ const std::function deleter;
};
/**
diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp
index 58aa8f128..96aec9277 100644
--- a/src/mesh/MeshPacketQueue.cpp
+++ b/src/mesh/MeshPacketQueue.cpp
@@ -16,6 +16,12 @@ inline uint32_t getPriority(const meshtastic_MeshPacket *p)
bool CompareMeshPacketFunc(const meshtastic_MeshPacket *p1, const meshtastic_MeshPacket *p2)
{
assert(p1 && p2);
+
+ // If one packet is in the late transmit window, prefer the other one
+ if ((bool)p1->tx_after != (bool)p2->tx_after) {
+ return !p1->tx_after;
+ }
+
auto p1p = getPriority(p1), p2p = getPriority(p2);
// If priorities differ, use that
// for equal priorities, prefer packets already on mesh.
@@ -94,11 +100,11 @@ meshtastic_MeshPacket *MeshPacketQueue::getFront()
}
/** Attempt to find and remove a packet from this queue. Returns a pointer to the removed packet, or NULL if not found */
-meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id)
+meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool tx_normal, bool tx_late)
{
for (auto it = queue.begin(); it != queue.end(); it++) {
auto p = (*it);
- if (getFrom(p) == from && p->id == id) {
+ if (getFrom(p) == from && p->id == id && ((tx_normal && !p->tx_after) || (tx_late && p->tx_after))) {
queue.erase(it);
return p;
}
@@ -127,9 +133,10 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p)
if (queue.empty()) {
return false; // No packets to replace
}
+
// Check if the packet at the back has a lower priority than the new packet
auto &backPacket = queue.back();
- if (backPacket->priority < p->priority) {
+ if (!backPacket->tx_after && backPacket->priority < p->priority) {
// Remove the back packet
packetPool.release(backPacket);
queue.pop_back();
@@ -138,6 +145,19 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p)
return true;
}
+ if (backPacket->tx_after) {
+ // Check if there's a non-late packet with lower priority
+ auto it = queue.end();
+ auto refPacket = *--it;
+ for (; refPacket->tx_after && it != queue.begin(); refPacket = *--it)
+ ;
+ if (!refPacket->tx_after && refPacket->priority < p->priority) {
+ packetPool.release(refPacket);
+ enqueue(refPacket);
+ return true;
+ }
+ }
+
// If the back packet's priority is not lower, no replacement occurs
return false;
}
\ No newline at end of file
diff --git a/src/mesh/MeshPacketQueue.h b/src/mesh/MeshPacketQueue.h
index 00c15e493..6b2c3998a 100644
--- a/src/mesh/MeshPacketQueue.h
+++ b/src/mesh/MeshPacketQueue.h
@@ -36,7 +36,7 @@ class MeshPacketQueue
meshtastic_MeshPacket *getFront();
/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
- meshtastic_MeshPacket *remove(NodeNum from, PacketId id);
+ meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true);
/* Attempt to find a packet from this queue. Return true if it was found. */
bool find(NodeNum from, PacketId id);
diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h
index 3f25082f1..680926d3c 100644
--- a/src/mesh/MeshTypes.h
+++ b/src/mesh/MeshTypes.h
@@ -49,6 +49,7 @@ typedef int ErrorCode;
/// Alloc and free packets to our global, ISR safe pool
extern Allocator &packetPool;
+using UniquePacketPoolPacket = Allocator::UniqueAllocation;
/**
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 2af85e4f5..9dbe92b7c 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -57,6 +57,7 @@ NodeDB *nodeDB = nullptr;
EXT_RAM_BSS_ATTR meshtastic_DeviceState devicestate;
meshtastic_MyNodeInfo &myNodeInfo = devicestate.my_node;
meshtastic_LocalConfig config;
+meshtastic_DeviceUIConfig uiconfig{.screen_brightness = 153, .screen_timeout = 30};
meshtastic_LocalModuleConfig moduleConfig;
meshtastic_ChannelFile channelFile;
@@ -895,6 +896,7 @@ void NodeDB::pickNewNodeNum()
static const char *prefFileName = "/prefs/db.proto";
static const char *configFileName = "/prefs/config.proto";
+static const char *uiconfigFileName = "/prefs/uiconfig.proto";
static const char *moduleConfigFileName = "/prefs/module.proto";
static const char *channelFileName = "/prefs/channels.proto";
@@ -1054,6 +1056,12 @@ void NodeDB::loadFromDisk()
}
}
+ state = loadProto(uiconfigFileName, meshtastic_DeviceUIConfig_size, sizeof(meshtastic_DeviceUIConfig),
+ &meshtastic_DeviceUIConfig_msg, &uiconfig);
+ if (state == LoadFileResult::LOAD_SUCCESS) {
+ LOG_INFO("Loaded UIConfig");
+ }
+
// 2.4.X - configuration migration to update new default intervals
if (moduleConfig.version < 23) {
LOG_DEBUG("ModuleConfig version %d is stale, upgrading to new default intervals", moduleConfig.version);
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index bfbbacf7d..6ef385e2b 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -29,6 +29,7 @@ extern meshtastic_DeviceState devicestate;
extern meshtastic_ChannelFile channelFile;
extern meshtastic_MyNodeInfo &myNodeInfo;
extern meshtastic_LocalConfig config;
+extern meshtastic_DeviceUIConfig uiconfig;
extern meshtastic_LocalModuleConfig moduleConfig;
extern meshtastic_User &owner;
extern meshtastic_Position localPosition;
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index f087d2f83..4092b5cfd 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -165,6 +165,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
*
* Our sending states progress in the following sequence (the client apps ASSUME THIS SEQUENCE, DO NOT CHANGE IT):
STATE_SEND_MY_INFO, // send our my info record
+ STATE_SEND_UIDATA,
STATE_SEND_OWN_NODEINFO,
STATE_SEND_METADATA,
STATE_SEND_CHANNELS
@@ -189,7 +190,6 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
case STATE_SEND_NOTHING:
LOG_DEBUG("FromRadio=STATE_SEND_NOTHING");
break;
-
case STATE_SEND_MY_INFO:
LOG_DEBUG("FromRadio=STATE_SEND_MY_INFO");
// If the user has specified they don't want our node to share its location, make sure to tell the phone
@@ -197,11 +197,18 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_my_info_tag;
strncpy(myNodeInfo.pio_env, optstr(APP_ENV), sizeof(myNodeInfo.pio_env));
fromRadioScratch.my_info = myNodeInfo;
- state = STATE_SEND_OWN_NODEINFO;
+ state = STATE_SEND_UIDATA;
service->refreshLocalMeshNode(); // Update my NodeInfo because the client will be asking for it soon.
break;
+ case STATE_SEND_UIDATA:
+ LOG_INFO("getFromRadio=STATE_SEND_UIDATA");
+ fromRadioScratch.which_payload_variant = meshtastic_FromRadio_deviceuiConfig_tag;
+ fromRadioScratch.deviceuiConfig = uiconfig;
+ state = STATE_SEND_OWN_NODEINFO;
+ break;
+
case STATE_SEND_OWN_NODEINFO: {
LOG_DEBUG("Send My NodeInfo");
auto us = nodeDB->readNextMeshNode(readIndex);
@@ -285,6 +292,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
LOG_DEBUG("Send config: sessionkey");
fromRadioScratch.config.which_payload_variant = meshtastic_Config_sessionkey_tag;
break;
+ case meshtastic_Config_device_ui_tag: // NOOP!
+ fromRadioScratch.config.which_payload_variant = meshtastic_Config_device_ui_tag;
+ break;
default:
LOG_ERROR("Unknown config type %d", config_state);
}
@@ -519,6 +529,7 @@ bool PhoneAPI::available()
case STATE_SEND_NOTHING:
return false;
case STATE_SEND_MY_INFO:
+ case STATE_SEND_UIDATA:
case STATE_SEND_CHANNELS:
case STATE_SEND_CONFIG:
case STATE_SEND_MODULECONFIG:
@@ -658,4 +669,4 @@ int PhoneAPI::onNotify(uint32_t newValue)
}
return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one
-}
\ No newline at end of file
+}
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index 3247fee5c..681b244c8 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -34,6 +34,7 @@ class PhoneAPI
{
enum State {
STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config
+ STATE_SEND_UIDATA, // send stored data for device-ui
STATE_SEND_MY_INFO, // send our my info record
STATE_SEND_OWN_NODEINFO,
STATE_SEND_METADATA,
@@ -148,6 +149,9 @@ class PhoneAPI
*/
virtual void onNowHasData(uint32_t fromRadioNum) {}
+ /// begin a new connection
+ void handleStartConfig();
+
private:
void releasePhonePacket();
@@ -157,9 +161,6 @@ class PhoneAPI
void releaseClientNotification();
- /// begin a new connection
- void handleStartConfig();
-
bool wasSeenRecently(uint32_t packetId);
/**
@@ -170,4 +171,4 @@ class PhoneAPI
/// If the mesh service tells us fromNum has changed, tell the phone
virtual int onNotify(uint32_t newValue) override;
-};
\ No newline at end of file
+};
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index ab65add19..39cfb6c33 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -254,8 +254,8 @@ uint32_t RadioInterface::getTxDelayMsec()
return random(0, pow(2, CWsize)) * slotTimeMsec;
}
-/** The delay to use when we want to flood a message */
-uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
+/** The CW size to use when calculating SNR_based delays */
+uint8_t RadioInterface::getCWsize(float snr)
{
// The minimum value for a LoRa SNR
const uint32_t SNR_MIN = -20;
@@ -263,10 +263,24 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
// The maximum value for a LoRa SNR
const uint32_t SNR_MAX = 15;
+ return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
+}
+
+/** The worst-case SNR_based packet delay */
+uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
+{
+ uint8_t CWsize = getCWsize(snr);
+ // offset the maximum delay for routers: (2 * CWmax * slotTimeMsec)
+ return (2 * CWmax * slotTimeMsec) + pow(2, CWsize) * slotTimeMsec;
+}
+
+/** The delay to use when we want to flood a message */
+uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
+{
// high SNR = large CW size (Long Delay)
// low SNR = small CW size (Short Delay)
uint32_t delay = 0;
- uint8_t CWsize = map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
+ uint8_t CWsize = getCWsize(snr);
// LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize);
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
@@ -313,6 +327,7 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
out += DEBUG_PORT.mt_sprintf(" failId=%08x", s.ackVariant.fail_id); */
} else {
out += " encrypted";
+ out += DEBUG_PORT.mt_sprintf(" len=%d", p->encrypted.size + sizeof(PacketHeader));
}
if (p->rx_time != 0)
@@ -626,4 +641,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
sendingPacket = p;
return p->encrypted.size + sizeof(PacketHeader);
-}
\ No newline at end of file
+}
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index 1e0adb7a7..b16cf09a8 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -176,9 +176,18 @@ class RadioInterface
/** The delay to use when we want to send something */
uint32_t getTxDelayMsec();
+ /** The CW to use when calculating SNR_based delays */
+ uint8_t getCWsize(float snr);
+
+ /** The worst-case SNR_based packet delay */
+ uint32_t getTxDelayMsecWeightedWorst(float snr);
+
/** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
uint32_t getTxDelayMsecWeighted(float snr);
+ /** If the packet is not already in the late rebroadcast window, move it there */
+ virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }
+
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index d684b126f..f53f13938 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -31,31 +31,7 @@ void LockingArduinoHal::spiEndTransaction()
#if ARCH_PORTDUINO
void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
{
- if (busy == RADIOLIB_NC) {
- spi->transfer(out, in, len);
- } else {
- uint16_t offset = 0;
-
- while (len) {
- uint8_t block_size = (len < 20 ? len : 20);
- spi->transfer((out != NULL ? out + offset : NULL), (in != NULL ? in + offset : NULL), block_size);
- if (block_size == len)
- return;
-
- // ensure GPIO is low
-
- uint32_t start = millis();
- while (digitalRead(busy)) {
- if (!Throttle::isWithinTimespanMs(start, 2000)) {
- LOG_ERROR("GPIO mid-transfer timeout, is it connected?");
- return;
- }
- }
-
- offset += block_size;
- len -= block_size;
- }
- }
+ spi->transfer(out, in, len);
}
#endif
@@ -265,12 +241,12 @@ void RadioLibInterface::onNotify(uint32_t notification)
case ISR_TX:
handleTransmitInterrupt();
startReceive();
- startTransmitTimer();
+ setTransmitDelay();
break;
case ISR_RX:
handleReceiveInterrupt();
startReceive();
- startTransmitTimer();
+ setTransmitDelay();
break;
case TRANSMIT_DELAY_COMPLETED:
@@ -280,23 +256,32 @@ void RadioLibInterface::onNotify(uint32_t notification)
if (!canSendImmediately()) {
setTransmitDelay(); // currently Rx/Tx-ing: reset random delay
} else {
- if (isChannelActive()) { // check if there is currently a LoRa packet on the channel
- startReceive(); // try receiving this packet, afterwards we'll be trying to transmit again
- setTransmitDelay();
+ meshtastic_MeshPacket *txp = txQueue.getFront();
+ assert(txp);
+ long delay_remaining = txp->tx_after ? txp->tx_after - millis() : 0;
+ if (delay_remaining > 0) {
+ // There's still some delay pending on this packet, so resume waiting for it to elapse
+ notifyLater(delay_remaining, TRANSMIT_DELAY_COMPLETED, false);
} else {
- // Send any outgoing packets we have ready as fast as possible to keep the time between channel scan and
- // actual transmission as short as possible
- meshtastic_MeshPacket *txp = txQueue.dequeue();
- assert(txp);
- bool sent = startSend(txp);
- if (sent) {
- // Packet has been sent, count it toward our TX airtime utilization.
- uint32_t xmitMsec = getPacketTime(txp);
- airTime->logAirtime(TX_LOG, xmitMsec);
+ if (isChannelActive()) { // check if there is currently a LoRa packet on the channel
+ startReceive(); // try receiving this packet, afterwards we'll be trying to transmit again
+ setTransmitDelay();
+ } else {
+ // Send any outgoing packets we have ready as fast as possible to keep the time between channel scan and
+ // actual transmission as short as possible
+ txp = txQueue.dequeue();
+ assert(txp);
+ bool sent = startSend(txp);
+ if (sent) {
+ // Packet has been sent, count it toward our TX airtime utilization.
+ uint32_t xmitMsec = getPacketTime(txp);
+ airTime->logAirtime(TX_LOG, xmitMsec);
+ }
}
}
}
} else {
+ // Do nothing, because the queue is empty
}
break;
default:
@@ -307,15 +292,24 @@ void RadioLibInterface::onNotify(uint32_t notification)
void RadioLibInterface::setTransmitDelay()
{
meshtastic_MeshPacket *p = txQueue.getFront();
+ if (!p) {
+ return; // noop if there's nothing in the queue
+ }
+
// We want all sending/receiving to be done by our daemon thread.
// We use a delay here because this packet might have been sent in response to a packet we just received.
// So we want to make sure the other side has had a chance to reconfigure its radio.
- /* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally.
- * This assumption is valid because of the offset generated by the radio to account for the noise
- * floor.
- */
- if (p->rx_snr == 0 && p->rx_rssi == 0) {
+ if (p->tx_after) {
+ unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr) : getTxDelayMsec();
+ unsigned long now = millis();
+ p->tx_after = max(p->tx_after + add_delay, now + add_delay);
+ notifyLater(now - p->tx_after, TRANSMIT_DELAY_COMPLETED, false);
+ } else if (p->rx_snr == 0 && p->rx_rssi == 0) {
+ /* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally.
+ * This assumption is valid because of the offset generated by the radio to account for the noise
+ * floor.
+ */
startTransmitTimer(true);
} else {
// If there is a SNR, start a timer scaled based on that SNR.
@@ -342,6 +336,20 @@ void RadioLibInterface::startTransmitTimerSNR(float snr)
}
}
+/**
+ * If the packet is not already in the late rebroadcast window, move it there
+ */
+void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
+{
+ // Look for non-late packets only, so we don't do this twice!
+ meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false);
+ if (p) {
+ p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr);
+ txQueue.enqueue(p);
+ LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
+ }
+}
+
void RadioLibInterface::handleTransmitInterrupt()
{
// This can be null if we forced the device to enter standby mode. In that case
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index f9dfbfd70..b24879eaf 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -22,18 +22,11 @@
class LockingArduinoHal : public ArduinoHal
{
public:
- LockingArduinoHal(SPIClass &spi, SPISettings spiSettings, RADIOLIB_PIN_TYPE _busy = RADIOLIB_NC)
- : ArduinoHal(spi, spiSettings)
- {
-#if ARCH_PORTDUINO
- busy = _busy;
-#endif
- };
+ LockingArduinoHal(SPIClass &spi, SPISettings spiSettings) : ArduinoHal(spi, spiSettings){};
void spiBeginTransaction() override;
void spiEndTransaction() override;
#if ARCH_PORTDUINO
- RADIOLIB_PIN_TYPE busy;
void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override;
#endif
@@ -150,10 +143,16 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* doing the transmit */
void setTransmitDelay();
- /** random timer with certain min. and max. settings */
+ /**
+ * random timer with certain min. and max. settings
+ * @return Timestamp after which the packet may be sent
+ */
void startTransmitTimer(bool withDelay = true);
- /** timer scaled to SNR of to be flooded packet */
+ /**
+ * timer scaled to SNR of to be flooded packet
+ * @return Timestamp after which the packet may be sent
+ */
void startTransmitTimerSNR(float snr);
void handleTransmitInterrupt();
@@ -203,4 +202,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
virtual void setStandby();
const char *radioLibErr = "RadioLib err=";
+
+ /**
+ * If the packet is not already in the late rebroadcast window, move it there
+ */
+ void clampToLateRebroadcastWindow(NodeNum from, PacketId id);
};
\ No newline at end of file
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 29f016e93..5df1092a4 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -37,7 +37,6 @@ static MemoryDynamic staticPool;
Allocator &packetPool = staticPool;
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
-static uint8_t ScratchEncrypted[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
/**
* Constructor
@@ -339,9 +338,6 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
}
bool decrypted = false;
ChannelIndex chIndex = 0;
- memcpy(bytes, p->encrypted.bytes,
- rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf
- memcpy(ScratchEncrypted, p->encrypted.bytes, rawSize);
#if !(MESHTASTIC_EXCLUDE_PKI)
// Attempt PKI decryption first
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr &&
@@ -349,7 +345,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
rawSize > MESHTASTIC_PKC_OVERHEAD) {
LOG_DEBUG("Attempt PKI decryption");
- if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, ScratchEncrypted,
+ if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, p->encrypted.bytes,
bytes)) {
LOG_INFO("PKI Decryption worked!");
memset(&p->decoded, 0, sizeof(p->decoded));
@@ -361,8 +357,6 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
p->pki_encrypted = true;
memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32);
p->public_key.size = 32;
- // memcpy(bytes, ScratchEncrypted, rawSize); // TODO: Rename the bytes buffers
- // chIndex = 8;
} else {
LOG_ERROR("PKC Decrypted, but pb_decode failed!");
return false;
@@ -379,6 +373,9 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
for (chIndex = 0; chIndex < channels.getNumChannels(); chIndex++) {
// Try to use this hash/channel pair
if (channels.decryptForHash(chIndex, p->channel)) {
+ // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf. Create a
+ // fresh copy for each decrypt attempt.
+ memcpy(bytes, p->encrypted.bytes, rawSize);
// Try to decrypt the packet if we can
crypto->decrypt(p->from, p->id, rawSize, bytes);
@@ -527,9 +524,8 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
*node->user.public_key.bytes);
return meshtastic_Routing_Error_PKI_FAILED;
}
- crypto->encryptCurve25519(p->to, getFrom(p), node->user.public_key, p->id, numbytes, bytes, ScratchEncrypted);
+ crypto->encryptCurve25519(p->to, getFrom(p), node->user.public_key, p->id, numbytes, bytes, p->encrypted.bytes);
numbytes += MESHTASTIC_PKC_OVERHEAD;
- memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes);
p->channel = 0;
p->pki_encrypted = true;
} else {
diff --git a/src/mesh/aes-ccm.cpp b/src/mesh/aes-ccm.cpp
index 8bc2989bf..a650ba2fc 100644
--- a/src/mesh/aes-ccm.cpp
+++ b/src/mesh/aes-ccm.cpp
@@ -18,12 +18,9 @@ static void WPA_PUT_BE16(uint8_t *a, uint16_t val)
static void xor_aes_block(uint8_t *dst, const uint8_t *src)
{
- uint32_t *d = (uint32_t *)dst;
- uint32_t *s = (uint32_t *)src;
- *d++ ^= *s++;
- *d++ ^= *s++;
- *d++ ^= *s++;
- *d++ ^= *s++;
+ for (uint8_t i = 0; i < AES_BLOCK_SIZE; i++) {
+ dst[i] ^= src[i];
+ }
}
static void aes_ccm_auth_start(size_t M, size_t L, const uint8_t *nonce, const uint8_t *aad, size_t aad_len, size_t plain_len,
uint8_t *x)
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 8e2264e93..5e105ab17 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -58,7 +58,13 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
Technical Details: Turns off many of the routine broadcasts to favor ATAK CoT packet stream
and automatic TAK PLI (position location information) broadcasts.
Uses position module configuration to determine TAK PLI broadcast interval. */
- meshtastic_Config_DeviceConfig_Role_TAK_TRACKER = 10
+ meshtastic_Config_DeviceConfig_Role_TAK_TRACKER = 10,
+ /* Description: Will always rebroadcast packets, but will do so after all other modes.
+ Technical Details: Used for router nodes that are intended to provide additional coverage
+ in areas not already covered by other routers, or to bridge around problematic terrain,
+ but should not be given priority over other routers in order to avoid unnecessaraily
+ consuming hops. */
+ meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11
} meshtastic_Config_DeviceConfig_Role;
/* Defines the device's behavior for how messages are rebroadcast */
@@ -588,8 +594,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_Config_DeviceConfig_Role_MIN meshtastic_Config_DeviceConfig_Role_CLIENT
-#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_TAK_TRACKER
-#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_TAK_TRACKER+1))
+#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_ROUTER_LATE
+#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_ROUTER_LATE+1))
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index df7ec072b..83646bf2c 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -770,6 +770,10 @@ typedef struct _meshtastic_MeshPacket {
/* Last byte of the node number of the node that will relay/relayed this packet.
Set by the firmware internally, clients are not supposed to set this. */
uint8_t relay_node;
+ /* *Never* sent over the radio links.
+ Timestamp after which this packet may be sent.
+ Set by the firmware internally, clients are not supposed to set this. */
+ uint32_t tx_after;
} meshtastic_MeshPacket;
/* The bluetooth to device link:
@@ -1178,7 +1182,7 @@ extern "C" {
#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0}
#define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0}
#define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0}
-#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0}
+#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0}
#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0}
#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, ""}
#define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN}
@@ -1203,7 +1207,7 @@ extern "C" {
#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0}
#define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0}
#define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0}
-#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0}
+#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0}
#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0}
#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, ""}
#define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN}
@@ -1301,6 +1305,7 @@ extern "C" {
#define meshtastic_MeshPacket_pki_encrypted_tag 17
#define meshtastic_MeshPacket_next_hop_tag 18
#define meshtastic_MeshPacket_relay_node_tag 19
+#define meshtastic_MeshPacket_tx_after_tag 20
#define meshtastic_NodeInfo_num_tag 1
#define meshtastic_NodeInfo_user_tag 2
#define meshtastic_NodeInfo_position_tag 3
@@ -1497,7 +1502,8 @@ X(a, STATIC, SINGULAR, UINT32, hop_start, 15) \
X(a, STATIC, SINGULAR, BYTES, public_key, 16) \
X(a, STATIC, SINGULAR, BOOL, pki_encrypted, 17) \
X(a, STATIC, SINGULAR, UINT32, next_hop, 18) \
-X(a, STATIC, SINGULAR, UINT32, relay_node, 19)
+X(a, STATIC, SINGULAR, UINT32, relay_node, 19) \
+X(a, STATIC, SINGULAR, UINT32, tx_after, 20)
#define meshtastic_MeshPacket_CALLBACK NULL
#define meshtastic_MeshPacket_DEFAULT NULL
#define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data
@@ -1747,7 +1753,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_FromRadio_size 510
#define meshtastic_Heartbeat_size 0
#define meshtastic_LogRecord_size 426
-#define meshtastic_MeshPacket_size 371
+#define meshtastic_MeshPacket_size 378
#define meshtastic_MqttClientProxyMessage_size 501
#define meshtastic_MyNodeInfo_size 77
#define meshtastic_NeighborInfo_size 258
diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index 2b88702ed..aa8a68f91 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -725,7 +725,6 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
node["position"] = new JSONValue(position);
}
- JSONObject user;
node["long_name"] = new JSONValue(tempNodeInfo->user.long_name);
node["short_name"] = new JSONValue(tempNodeInfo->user.short_name);
char macStr[18];
diff --git a/src/mesh/http/ContentHelper.h b/src/mesh/http/ContentHelper.h
index a80c39f47..e5d3a2f57 100644
--- a/src/mesh/http/ContentHelper.h
+++ b/src/mesh/http/ContentHelper.h
@@ -1,5 +1,6 @@
#include
#include
+#include
#define BoolToString(x) ((x) ? "true" : "false")
diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp
index f9e5d1cc9..38aa2e2a2 100644
--- a/src/mesh/wifi/WiFiAPClient.cpp
+++ b/src/mesh/wifi/WiFiAPClient.cpp
@@ -430,4 +430,4 @@ uint8_t getWifiDisconnectReason()
{
return wifiDisconnectReason;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 2d33b723d..fc3b914e5 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -175,6 +175,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
LOG_INFO("Client set ham mode");
handleSetHamMode(r->set_ham_mode);
break;
+ case meshtastic_AdminMessage_get_ui_config_request_tag: {
+ LOG_INFO("Client is getting device-ui config");
+ handleGetDeviceUIConfig(mp);
+ handled = true;
+ break;
+ }
/**
* Other
@@ -234,6 +240,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
reboot(DEFAULT_REBOOT_SECONDS);
break;
}
+ case meshtastic_AdminMessage_store_ui_config_tag: {
+ LOG_INFO("Storing device-ui config");
+ handleStoreDeviceUIConfig(r->store_ui_config);
+ handled = true;
+ break;
+ }
case meshtastic_AdminMessage_begin_edit_settings_tag: {
LOG_INFO("Begin transaction for editing settings");
hasOpenEditTransaction = true;
@@ -358,7 +370,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
#ifdef ARCH_PORTDUINO
case meshtastic_AdminMessage_exit_simulator_tag:
LOG_INFO("Exiting simulator");
- _exit(0);
+ exit(0);
break;
#endif
@@ -477,7 +489,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_REPEATER)) {
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
- const char *warning = "Rebroadcast mode can't be set to NONE for a router or repeater\n";
+ const char *warning = "Rebroadcast mode can't be set to NONE for a router or repeater";
LOG_WARN(warning);
sendWarning(warning);
}
@@ -621,6 +633,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
requiresReboot = false;
break;
+ case meshtastic_Config_device_ui_tag:
+ // NOOP! This is handled by handleStoreDeviceUIConfig
+ break;
}
if (requiresReboot && !hasOpenEditTransaction) {
disableBluetooth();
@@ -783,6 +798,10 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32
LOG_INFO("Get config: Sessionkey");
res.get_config_response.which_payload_variant = meshtastic_Config_sessionkey_tag;
break;
+ case meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG:
+ // NOOP! This is handled by handleGetDeviceUIConfig
+ res.get_config_response.which_payload_variant = meshtastic_Config_device_ui_tag;
+ break;
}
// NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior.
// So even if we internally use 0 to represent 'use default' we still need to send the value we are
@@ -995,6 +1014,14 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch
}
}
+void AdminModule::handleGetDeviceUIConfig(const meshtastic_MeshPacket &req)
+{
+ meshtastic_AdminMessage r = meshtastic_AdminMessage_init_default;
+ r.which_payload_variant = meshtastic_AdminMessage_get_ui_config_response_tag;
+ r.get_ui_config_response = uiconfig;
+ myReply = allocDataProtobuf(r);
+}
+
void AdminModule::reboot(int32_t seconds)
{
LOG_INFO("Reboot in %d seconds", seconds);
@@ -1015,6 +1042,11 @@ void AdminModule::saveChanges(int saveWhat, bool shouldReboot)
}
}
+void AdminModule::handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg)
+{
+ nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uicfg);
+}
+
void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)
{
// Set call sign and override lora limitations for licensed use
@@ -1081,7 +1113,7 @@ bool AdminModule::messageIsResponse(const meshtastic_AdminMessage *r)
r->which_payload_variant == meshtastic_AdminMessage_get_ringtone_response_tag ||
r->which_payload_variant == meshtastic_AdminMessage_get_device_connection_status_response_tag ||
r->which_payload_variant == meshtastic_AdminMessage_get_node_remote_hardware_pins_response_tag ||
- r->which_payload_variant == meshtastic_NodeRemoteHardwarePinsResponse_node_remote_hardware_pins_tag)
+ r->which_payload_variant == meshtastic_AdminMessage_get_ui_config_response_tag)
return true;
else
return false;
@@ -1097,7 +1129,8 @@ bool AdminModule::messageIsRequest(const meshtastic_AdminMessage *r)
r->which_payload_variant == meshtastic_AdminMessage_get_device_metadata_request_tag ||
r->which_payload_variant == meshtastic_AdminMessage_get_ringtone_request_tag ||
r->which_payload_variant == meshtastic_AdminMessage_get_device_connection_status_request_tag ||
- r->which_payload_variant == meshtastic_AdminMessage_get_node_remote_hardware_pins_request_tag)
+ r->which_payload_variant == meshtastic_AdminMessage_get_node_remote_hardware_pins_request_tag ||
+ r->which_payload_variant == meshtastic_AdminMessage_get_ui_config_request_tag)
return true;
else
return false;
@@ -1123,4 +1156,4 @@ void disableBluetooth()
nrf52Bluetooth->shutdown();
#endif
#endif
-}
\ No newline at end of file
+}
diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h
index b99e86707..ee2ebfd96 100644
--- a/src/modules/AdminModule.h
+++ b/src/modules/AdminModule.h
@@ -43,6 +43,7 @@ class AdminModule : public ProtobufModule, public Obser
void handleGetDeviceMetadata(const meshtastic_MeshPacket &req);
void handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &req);
void handleGetNodeRemoteHardwarePins(const meshtastic_MeshPacket &req);
+ void handleGetDeviceUIConfig(const meshtastic_MeshPacket &req);
/**
* Setters
*/
@@ -52,6 +53,7 @@ class AdminModule : public ProtobufModule, public Obser
void handleSetModuleConfig(const meshtastic_ModuleConfig &c);
void handleSetChannel();
void handleSetHamMode(const meshtastic_HamParameters &req);
+ void handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg);
void reboot(int32_t seconds);
void setPassKey(meshtastic_AdminMessage *res);
diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h
index fd9ffc9b6..a91933a0f 100644
--- a/src/modules/CannedMessageModule.h
+++ b/src/modules/CannedMessageModule.h
@@ -117,8 +117,10 @@ class CannedMessageModule : public SinglePortModule, public ObservableshouldDraw(); }
virtual Observable *getUIFrameObservable() override { return this; }
- virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
virtual bool interceptingKeyboardInput() override;
+#if !HAS_TFT
+ virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
+#endif
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response) override;
@@ -228,4 +230,4 @@ class CannedMessageModule : public SinglePortModule, public Observableinit();
#endif // INPUTBROKER_MATRIX_TYPE
#endif // HAS_BUTTON
-#if ARCH_PORTDUINO
+#if ARCH_PORTDUINO && !HAS_TFT
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
#endif
@@ -195,11 +195,13 @@ void setupModules()
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
new AirQualityTelemetryModule();
}
+#if !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 ||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) {
new HealthTelemetryModule();
}
#endif
+#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
new PowerTelemetryModule();
#endif
@@ -245,4 +247,4 @@ void setupModules()
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
// acks
routingModule = new RoutingModule();
-}
+}
\ No newline at end of file
diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp
index bf842ce55..c42839d97 100644
--- a/src/modules/RangeTestModule.cpp
+++ b/src/modules/RangeTestModule.cpp
@@ -155,8 +155,6 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
LOG_DEBUG("mp.from %d", mp.from);
LOG_DEBUG("mp.rx_snr %f", mp.rx_snr);
LOG_DEBUG("mp.hop_limit %d", mp.hop_limit);
- // LOG_DEBUG("mp.decoded.position.latitude_i %d", mp.decoded.position.latitude_i); // Deprecated
- // LOG_DEBUG("mp.decoded.position.longitude_i %d", mp.decoded.position.longitude_i); // Deprecated
LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
LOG_DEBUG("n->user.long_name %s", n->user.long_name);
LOG_DEBUG("n->user.short_name %s", n->user.short_name);
@@ -194,8 +192,6 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
LOG_DEBUG("mp.from %d", mp.from);
LOG_DEBUG("mp.rx_snr %f", mp.rx_snr);
LOG_DEBUG("mp.hop_limit %d", mp.hop_limit);
- // LOG_DEBUG("mp.decoded.position.latitude_i %d", mp.decoded.position.latitude_i); // Deprecated
- // LOG_DEBUG("mp.decoded.position.longitude_i %d", mp.decoded.position.longitude_i); // Deprecated
LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
LOG_DEBUG("n->user.long_name %s", n->user.long_name);
LOG_DEBUG("n->user.short_name %s", n->user.short_name);
@@ -265,13 +261,21 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
fileToAppend.printf("??:??:??,"); // Time
}
- fileToAppend.printf("%d,", getFrom(&mp)); // From
- fileToAppend.printf("%s,", n->user.long_name); // Long Name
- fileToAppend.printf("%f,", n->position.latitude_i * 1e-7); // Sender Lat
- fileToAppend.printf("%f,", n->position.longitude_i * 1e-7); // Sender Long
- fileToAppend.printf("%f,", gpsStatus->getLatitude() * 1e-7); // RX Lat
- fileToAppend.printf("%f,", gpsStatus->getLongitude() * 1e-7); // RX Long
- fileToAppend.printf("%d,", gpsStatus->getAltitude()); // RX Altitude
+ fileToAppend.printf("%d,", getFrom(&mp)); // From
+ fileToAppend.printf("%s,", n->user.long_name); // Long Name
+ fileToAppend.printf("%f,", n->position.latitude_i * 1e-7); // Sender Lat
+ fileToAppend.printf("%f,", n->position.longitude_i * 1e-7); // Sender Long
+ if (gpsStatus->getIsConnected() || config.position.fixed_position) {
+ fileToAppend.printf("%f,", gpsStatus->getLatitude() * 1e-7); // RX Lat
+ fileToAppend.printf("%f,", gpsStatus->getLongitude() * 1e-7); // RX Long
+ fileToAppend.printf("%d,", gpsStatus->getAltitude()); // RX Altitude
+ } else {
+ // When the phone API is in use, the node info will be updated with position
+ meshtastic_NodeInfoLite *us = nodeDB->getMeshNode(nodeDB->getNodeNum());
+ fileToAppend.printf("%f,", us->position.latitude_i * 1e-7); // RX Lat
+ fileToAppend.printf("%f,", us->position.longitude_i * 1e-7); // RX Long
+ fileToAppend.printf("%d,", us->position.altitude); // RX Altitude
+ }
fileToAppend.printf("%f,", mp.rx_snr); // RX SNR
@@ -292,4 +296,4 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
#endif
return 1;
-}
+}
\ No newline at end of file
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 92d964f7d..008da5c71 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -18,6 +18,7 @@
#include
#include
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
// Sensors
#include "Sensor/AHT10.h"
#include "Sensor/BME280Sensor.h"
@@ -36,7 +37,6 @@
#include "Sensor/SHT31Sensor.h"
#include "Sensor/SHT4XSensor.h"
#include "Sensor/SHTC3Sensor.h"
-#include "Sensor/T1000xSensor.h"
#include "Sensor/TSL2591Sensor.h"
#include "Sensor/VEML7700Sensor.h"
@@ -58,11 +58,12 @@ MLX90632Sensor mlx90632Sensor;
DFRobotLarkSensor dfRobotLarkSensor;
NAU7802Sensor nau7802Sensor;
BMP3XXSensor bmp3xxSensor;
+CGRadSensSensor cgRadSens;
+#endif
#ifdef T1000X_SENSOR_EN
+#include "Sensor/T1000xSensor.h"
T1000xSensor t1000xSensor;
#endif
-CGRadSensSensor cgRadSens;
-
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
@@ -104,7 +105,7 @@ int32_t EnvironmentTelemetryModule::runOnce()
// therefore, we should only enable the sensor loop if measurement is also enabled
#ifdef T1000X_SENSOR_EN
result = t1000xSensor.runOnce();
-#else
+#elif !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
if (dfRobotLarkSensor.hasSensor())
result = dfRobotLarkSensor.runOnce();
if (bmp085Sensor.hasSensor())
@@ -159,8 +160,10 @@ int32_t EnvironmentTelemetryModule::runOnce()
if (!moduleConfig.telemetry.environment_measurement_enabled) {
return disable();
} else {
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
if (bme680Sensor.hasSensor())
result = bme680Sensor.runTrigger();
+#endif
}
if (((lastSentToMesh == 0) ||
@@ -499,6 +502,7 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
meshtastic_AdminMessage *response)
{
AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED;
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
if (dfRobotLarkSensor.hasSensor()) {
result = dfRobotLarkSensor.handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
@@ -609,7 +613,8 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
+#endif
return result;
}
-#endif
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp
index 22534e9f5..1b9b49813 100644
--- a/src/modules/Telemetry/HealthTelemetry.cpp
+++ b/src/modules/Telemetry/HealthTelemetry.cpp
@@ -1,6 +1,6 @@
#include "configuration.h"
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY && !defined(ARCH_PORTDUINO)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "Default.h"
@@ -246,4 +246,4 @@ bool HealthTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
return false;
}
-#endif
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/HealthTelemetry.h b/src/modules/Telemetry/HealthTelemetry.h
index fe84f2d27..01e4c2372 100644
--- a/src/modules/Telemetry/HealthTelemetry.h
+++ b/src/modules/Telemetry/HealthTelemetry.h
@@ -1,6 +1,6 @@
#include "configuration.h"
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY && !defined(ARCH_PORTDUINO)
#pragma once
#include "../mesh/generated/meshtastic/telemetry.pb.h"
@@ -57,4 +57,4 @@ class HealthTelemetryModule : private concurrency::OSThread, public ProtobufModu
uint32_t sensor_read_error_count = 0;
};
-#endif
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp
index 367643849..10133fca5 100644
--- a/src/modules/Telemetry/PowerTelemetry.cpp
+++ b/src/modules/Telemetry/PowerTelemetry.cpp
@@ -56,6 +56,8 @@ int32_t PowerTelemetryModule::runOnce()
// therefore, we should only enable the sensor loop if measurement is also enabled
if (ina219Sensor.hasSensor() && !ina219Sensor.isInitialized())
result = ina219Sensor.runOnce();
+ if (ina226Sensor.hasSensor() && !ina226Sensor.isInitialized())
+ result = ina226Sensor.runOnce();
if (ina260Sensor.hasSensor() && !ina260Sensor.isInitialized())
result = ina260Sensor.runOnce();
if (ina3221Sensor.hasSensor() && !ina3221Sensor.isInitialized())
@@ -170,6 +172,8 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m)
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
if (ina219Sensor.hasSensor())
valid = ina219Sensor.getMetrics(m);
+ if (ina226Sensor.hasSensor())
+ valid = ina226Sensor.getMetrics(m);
if (ina260Sensor.hasSensor())
valid = ina260Sensor.getMetrics(m);
if (ina3221Sensor.hasSensor())
@@ -253,4 +257,4 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
return false;
}
-#endif
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/CurrentSensor.h b/src/modules/Telemetry/Sensor/CurrentSensor.h
new file mode 100644
index 000000000..9827a9aa4
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/CurrentSensor.h
@@ -0,0 +1,13 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#pragma once
+
+class CurrentSensor
+{
+ public:
+ virtual int16_t getCurrentMa() = 0;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.cpp b/src/modules/Telemetry/Sensor/INA219Sensor.cpp
index de69163b4..ea47e265d 100644
--- a/src/modules/Telemetry/Sensor/INA219Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/INA219Sensor.cpp
@@ -45,4 +45,9 @@ uint16_t INA219Sensor::getBusVoltageMv()
return lround(ina219.getBusVoltage_V() * 1000);
}
+int16_t INA219Sensor::getCurrentMa()
+{
+ return lround(ina219.getCurrent_mA());
+}
+
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.h b/src/modules/Telemetry/Sensor/INA219Sensor.h
index 9dded067b..9b6a2fcca 100644
--- a/src/modules/Telemetry/Sensor/INA219Sensor.h
+++ b/src/modules/Telemetry/Sensor/INA219Sensor.h
@@ -3,11 +3,12 @@
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "CurrentSensor.h"
#include "TelemetrySensor.h"
#include "VoltageSensor.h"
#include
-class INA219Sensor : public TelemetrySensor, VoltageSensor
+class INA219Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor
{
private:
Adafruit_INA219 ina219;
@@ -20,6 +21,7 @@ class INA219Sensor : public TelemetrySensor, VoltageSensor
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual uint16_t getBusVoltageMv() override;
+ virtual int16_t getCurrentMa() override;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA226Sensor.cpp b/src/modules/Telemetry/Sensor/INA226Sensor.cpp
new file mode 100644
index 000000000..1ee7cd92e
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/INA226Sensor.cpp
@@ -0,0 +1,58 @@
+#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "INA226.h"
+#include "INA226Sensor.h"
+#include "TelemetrySensor.h"
+
+INA226Sensor::INA226Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_INA226, "INA226") {}
+
+int32_t INA226Sensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+
+ begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first);
+
+ if (!status) {
+ status = ina226.begin();
+ }
+ return initI2CSensor();
+}
+
+void INA226Sensor::setup() {}
+
+void INA226Sensor::begin(TwoWire *wire, uint8_t addr)
+{
+ _wire = wire;
+ _addr = addr;
+ ina226 = INA226(_addr, _wire);
+ _wire->begin();
+}
+
+bool INA226Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ measurement->variant.environment_metrics.has_voltage = true;
+ measurement->variant.environment_metrics.has_current = true;
+
+ // mV conversion to V
+ measurement->variant.environment_metrics.voltage = ina226.getBusVoltage() / 1000;
+ measurement->variant.environment_metrics.current = ina226.getCurrent_mA();
+ return true;
+}
+
+uint16_t INA226Sensor::getBusVoltageMv()
+{
+ return lround(ina226.getBusVoltage());
+}
+
+int16_t INA226Sensor::getCurrentMa()
+{
+ return lround(ina226.getCurrent_mA());
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA226Sensor.h b/src/modules/Telemetry/Sensor/INA226Sensor.h
new file mode 100644
index 000000000..2f71c5b86
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/INA226Sensor.h
@@ -0,0 +1,30 @@
+#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "CurrentSensor.h"
+#include "TelemetrySensor.h"
+#include "VoltageSensor.h"
+#include
+
+class INA226Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor
+{
+ private:
+ uint8_t _addr = INA_ADDR;
+ TwoWire *_wire = &Wire;
+ INA226 ina226 = INA226(_addr, _wire);
+
+ protected:
+ virtual void setup() override;
+ void begin(TwoWire *wire = &Wire, uint8_t addr = INA_ADDR);
+
+ public:
+ INA226Sensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual uint16_t getBusVoltageMv() override;
+ virtual int16_t getCurrentMa() override;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp
index ed09856e2..7ac11dfde 100644
--- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp
@@ -102,4 +102,9 @@ uint16_t INA3221Sensor::getBusVoltageMv()
return lround(ina3221.getVoltage(BAT_CH) * 1000);
}
+int16_t INA3221Sensor::getCurrentMa()
+{
+ return lround(ina3221.getCurrent(BAT_CH));
+}
+
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.h b/src/modules/Telemetry/Sensor/INA3221Sensor.h
index d5121aab6..8eeda3e02 100644
--- a/src/modules/Telemetry/Sensor/INA3221Sensor.h
+++ b/src/modules/Telemetry/Sensor/INA3221Sensor.h
@@ -3,11 +3,12 @@
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "CurrentSensor.h"
#include "TelemetrySensor.h"
#include "VoltageSensor.h"
#include
-class INA3221Sensor : public TelemetrySensor, VoltageSensor
+class INA3221Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor
{
private:
INA3221 ina3221 = INA3221(INA3221_ADDR42_SDA);
@@ -35,6 +36,7 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor
int32_t runOnce() override;
bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual uint16_t getBusVoltageMv() override;
+ virtual int16_t getCurrentMa() override;
};
struct _INA3221Measurement {
diff --git a/src/modules/Telemetry/Sensor/MAX30102Sensor.cpp b/src/modules/Telemetry/Sensor/MAX30102Sensor.cpp
index 88128a6db..f99956925 100644
--- a/src/modules/Telemetry/Sensor/MAX30102Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/MAX30102Sensor.cpp
@@ -1,6 +1,6 @@
#include "configuration.h"
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY && !defined(ARCH_PORTDUINO)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "MAX30102Sensor.h"
@@ -80,4 +80,4 @@ bool MAX30102Sensor::getMetrics(meshtastic_Telemetry *measurement)
return true;
}
-#endif
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/MAX30102Sensor.h b/src/modules/Telemetry/Sensor/MAX30102Sensor.h
index 426d9d365..026e30ed0 100644
--- a/src/modules/Telemetry/Sensor/MAX30102Sensor.h
+++ b/src/modules/Telemetry/Sensor/MAX30102Sensor.h
@@ -1,6 +1,6 @@
#include "configuration.h"
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY && !defined(ARCH_PORTDUINO)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
@@ -23,4 +23,4 @@ class MAX30102Sensor : public TelemetrySensor
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
};
-#endif
+#endif
\ No newline at end of file
diff --git a/src/motion/LIS3DHSensor.cpp b/src/motion/LIS3DHSensor.cpp
index f3f5a62d1..995f74abe 100755
--- a/src/motion/LIS3DHSensor.cpp
+++ b/src/motion/LIS3DHSensor.cpp
@@ -22,7 +22,7 @@ int32_t LIS3DHSensor::runOnce()
{
if (sensor.getClick() > 0) {
uint8_t click = sensor.getClick();
- if (!config.device.double_tap_as_button_press) {
+ if (!config.device.double_tap_as_button_press && config.display.wake_on_tap_or_motion) {
wakeScreen();
}
@@ -34,4 +34,4 @@ int32_t LIS3DHSensor::runOnce()
return MOTION_SENSOR_CHECK_INTERVAL_MS;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/motion/QMA6100PSensor.cpp b/src/motion/QMA6100PSensor.cpp
index 4c5bc14d2..eb81e16c7 100644
--- a/src/motion/QMA6100PSensor.cpp
+++ b/src/motion/QMA6100PSensor.cpp
@@ -88,13 +88,13 @@ bool QMA6100PSingleton::init(ScanI2C::FoundDevice device)
bool status = begin(device.address.address, &Wire);
#endif
if (status != true) {
- LOG_WARN("QMA6100P init begin failed\n");
+ LOG_WARN("QMA6100P init begin failed");
return false;
}
delay(20);
// SW reset to make sure the device starts in a known state
if (softwareReset() != true) {
- LOG_WARN("QMA6100P init reset failed\n");
+ LOG_WARN("QMA6100P init reset failed");
return false;
}
delay(20);
@@ -180,4 +180,4 @@ bool QMA6100PSingleton::setWakeOnMotion()
return true;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 967db04d6..4260ae201 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -19,24 +19,240 @@
#include
#endif
#include "Default.h"
+#if !defined(ARCH_NRF52) || NRF52_USE_JSON
#include "serialization/JSON.h"
#include "serialization/MeshPacketSerializer.h"
+#endif
#include
#include
+#include
+#include
-const int reconnectMax = 5;
+#include
+#if defined(ARCH_PORTDUINO)
+#include
+#elif !defined(ntohl)
+#include
+#define ntohl __ntohl
+#endif
MQTT *mqtt;
-static MemoryDynamic staticMqttPool;
-
-Allocator &mqttPool = staticMqttPool;
+namespace
+{
+constexpr int reconnectMax = 5;
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid
static bool isMqttServerAddressPrivate = false;
+// meshtastic_ServiceEnvelope that automatically releases dynamically allocated memory when it goes out of scope.
+struct DecodedServiceEnvelope : public meshtastic_ServiceEnvelope {
+ DecodedServiceEnvelope() = delete;
+ DecodedServiceEnvelope(const uint8_t *payload, size_t length)
+ : meshtastic_ServiceEnvelope(meshtastic_ServiceEnvelope_init_default),
+ validDecode(pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, this))
+ {
+ }
+ ~DecodedServiceEnvelope()
+ {
+ if (validDecode)
+ pb_release(&meshtastic_ServiceEnvelope_msg, this);
+ }
+ // Clients must check that this is true before using.
+ const bool validDecode;
+};
+
+inline void onReceiveProto(char *topic, byte *payload, size_t length)
+{
+ const DecodedServiceEnvelope e(payload, length);
+ if (!e.validDecode || e.channel_id == NULL || e.gateway_id == NULL || e.packet == NULL) {
+ LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
+ return;
+ }
+ const meshtastic_Channel &ch = channels.getByName(e.channel_id);
+ if (strcmp(e.gateway_id, owner.id) == 0) {
+ // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message.
+ // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node
+ // receives it when we get our own packet back. Then we'll stop our retransmissions.
+ if (isFromUs(e.packet))
+ routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index);
+ else
+ LOG_INFO("Ignore downlink message we originally sent");
+ return;
+ }
+ if (isFromUs(e.packet)) {
+ LOG_INFO("Ignore downlink message we originally sent");
+ return;
+ }
+
+ // Find channel by channel_id and check downlink_enabled
+ if (!(strcmp(e.channel_id, "PKI") == 0 ||
+ (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
+ return;
+ }
+ LOG_INFO("Received MQTT topic %s, len=%u", topic, length);
+
+ UniquePacketPoolPacket p = packetPool.allocUniqueCopy(*e.packet);
+ p->via_mqtt = true; // Mark that the packet was received via MQTT
+ // Unset received SNR/RSSI which might have been added by the MQTT gateway
+ p->rx_snr = 0;
+ p->rx_rssi = 0;
+
+ if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
+ if (moduleConfig.mqtt.encryption_enabled) {
+ LOG_INFO("Ignore decoded message on MQTT, encryption is enabled");
+ return;
+ }
+ if (p->decoded.portnum == meshtastic_PortNum_ADMIN_APP) {
+ LOG_INFO("Ignore decoded admin packet");
+ return;
+ }
+ p->channel = ch.index;
+ }
+
+ // PKI messages get accepted even if we can't decrypt
+ if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && strcmp(e.channel_id, "PKI") == 0) {
+ const meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p.get()));
+ const meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to);
+ // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's
+ // likely they discovered each other via a channel we have downlink enabled for
+ if (isToUs(p.get()) || (tx && tx->has_user && rx && rx->has_user))
+ router->enqueueReceivedMessage(p.release());
+ } else if (router && perhapsDecode(p.get())) // ignore messages if we don't have the channel key
+ router->enqueueReceivedMessage(p.release());
+}
+
+#if !defined(ARCH_NRF52) || NRF52_USE_JSON
+// returns true if this is a valid JSON envelope which we accept on downlink
+inline bool isValidJsonEnvelope(JSONObject &json)
+{
+ // if "sender" is provided, avoid processing packets we uplinked
+ return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) &&
+ (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number
+ (json.find("from") != json.end()) && json["from"]->IsNumber() &&
+ (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us
+ (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type
+ (json.find("payload") != json.end()); // should have a payload
+}
+
+inline void onReceiveJson(byte *payload, size_t length)
+{
+ char payloadStr[length + 1];
+ memcpy(payloadStr, payload, length);
+ payloadStr[length] = 0; // null terminated string
+ std::unique_ptr json_value(JSON::Parse(payloadStr));
+ if (json_value == nullptr) {
+ LOG_ERROR("JSON received payload on MQTT but not a valid JSON");
+ return;
+ }
+
+ JSONObject json;
+ json = json_value->AsObject();
+
+ if (!isValidJsonEnvelope(json)) {
+ LOG_ERROR("JSON received payload on MQTT but not a valid envelope");
+ return;
+ }
+
+ // this is a valid envelope
+ if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) {
+ std::string jsonPayloadStr = json["payload"]->AsString();
+ LOG_INFO("JSON payload %s, length %u", jsonPayloadStr.c_str(), jsonPayloadStr.length());
+
+ // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh
+ meshtastic_MeshPacket *p = router->allocForSending();
+ p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
+ if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
+ (json["channel"]->AsNumber() < channels.getNumChannels()))
+ p->channel = json["channel"]->AsNumber();
+ if (json.find("to") != json.end() && json["to"]->IsNumber())
+ p->to = json["to"]->AsNumber();
+ if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
+ p->hop_limit = json["hopLimit"]->AsNumber();
+ if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
+ memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
+ p->decoded.payload.size = jsonPayloadStr.length();
+ service->sendToMesh(p, RX_SRC_LOCAL);
+ } else {
+ LOG_WARN("Received MQTT json payload too long, drop");
+ }
+ } else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) {
+ // invent the "sendposition" type for a valid envelope
+ JSONObject posit;
+ posit = json["payload"]->AsObject(); // get nested JSON Position
+ meshtastic_Position pos = meshtastic_Position_init_default;
+ if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber())
+ pos.latitude_i = posit["latitude_i"]->AsNumber();
+ if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber())
+ pos.longitude_i = posit["longitude_i"]->AsNumber();
+ if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber())
+ pos.altitude = posit["altitude"]->AsNumber();
+ if (posit.find("time") != posit.end() && posit["time"]->IsNumber())
+ pos.time = posit["time"]->AsNumber();
+
+ // construct protobuf data packet using POSITION, send it to the mesh
+ meshtastic_MeshPacket *p = router->allocForSending();
+ p->decoded.portnum = meshtastic_PortNum_POSITION_APP;
+ if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
+ (json["channel"]->AsNumber() < channels.getNumChannels()))
+ p->channel = json["channel"]->AsNumber();
+ if (json.find("to") != json.end() && json["to"]->IsNumber())
+ p->to = json["to"]->AsNumber();
+ if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
+ p->hop_limit = json["hopLimit"]->AsNumber();
+ p->decoded.payload.size =
+ pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Position_msg,
+ &pos); // make the Data protobuf from position
+ service->sendToMesh(p, RX_SRC_LOCAL);
+ } else {
+ LOG_DEBUG("JSON ignore downlink message with unsupported type");
+ }
+}
+#endif
+
+/// Determines if the given IPAddress is a private IPv4 address, i.e. not routable on the public internet.
+bool isPrivateIpAddress(const IPAddress &ip)
+{
+ constexpr struct {
+ uint32_t network;
+ uint32_t mask;
+ } privateCidrRanges[] = {
+ {.network = 192u << 24 | 168 << 16, .mask = 0xffff0000}, // 192.168.0.0/16
+ {.network = 172u << 24 | 16 << 16, .mask = 0xfff00000}, // 172.16.0.0/12
+ {.network = 169u << 24 | 254 << 16, .mask = 0xffff0000}, // 169.254.0.0/16
+ {.network = 10u << 24, .mask = 0xff000000}, // 10.0.0.0/8
+ {.network = 127u << 24 | 1, .mask = 0xffffffff}, // 127.0.0.1/32
+ };
+ const uint32_t addr = ntohl(ip);
+ for (const auto &cidrRange : privateCidrRanges) {
+ if (cidrRange.network == (addr & cidrRange.mask)) {
+ LOG_INFO("MQTT server on a private IP");
+ return true;
+ }
+ }
+ return false;
+}
+
+// Separate a [:] string. Returns a pair containing the parsed host and port. If the port is
+// not present in the input string, or is invalid, the value of the `port` argument will be returned.
+std::pair parseHostAndPort(String server, uint16_t port = 0)
+{
+ const int delimIndex = server.indexOf(':');
+ if (delimIndex > 0) {
+ const long parsedPort = server.substring(delimIndex + 1, server.length()).toInt();
+ if (parsedPort < 1 || parsedPort > UINT16_MAX) {
+ LOG_WARN("Invalid MQTT port %d: %s", parsedPort, server.c_str());
+ } else {
+ port = parsedPort;
+ }
+ server[delimIndex] = 0;
+ }
+ return std::make_pair(std::move(server), port);
+}
+} // namespace
+
void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length)
{
mqtt->onReceive(topic, payload, length);
@@ -49,170 +265,32 @@ void MQTT::onClientProxyReceive(meshtastic_MqttClientProxyMessage msg)
void MQTT::onReceive(char *topic, byte *payload, size_t length)
{
- meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default;
-
- if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) {
- // check if this is a json payload message by comparing the topic start
- char payloadStr[length + 1];
- memcpy(payloadStr, payload, length);
- payloadStr[length] = 0; // null terminated string
- JSONValue *json_value = JSON::Parse(payloadStr);
- if (json_value != NULL) {
- // check if it is a valid envelope
- JSONObject json;
- json = json_value->AsObject();
-
- // parse the channel name from the topic string
- // the topic has been checked above for having jsonTopic prefix, so just move past it
- char *ptr = topic + jsonTopic.length();
- ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character
- meshtastic_Channel sendChannel = channels.getByName(ptr);
- // We allow downlink JSON packets only on a channel named "mqtt"
- if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 &&
- sendChannel.settings.downlink_enabled) {
- if (isValidJsonEnvelope(json)) {
- // this is a valid envelope
- if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) {
- std::string jsonPayloadStr = json["payload"]->AsString();
- LOG_INFO("JSON payload %s, length %u", jsonPayloadStr.c_str(), jsonPayloadStr.length());
-
- // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh
- meshtastic_MeshPacket *p = router->allocForSending();
- p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
- if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
- (json["channel"]->AsNumber() < channels.getNumChannels()))
- p->channel = json["channel"]->AsNumber();
- if (json.find("to") != json.end() && json["to"]->IsNumber())
- p->to = json["to"]->AsNumber();
- if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
- p->hop_limit = json["hopLimit"]->AsNumber();
- if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
- memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
- p->decoded.payload.size = jsonPayloadStr.length();
- service->sendToMesh(p, RX_SRC_LOCAL);
- } else {
- LOG_WARN("Received MQTT json payload too long, drop");
- }
- } else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) {
- // invent the "sendposition" type for a valid envelope
- JSONObject posit;
- posit = json["payload"]->AsObject(); // get nested JSON Position
- meshtastic_Position pos = meshtastic_Position_init_default;
- if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber())
- pos.latitude_i = posit["latitude_i"]->AsNumber();
- if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber())
- pos.longitude_i = posit["longitude_i"]->AsNumber();
- if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber())
- pos.altitude = posit["altitude"]->AsNumber();
- if (posit.find("time") != posit.end() && posit["time"]->IsNumber())
- pos.time = posit["time"]->AsNumber();
-
- // construct protobuf data packet using POSITION, send it to the mesh
- meshtastic_MeshPacket *p = router->allocForSending();
- p->decoded.portnum = meshtastic_PortNum_POSITION_APP;
- if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
- (json["channel"]->AsNumber() < channels.getNumChannels()))
- p->channel = json["channel"]->AsNumber();
- if (json.find("to") != json.end() && json["to"]->IsNumber())
- p->to = json["to"]->AsNumber();
- if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
- p->hop_limit = json["hopLimit"]->AsNumber();
- p->decoded.payload.size =
- pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
- &meshtastic_Position_msg, &pos); // make the Data protobuf from position
- service->sendToMesh(p, RX_SRC_LOCAL);
- } else {
- LOG_DEBUG("JSON ignore downlink message with unsupported type");
- }
- } else {
- LOG_ERROR("JSON received payload on MQTT but not a valid envelope");
- }
- } else {
- LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled");
- }
- } else {
- // no json, this is an invalid payload
- LOG_ERROR("JSON received payload on MQTT but not a valid JSON");
- }
- delete json_value;
- } else {
- if (length == 0) {
- LOG_WARN("Empty MQTT payload received, topic %s!", topic);
- return;
- } else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) {
- LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
- return;
- } else {
- if (e.channel_id == NULL || e.gateway_id == NULL) {
- LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
- return;
- }
- meshtastic_Channel ch = channels.getByName(e.channel_id);
- if (strcmp(e.gateway_id, owner.id) == 0) {
- // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message.
- // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node
- // receives it when we get our own packet back. Then we'll stop our retransmissions.
- if (e.packet && isFromUs(e.packet))
- routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index);
- else
- LOG_INFO("Ignore downlink message we originally sent");
- } else {
- // Find channel by channel_id and check downlink_enabled
- if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) ||
- (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) {
- LOG_INFO("Received MQTT topic %s, len=%u", topic, length);
- meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet);
- p->via_mqtt = true; // Mark that the packet was received via MQTT
-
- if (isFromUs(p)) {
- LOG_INFO("Ignore downlink message we originally sent");
- packetPool.release(p);
- free(e.channel_id);
- free(e.gateway_id);
- free(e.packet);
- return;
- }
- if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
- if (moduleConfig.mqtt.encryption_enabled) {
- LOG_INFO("Ignore decoded message on MQTT, encryption is enabled");
- packetPool.release(p);
- free(e.channel_id);
- free(e.gateway_id);
- free(e.packet);
- return;
- }
- if (p->decoded.portnum == meshtastic_PortNum_ADMIN_APP) {
- LOG_INFO("Ignore decoded admin packet");
- packetPool.release(p);
- free(e.channel_id);
- free(e.gateway_id);
- free(e.packet);
- return;
- }
- p->channel = ch.index;
- }
-
- // PKI messages get accepted even if we can't decrypt
- if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag &&
- strcmp(e.channel_id, "PKI") == 0) {
- const meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p));
- const meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to);
- // Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's
- // likely they discovered each other via a channel we have downlink enabled for
- if (isToUs(p) || (tx && tx->has_user && rx && rx->has_user))
- router->enqueueReceivedMessage(p);
- } else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key
- router->enqueueReceivedMessage(p);
- else
- packetPool.release(p);
- }
- }
- }
- // make sure to free both strings and the MeshPacket (passing in NULL is acceptable)
- free(e.channel_id);
- free(e.gateway_id);
- free(e.packet);
+ if (length == 0) {
+ LOG_WARN("Empty MQTT payload received, topic %s!", topic);
+ return;
}
+
+ // check if this is a json payload message by comparing the topic start
+ if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) {
+#if !defined(ARCH_NRF52) || NRF52_USE_JSON
+ // parse the channel name from the topic string
+ // the topic has been checked above for having jsonTopic prefix, so just move past it
+ char *channelName = topic + jsonTopic.length();
+ // if another "/" was added, parse string up to that character
+ channelName = strtok(channelName, "/") ? strtok(channelName, "/") : channelName;
+ // We allow downlink JSON packets only on a channel named "mqtt"
+ meshtastic_Channel &sendChannel = channels.getByName(channelName);
+ if (!(strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 &&
+ sendChannel.settings.downlink_enabled)) {
+ LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled");
+ return;
+ }
+ onReceiveJson(payload, length);
+#endif
+ return;
+ }
+
+ onReceiveProto(topic, payload, length);
}
void mqttInit()
@@ -249,10 +327,10 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs);
}
- isMqttServerAddressPrivate = isPrivateIpAddress(moduleConfig.mqtt.address);
- if (isMqttServerAddressPrivate) {
- LOG_INFO("MQTT server on a private IP");
- }
+ String host = parseHostAndPort(moduleConfig.mqtt.address).first;
+ isConfiguredForDefaultServer = host.length() == 0 || host == default_mqtt_address;
+ IPAddress ip;
+ isMqttServerAddressPrivate = ip.fromString(host.c_str()) && isPrivateIpAddress(ip);
#if HAS_NETWORKING
if (!moduleConfig.mqtt.proxy_to_client_enabled)
@@ -368,14 +446,9 @@ void MQTT::reconnect()
pubSub.setClient(mqttClient);
#endif
- String server = String(serverAddr);
- int delimIndex = server.indexOf(':');
- if (delimIndex > 0) {
- String port = server.substring(delimIndex + 1, server.length());
- server[delimIndex] = 0;
- serverPort = port.toInt();
- serverAddr = server.c_str();
- }
+ std::pair hostAndPort = parseHostAndPort(serverAddr, serverPort);
+ serverAddr = hostAndPort.first.c_str();
+ serverPort = hostAndPort.second;
pubSub.setServer(serverAddr, serverPort);
pubSub.setBufferSize(512);
@@ -388,6 +461,7 @@ void MQTT::reconnect()
enabled = true; // Start running background process again
runASAP = true;
reconnectCount = 0;
+ isMqttServerAddressPrivate = isPrivateIpAddress(mqttClient.remoteIP());
publishNodeInfo();
sendSubscriptions();
@@ -504,39 +578,37 @@ void MQTT::publishNodeInfo()
}
void MQTT::publishQueuedMessages()
{
- if (!mqttQueue.isEmpty()) {
- LOG_DEBUG("Publish enqueued MQTT message");
- meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0);
- size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env);
- std::string topic;
- if (env->packet->pki_encrypted) {
- topic = cryptTopic + "PKI/" + owner.id;
- } else {
- topic = cryptTopic + env->channel_id + "/" + owner.id;
- }
- LOG_INFO("publish %s, %u bytes from queue", topic.c_str(), numBytes);
+ if (mqttQueue.isEmpty())
+ return;
- publish(topic.c_str(), bytes, numBytes, false);
+ LOG_DEBUG("Publish enqueued MQTT message");
+ const std::unique_ptr entry(mqttQueue.dequeuePtr(0));
+ LOG_INFO("publish %s, %u bytes from queue", entry->topic.c_str(), entry->envBytes.size());
+ publish(entry->topic.c_str(), entry->envBytes.data(), entry->envBytes.size(), false);
#if !defined(ARCH_NRF52) || \
defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ###
- if (moduleConfig.mqtt.json_enabled) {
- // handle json topic
- auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet);
- if (jsonString.length() != 0) {
- std::string topicJson;
- if (env->packet->pki_encrypted) {
- topicJson = jsonTopic + "PKI/" + owner.id;
- } else {
- topicJson = jsonTopic + env->channel_id + "/" + owner.id;
- }
- LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
- publish(topicJson.c_str(), jsonString.c_str(), false);
- }
- }
-#endif // ARCH_NRF52 NRF52_USE_JSON
- mqttPool.release(env);
+ if (!moduleConfig.mqtt.json_enabled)
+ return;
+
+ // handle json topic
+ const DecodedServiceEnvelope env(entry->envBytes.data(), entry->envBytes.size());
+ if (!env.validDecode || env.packet == NULL || env.channel_id == NULL)
+ return;
+
+ auto jsonString = MeshPacketSerializer::JsonSerialize(env.packet);
+ if (jsonString.length() == 0)
+ return;
+
+ std::string topicJson;
+ if (env.packet->pki_encrypted) {
+ topicJson = jsonTopic + "PKI/" + owner.id;
+ } else {
+ topicJson = jsonTopic + env.channel_id + "/" + owner.id;
}
+ LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
+ publish(topicJson.c_str(), jsonString.c_str(), false);
+#endif // ARCH_NRF52 NRF52_USE_JSON
}
void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex)
@@ -555,8 +627,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
// mp_decoded will not be decoded when it's PKI encrypted and not directed to us
if (mp_decoded.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
// For uplinking other's packets, check if it's not OK to MQTT or if it's an older packet without the bitfield
- bool dontUplink = !mp_decoded.decoded.has_bitfield ||
- (mp_decoded.decoded.has_bitfield && !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK));
+ bool dontUplink = !mp_decoded.decoded.has_bitfield || !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK);
// check for the lowest bit of the data bitfield set false, and the use of one of the default keys.
if (!isFromUs(&mp_decoded) && !isMqttServerAddressPrivate && dontUplink &&
(ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) ||
@@ -565,9 +636,8 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
return;
}
- if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 &&
- (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP ||
- mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) {
+ if (isConfiguredForDefaultServer && (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP ||
+ mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) {
LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt");
return;
}
@@ -575,59 +645,56 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
// Either encrypted packet (we couldn't decrypt) is marked as pki_encrypted, or we could decode the PKI encrypted packet
bool isPKIEncrypted = mp_encrypted.pki_encrypted || mp_decoded.pki_encrypted;
// If it was to a channel, check uplink enabled, else must be pki_encrypted
- if ((ch.settings.uplink_enabled && !isPKIEncrypted) || isPKIEncrypted) {
- const char *channelId = isPKIEncrypted ? "PKI" : channels.getGlobalId(chIndex);
+ if (!(ch.settings.uplink_enabled || isPKIEncrypted))
+ return;
+ const char *channelId = isPKIEncrypted ? "PKI" : channels.getGlobalId(chIndex);
- meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed();
- env->channel_id = (char *)channelId;
- env->gateway_id = owner.id;
+ LOG_DEBUG("MQTT onSend - Publish ");
+ const meshtastic_MeshPacket *p;
+ if (moduleConfig.mqtt.encryption_enabled) {
+ p = &mp_encrypted;
+ LOG_DEBUG("encrypted message");
+ } else if (mp_decoded.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
+ p = &mp_decoded;
+ LOG_DEBUG("portnum %i message", mp_decoded.decoded.portnum);
+ } else {
+ LOG_DEBUG("nothing, pkt not decrypted");
+ return; // Don't upload a still-encrypted PKI packet if not encryption_enabled
+ }
- LOG_DEBUG("MQTT onSend - Publish ");
- if (moduleConfig.mqtt.encryption_enabled) {
- env->packet = (meshtastic_MeshPacket *)&mp_encrypted;
- LOG_DEBUG("encrypted message");
- } else if (mp_decoded.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
- env->packet = (meshtastic_MeshPacket *)&mp_decoded;
- LOG_DEBUG("portnum %i message", env->packet->decoded.portnum);
- } else {
- LOG_DEBUG("nothing, pkt not decrypted");
- mqttPool.release(env);
- return; // Don't upload a still-encrypted PKI packet if not encryption_enabled
- }
+ const meshtastic_ServiceEnvelope env = {
+ .packet = const_cast(p), .channel_id = const_cast(channelId), .gateway_id = owner.id};
+ size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &env);
+ std::string topic = cryptTopic + channelId + "/" + owner.id;
- if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) {
- size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env);
- std::string topic = cryptTopic + channelId + "/" + owner.id;
- LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes);
-
- publish(topic.c_str(), bytes, numBytes, false);
+ if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) {
+ LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes);
+ publish(topic.c_str(), bytes, numBytes, false);
#if !defined(ARCH_NRF52) || \
defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ###
- if (moduleConfig.mqtt.json_enabled) {
- // handle json topic
- auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded);
- if (jsonString.length() != 0) {
- std::string topicJson = jsonTopic + channelId + "/" + owner.id;
- LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(),
- jsonString.c_str());
- publish(topicJson.c_str(), jsonString.c_str(), false);
- }
- }
+ if (!moduleConfig.mqtt.json_enabled)
+ return;
+ // handle json topic
+ auto jsonString = MeshPacketSerializer::JsonSerialize(&mp_decoded);
+ if (jsonString.length() == 0)
+ return;
+ std::string topicJson = jsonTopic + channelId + "/" + owner.id;
+ LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
+ publish(topicJson.c_str(), jsonString.c_str(), false);
#endif // ARCH_NRF52 NRF52_USE_JSON
+ } else {
+ LOG_INFO("MQTT not connected, queue packet");
+ QueueEntry *entry;
+ if (mqttQueue.numFree() == 0) {
+ LOG_WARN("MQTT queue is full, discard oldest");
+ entry = mqttQueue.dequeuePtr(0);
} else {
- LOG_INFO("MQTT not connected, queue packet");
- if (mqttQueue.numFree() == 0) {
- LOG_WARN("MQTT queue is full, discard oldest");
- meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0);
- if (d)
- mqttPool.release(d);
- }
- // make a copy of serviceEnvelope and queue it
- meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env);
- assert(mqttQueue.enqueue(copied, 0));
+ entry = new QueueEntry;
}
- mqttPool.release(env);
+ entry->topic = std::move(topic);
+ entry->envBytes.assign(bytes, numBytes);
+ assert(mqttQueue.enqueue(entry, 0));
}
}
@@ -636,148 +703,68 @@ void MQTT::perhapsReportToMap()
if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly()))
return;
- if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs)) {
+ if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs))
return;
- } else {
- if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
- last_report_to_map = millis();
- if (map_position_precision == 0)
- LOG_WARN("MQTT Map report enabled, but precision is 0");
- if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)
- LOG_WARN("MQTT Map report enabled, but no position available");
- return;
- }
- // Allocate ServiceEnvelope and fill it
- meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed();
- se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id
- se->gateway_id = owner.id;
-
- // Allocate MeshPacket and fill it
- meshtastic_MeshPacket *mp = packetPool.allocZeroed();
- mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
- mp->from = nodeDB->getNodeNum();
- mp->to = NODENUM_BROADCAST;
- mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP;
-
- // Fill MapReport message
- meshtastic_MapReport mapReport = meshtastic_MapReport_init_default;
- memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name));
- memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name));
- mapReport.role = config.device.role;
- mapReport.hw_model = owner.hw_model;
- strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version));
- mapReport.region = config.lora.region;
- mapReport.modem_preset = config.lora.modem_preset;
- mapReport.has_default_channel = channels.hasDefaultChannel();
-
- // Set position with precision (same as in PositionModule)
- if (map_position_precision < 32 && map_position_precision > 0) {
- mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision));
- mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision));
- mapReport.latitude_i += (1 << (31 - map_position_precision));
- mapReport.longitude_i += (1 << (31 - map_position_precision));
- } else {
- mapReport.latitude_i = localPosition.latitude_i;
- mapReport.longitude_i = localPosition.longitude_i;
- }
- mapReport.altitude = localPosition.altitude;
- mapReport.position_precision = map_position_precision;
-
- mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true);
-
- // Encode MapReport message and set it to MeshPacket in ServiceEnvelope
- mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes),
- &meshtastic_MapReport_msg, &mapReport);
- se->packet = mp;
-
- size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se);
-
- LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str());
- publish(mapTopic.c_str(), bytes, numBytes, false);
-
- // Release the allocated memory for ServiceEnvelope and MeshPacket
- mqttPool.release(se);
- packetPool.release(mp);
-
- // Update the last report time
+ if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
last_report_to_map = millis();
- }
-}
-
-bool MQTT::isValidJsonEnvelope(JSONObject &json)
-{
- // if "sender" is provided, avoid processing packets we uplinked
- return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) &&
- (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number
- (json.find("from") != json.end()) && json["from"]->IsNumber() &&
- (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us
- (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type
- (json.find("payload") != json.end()); // should have a payload
-}
-
-bool MQTT::isPrivateIpAddress(const char address[])
-{
- // Min. length like 10.0.0.0 (8), max like 192.168.255.255:65535 (21)
- size_t length = strlen(address);
- if (length < 8 || length > 21) {
- return false;
+ if (map_position_precision == 0)
+ LOG_WARN("MQTT Map report enabled, but precision is 0");
+ if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)
+ LOG_WARN("MQTT Map report enabled, but no position available");
+ return;
}
- // Ensure the address contains only digits and dots and maybe a colon.
- // Some limited validation is done.
- // Even if it's not a valid IP address, we will know it's not a domain.
- bool hasColon = false;
- int numDots = 0;
- for (size_t i = 0; i < length; i++) {
- if (!isdigit(address[i]) && address[i] != '.' && address[i] != ':') {
- return false;
- }
+ // Allocate MeshPacket and fill it
+ meshtastic_MeshPacket *mp = packetPool.allocZeroed();
+ mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
+ mp->from = nodeDB->getNodeNum();
+ mp->to = NODENUM_BROADCAST;
+ mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP;
- // Dots can't be the first character, immediately follow another dot,
- // occur more than 3 times, or occur after a colon.
- if (address[i] == '.') {
- if (++numDots > 3 || i == 0 || address[i - 1] == '.' || hasColon) {
- return false;
- }
- }
- // There can only be a single colon, and it can only occur after 3 dots
- else if (address[i] == ':') {
- if (hasColon || numDots < 3) {
- return false;
- }
+ // Fill MapReport message
+ meshtastic_MapReport mapReport = meshtastic_MapReport_init_default;
+ memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name));
+ memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name));
+ mapReport.role = config.device.role;
+ mapReport.hw_model = owner.hw_model;
+ strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version));
+ mapReport.region = config.lora.region;
+ mapReport.modem_preset = config.lora.modem_preset;
+ mapReport.has_default_channel = channels.hasDefaultChannel();
- hasColon = true;
- }
+ // Set position with precision (same as in PositionModule)
+ if (map_position_precision < 32 && map_position_precision > 0) {
+ mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision));
+ mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision));
+ mapReport.latitude_i += (1 << (31 - map_position_precision));
+ mapReport.longitude_i += (1 << (31 - map_position_precision));
+ } else {
+ mapReport.latitude_i = localPosition.latitude_i;
+ mapReport.longitude_i = localPosition.longitude_i;
}
+ mapReport.altitude = localPosition.altitude;
+ mapReport.position_precision = map_position_precision;
- // Final validation for IPv4 address and port format.
- // Note that the values of octets haven't been tested, only the address format.
- if (numDots != 3) {
- return false;
- }
+ mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true);
- // Check the easy ones first.
- if (strcmp(address, "127.0.0.1") == 0 || strncmp(address, "10.", 3) == 0 || strncmp(address, "192.168", 7) == 0 ||
- strncmp(address, "169.254", 7) == 0) {
- return true;
- }
+ // Encode MapReport message into the MeshPacket
+ mp->decoded.payload.size =
+ pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_MapReport_msg, &mapReport);
- // See if it's definitely not a 172 address.
- if (strncmp(address, "172", 3) != 0) {
- return false;
- }
+ // Encode the MeshPacket into a binary ServiceEnvelope and publish
+ const meshtastic_ServiceEnvelope se = {
+ .packet = mp,
+ .channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()), // Use primary channel as the channel_id
+ .gateway_id = owner.id};
+ size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &se);
- // We know it's a 172 address, now see if the second octet is 2 digits.
- if (address[6] != '.') {
- return false;
- }
+ LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str());
+ publish(mapTopic.c_str(), bytes, numBytes, false);
- // Copy the second octet into a secondary buffer we can null-terminate and parse.
- char octet2[3];
- strncpy(octet2, address + 4, 2);
- octet2[2] = 0;
+ // Release the allocated memory for MeshPacket
+ packetPool.release(mp);
- int octet2Num = atoi(octet2);
- return octet2Num >= 16 && octet2Num <= 31;
-}
+ // Update the last report time
+ last_report_to_map = millis();
+}
\ No newline at end of file
diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h
index 7e0378238..cb1fffcc9 100644
--- a/src/mqtt/MQTT.h
+++ b/src/mqtt/MQTT.h
@@ -5,7 +5,9 @@
#include "concurrency/OSThread.h"
#include "mesh/Channels.h"
#include "mesh/generated/meshtastic/mqtt.pb.h"
+#if !defined(ARCH_NRF52) || NRF52_USE_JSON
#include "serialization/JSON.h"
+#endif
#if HAS_WIFI
#include
#if !defined(ARCH_PORTDUINO)
@@ -77,10 +79,17 @@ class MQTT : private concurrency::OSThread
void start() { setIntervalFromNow(0); };
+ bool isUsingDefaultServer() { return isConfiguredForDefaultServer; }
+
protected:
- PointerQueue mqttQueue;
+ struct QueueEntry {
+ std::string topic;
+ std::basic_string envBytes; // binary/pb_encode_to_bytes ServiceEnvelope
+ };
+ PointerQueue mqttQueue;
int reconnectCount = 0;
+ bool isConfiguredForDefaultServer = true;
virtual int32_t runOnce() override;
@@ -117,17 +126,10 @@ class MQTT : private concurrency::OSThread
// Check if we should report unencrypted information about our node for consumption by a map
void perhapsReportToMap();
- // returns true if this is a valid JSON envelope which we accept on downlink
- bool isValidJsonEnvelope(JSONObject &json);
-
- /// Determines if the given address is a private IPv4 address, i.e. not routable on the public internet.
- /// These are the ranges: 127.0.0.1, 10.0.0.0-10.255.255.255, 172.16.0.0-172.31.255.255, 192.168.0.0-192.168.255.255.
- bool isPrivateIpAddress(const char address[]);
-
/// Return 0 if sleep is okay, veto sleep if we are connected to pubsub server
// int preflightSleepCb(void *unused = NULL) { return pubSub.connected() ? 1 : 0; }
};
void mqttInit();
-extern MQTT *mqtt;
+extern MQTT *mqtt;
\ No newline at end of file
diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index 1a274aa28..742b295b5 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -174,6 +174,8 @@
#define HW_VENDOR meshtastic_HardwareModel_SENSECAP_INDICATOR
#elif defined(SEEED_XIAO_S3)
#define HW_VENDOR meshtastic_HardwareModel_SEEED_XIAO_S3
+#elif defined(MESH_TAB)
+#define HW_VENDOR meshtastic_HardwareModel_MESH_TAB
#endif
// -----------------------------------------------------------------------------
diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index 750cc1630..0c981bf16 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -21,9 +21,12 @@
#include
#include
+#include "platform/portduino/USBHal.h"
+
std::map settingsMap;
std::map settingsStrings;
std::ofstream traceFile;
+Ch341Hal *ch341Hal = nullptr;
char *configPath = nullptr;
char *optionMac = nullptr;
@@ -87,7 +90,7 @@ void getMacAddr(uint8_t *dmac)
if (strlen(optionMac) >= 12) {
MAC_from_string(optionMac, dmac);
} else {
- uint32_t hwId;
+ uint32_t hwId = {0};
sscanf(optionMac, "%u", &hwId);
dmac[0] = 0x80;
dmac[1] = 0;
@@ -101,10 +104,9 @@ void getMacAddr(uint8_t *dmac)
exit;
} else {
- struct hci_dev_info di;
+ struct hci_dev_info di = {0};
di.dev_id = 0;
bdaddr_t bdaddr;
- char addr[18];
int btsock;
btsock = socket(AF_BLUETOOTH, SOCK_RAW, 1);
if (btsock < 0) { // If anything fails, just return with the default value
@@ -151,6 +153,7 @@ void portduinoSetup()
std::string gpioChipName = "gpiochip";
settingsStrings[i2cdev] = "";
settingsStrings[keyboardDevice] = "";
+ settingsStrings[pointerDevice] = "";
settingsStrings[webserverrootpath] = "";
settingsStrings[spidev] = "";
settingsStrings[displayspidev] = "";
@@ -201,8 +204,36 @@ void portduinoSetup()
}
}
}
-
+ // if we're using a usermode driver, we need to initialize it here, to get a serial number back for mac address
uint8_t dmac[6] = {0};
+ if (settingsStrings[spidev] == "ch341") {
+ ch341Hal = new Ch341Hal(0);
+ if (settingsStrings[lora_usb_serial_num] != "") {
+ ch341Hal->serial = settingsStrings[lora_usb_serial_num];
+ }
+ ch341Hal->vid = settingsMap[lora_usb_vid];
+ ch341Hal->pid = settingsMap[lora_usb_pid];
+ ch341Hal->init();
+ if (!ch341Hal->isInit()) {
+ std::cout << "Could not initialize CH341 device!" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ char serial[9] = {0};
+ ch341Hal->getSerialString(serial, 8);
+ std::cout << "Serial " << serial << std::endl;
+ if (strlen(serial) == 8 && settingsStrings[mac_address].length() < 12) {
+ uint8_t hash[32] = {0};
+ memcpy(hash, serial, 8);
+ crypto->hash(hash, 8);
+ dmac[0] = (hash[0] << 4) | 2;
+ dmac[1] = hash[1];
+ dmac[2] = hash[2];
+ dmac[3] = hash[3];
+ dmac[4] = hash[4];
+ dmac[5] = hash[5];
+ }
+ }
+
getMacAddr(dmac);
if (dmac[0] == 0 && dmac[1] == 0 && dmac[2] == 0 && dmac[3] == 0 && dmac[4] == 0 && dmac[5] == 0) {
std::cout << "*** Blank MAC Address not allowed!" << std::endl;
@@ -225,47 +256,11 @@ void portduinoSetup()
// Need to bind all the configured GPIO pins so they're not simulated
// TODO: Can we do this in the for loop above?
// TODO: If one of these fails, we should log and terminate
- if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) {
- if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) {
- settingsMap[cs] = RADIOLIB_NC;
- }
- }
- if (settingsMap.count(irq) > 0 && settingsMap[irq] != RADIOLIB_NC) {
- if (initGPIOPin(settingsMap[irq], gpioChipName) != ERRNO_OK) {
- settingsMap[irq] = RADIOLIB_NC;
- }
- }
- if (settingsMap.count(busy) > 0 && settingsMap[busy] != RADIOLIB_NC) {
- if (initGPIOPin(settingsMap[busy], gpioChipName) != ERRNO_OK) {
- settingsMap[busy] = RADIOLIB_NC;
- }
- }
- if (settingsMap.count(reset) > 0 && settingsMap[reset] != RADIOLIB_NC) {
- if (initGPIOPin(settingsMap[reset], gpioChipName) != ERRNO_OK) {
- settingsMap[reset] = RADIOLIB_NC;
- }
- }
- if (settingsMap.count(sx126x_ant_sw) > 0 && settingsMap[sx126x_ant_sw] != RADIOLIB_NC) {
- if (initGPIOPin(settingsMap[sx126x_ant_sw], gpioChipName) != ERRNO_OK) {
- settingsMap[sx126x_ant_sw] = RADIOLIB_NC;
- }
- }
if (settingsMap.count(user) > 0 && settingsMap[user] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[user], gpioChipName) != ERRNO_OK) {
settingsMap[user] = RADIOLIB_NC;
}
}
- if (settingsMap.count(rxen) > 0 && settingsMap[rxen] != RADIOLIB_NC) {
- if (initGPIOPin(settingsMap[rxen], gpioChipName) != ERRNO_OK) {
- settingsMap[rxen] = RADIOLIB_NC;
- }
- }
- if (settingsMap.count(txen) > 0 && settingsMap[txen] != RADIOLIB_NC) {
- if (initGPIOPin(settingsMap[txen], gpioChipName) != ERRNO_OK) {
- settingsMap[txen] = RADIOLIB_NC;
- }
- }
-
if (settingsMap[displayPanel] != no_screen) {
if (settingsMap[displayCS] > 0)
initGPIOPin(settingsMap[displayCS], gpioChipName);
@@ -283,7 +278,43 @@ void portduinoSetup()
initGPIOPin(settingsMap[touchscreenIRQ], gpioChipName);
}
- if (settingsStrings[spidev] != "") {
+ // Only initialize the radio pins when dealing with real, kernel controlled SPI hardware
+ if (settingsStrings[spidev] != "" && settingsStrings[spidev] != "ch341") {
+ if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) {
+ if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) {
+ settingsMap[cs] = RADIOLIB_NC;
+ }
+ }
+ if (settingsMap.count(irq) > 0 && settingsMap[irq] != RADIOLIB_NC) {
+ if (initGPIOPin(settingsMap[irq], gpioChipName) != ERRNO_OK) {
+ settingsMap[irq] = RADIOLIB_NC;
+ }
+ }
+ if (settingsMap.count(busy) > 0 && settingsMap[busy] != RADIOLIB_NC) {
+ if (initGPIOPin(settingsMap[busy], gpioChipName) != ERRNO_OK) {
+ settingsMap[busy] = RADIOLIB_NC;
+ }
+ }
+ if (settingsMap.count(reset) > 0 && settingsMap[reset] != RADIOLIB_NC) {
+ if (initGPIOPin(settingsMap[reset], gpioChipName) != ERRNO_OK) {
+ settingsMap[reset] = RADIOLIB_NC;
+ }
+ }
+ if (settingsMap.count(sx126x_ant_sw) > 0 && settingsMap[sx126x_ant_sw] != RADIOLIB_NC) {
+ if (initGPIOPin(settingsMap[sx126x_ant_sw], gpioChipName) != ERRNO_OK) {
+ settingsMap[sx126x_ant_sw] = RADIOLIB_NC;
+ }
+ }
+ if (settingsMap.count(rxen) > 0 && settingsMap[rxen] != RADIOLIB_NC) {
+ if (initGPIOPin(settingsMap[rxen], gpioChipName) != ERRNO_OK) {
+ settingsMap[rxen] = RADIOLIB_NC;
+ }
+ }
+ if (settingsMap.count(txen) > 0 && settingsMap[txen] != RADIOLIB_NC) {
+ if (initGPIOPin(settingsMap[txen], gpioChipName) != ERRNO_OK) {
+ settingsMap[txen] = RADIOLIB_NC;
+ }
+ }
SPI.begin(settingsStrings[spidev].c_str());
}
if (settingsStrings[traceFilename] != "") {
@@ -378,17 +409,24 @@ bool loadConfig(const char *configPath)
settingsMap[rxen] = yamlConfig["Lora"]["RXen"].as(RADIOLIB_NC);
settingsMap[sx126x_ant_sw] = yamlConfig["Lora"]["SX126X_ANT_SW"].as(RADIOLIB_NC);
settingsMap[gpiochip] = yamlConfig["Lora"]["gpiochip"].as(0);
- settingsMap[ch341Quirk] = yamlConfig["Lora"]["ch341_quirk"].as(false);
settingsMap[spiSpeed] = yamlConfig["Lora"]["spiSpeed"].as(2000000);
+ settingsStrings[lora_usb_serial_num] = yamlConfig["Lora"]["USB_Serialnum"].as("");
+ settingsMap[lora_usb_pid] = yamlConfig["Lora"]["USB_PID"].as(0x5512);
+ settingsMap[lora_usb_vid] = yamlConfig["Lora"]["USB_VID"].as(0x1A86);
- settingsStrings[spidev] = "/dev/" + yamlConfig["Lora"]["spidev"].as("spidev0.0");
- if (settingsStrings[spidev].length() == 14) {
- int x = settingsStrings[spidev].at(11) - '0';
- int y = settingsStrings[spidev].at(13) - '0';
- if (x >= 0 && x < 10 && y >= 0 && y < 10) {
- settingsMap[spidev] = x + y << 4;
- settingsMap[displayspidev] = settingsMap[spidev];
- settingsMap[touchscreenspidev] = settingsMap[spidev];
+ settingsStrings[spidev] = yamlConfig["Lora"]["spidev"].as("spidev0.0");
+ if (settingsStrings[spidev] != "ch341") {
+ settingsStrings[spidev] = "/dev/" + settingsStrings[spidev];
+ if (settingsStrings[spidev].length() == 14) {
+ int x = settingsStrings[spidev].at(11) - '0';
+ int y = settingsStrings[spidev].at(13) - '0';
+ // Pretty sure this is always true
+ if (x >= 0 && x < 10 && y >= 0 && y < 10) {
+ // I believe this bit of weirdness is specifically for the new GUI
+ settingsMap[spidev] = x + y << 4;
+ settingsMap[displayspidev] = settingsMap[spidev];
+ settingsMap[touchscreenspidev] = settingsMap[spidev];
+ }
}
}
}
@@ -418,6 +456,8 @@ bool loadConfig(const char *configPath)
settingsMap[displayPanel] = ili9341;
else if (yamlConfig["Display"]["Panel"].as("") == "ILI9342")
settingsMap[displayPanel] = ili9342;
+ else if (yamlConfig["Display"]["Panel"].as("") == "ILI9486")
+ settingsMap[displayPanel] = ili9486;
else if (yamlConfig["Display"]["Panel"].as("") == "ILI9488")
settingsMap[displayPanel] = ili9488;
else if (yamlConfig["Display"]["Panel"].as("") == "HX8357D")
@@ -478,6 +518,7 @@ bool loadConfig(const char *configPath)
}
if (yamlConfig["Input"]) {
settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as("");
+ settingsStrings[pointerDevice] = (yamlConfig["Input"]["PointerDevice"]).as("");
}
if (yamlConfig["Webserver"]) {
@@ -533,4 +574,4 @@ bool MAC_from_string(std::string mac_str, uint8_t *dmac)
} else {
return false;
}
-}
\ No newline at end of file
+}
diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h
index 01541eeed..5bc07df6a 100644
--- a/src/platform/portduino/PortduinoGlue.h
+++ b/src/platform/portduino/PortduinoGlue.h
@@ -2,6 +2,8 @@
#include
#include