diff --git a/.github/ISSUE_TEMPLATE/Bug Report.yml b/.github/ISSUE_TEMPLATE/Bug Report.yml
index bc77e8c1b..cdf482344 100644
--- a/.github/ISSUE_TEMPLATE/Bug Report.yml
+++ b/.github/ISSUE_TEMPLATE/Bug Report.yml
@@ -75,11 +75,11 @@ body:
- type: checkboxes
id: mui
attributes:
- label: Is this bug report about any UI component firmware like InkHUD or Meshtatic UI (MUI)?
+ label: Is this bug report about any UI (https://meshtastic.org/docs/configuration/device-uis/) component firmware?
options:
- - label: Meshtastic UI aka MUI colorTFT
- - label: InkHUD ePaper
- - label: OLED slide UI on any display
+ - label: Meshtastic UI aka MUI
+ - label: InkHUD
+ - label: BaseUI
- type: input
id: version
diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml
index 2fabf0591..1e22d74d1 100644
--- a/.github/workflows/test_native.yml
+++ b/.github/workflows/test_native.yml
@@ -86,7 +86,13 @@ jobs:
run: sed -i 's/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
- name: PlatformIO Tests
- run: platformio test -e coverage -v --junit-output-path testreport.xml
+ run: |
+ set -o pipefail
+ # Filter out SKIPPED summary rows for hardware variants that can't run on the
+ # native host. They flood the log and make it harder to spot real failures.
+ # The JUnit XML is written directly to testreport.xml before the pipe, so
+ # the test artifact is unaffected.
+ platformio test -e coverage -v --junit-output-path testreport.xml 2>&1 | grep -v "[[:space:]]SKIPPED$"
- name: Save test results
if: always() # run this step even if previous step failed
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index d0cbaa8bc..f90f4f4ac 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,18 +8,18 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- - checkov@3.2.517
- - renovate@43.110.9
- - prettier@3.8.1
- - trufflehog@3.94.3
+ - checkov@3.2.524
+ - renovate@43.141.0
+ - prettier@3.8.3
+ - trufflehog@3.95.2
- yamllint@1.38.0
- bandit@1.9.4
- - trivy@0.69.3
+ - trivy@0.70.0
- taplo@0.10.0
- - ruff@0.15.9
+ - ruff@0.15.11
- isort@8.0.1
- markdownlint@0.48.0
- - oxipng@10.1.0
+ - oxipng@10.1.1
- svgo@4.0.1
- actionlint@1.7.12
- flake8@7.3.0
diff --git a/bin/show-unmerged-prs.sh b/bin/show-unmerged-prs.sh
new file mode 100755
index 000000000..2a76f63d6
--- /dev/null
+++ b/bin/show-unmerged-prs.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+
+# Script to show commits in develop that are not in master
+# with their associated PR info and commit hashes
+#
+# Usage:
+# ./show-unmerged-prs.sh # Show all unmerged commits
+# ./show-unmerged-prs.sh --bugfix # Show only bugfix-labeled PRs
+
+set -e
+
+REPO="firmware"
+OWNER="meshtastic"
+BASE_BRANCH="master"
+HEAD_BRANCH="develop"
+LIMIT=100
+FILTER_LABEL=""
+
+# Parse arguments
+for arg in "$@"; do
+ case $arg in
+ --bugfix)
+ FILTER_LABEL="bugfix"
+ shift
+ ;;
+ --feature)
+ FILTER_LABEL="feature"
+ shift
+ ;;
+ --help)
+ echo "Usage: $0 [OPTIONS]"
+ echo "Options:"
+ echo " --bugfix Show only PRs labeled with 'bugfix'"
+ echo " --feature Show only PRs labeled with 'feature'"
+ echo " --help Show this help message"
+ exit 0
+ ;;
+ esac
+done
+
+if [ -n "$FILTER_LABEL" ]; then
+ echo "Fetching commits in $HEAD_BRANCH that are not in $BASE_BRANCH (filtered by label: $FILTER_LABEL)..."
+else
+ echo "Fetching commits in $HEAD_BRANCH that are not in $BASE_BRANCH..."
+fi
+echo ""
+
+# Check if gh CLI is available
+if ! command -v gh &> /dev/null; then
+ echo "ERROR: GitHub CLI (gh) not found. Please install it first."
+ echo "Visit: https://cli.github.com/"
+ exit 1
+fi
+
+# Get commits in develop that are not in master
+# For each commit, try to find associated PR
+git fetch origin develop master 2>/dev/null || true
+
+# Use git to get the list of commits
+commits=$(git log --pretty=format:"%H|%s" origin/master..origin/develop | head -n $LIMIT)
+
+count=0
+displayed=0
+echo "Commits in $HEAD_BRANCH not in $BASE_BRANCH:"
+echo "=============================================="
+echo ""
+
+while IFS='|' read -r hash subject; do
+ ((count++))
+
+ # Try to find the PR for this commit
+ # Extract PR number, title, description, and labels
+ pr_response=$(gh api -X GET "/repos/$OWNER/$REPO/commits/$hash/pulls" \
+ -H "Accept: application/vnd.github.v3+json" 2>/dev/null | \
+ jq -r '.[0] | "\(.number)|\(.title)|\(.body // "No description")|\(.labels | map(.name) | join(","))"' 2>/dev/null || echo "||||")
+
+ if [ -z "$pr_response" ] || [ "$pr_response" = "||||" ]; then
+ # If no PR found, skip if filter is active, otherwise show the commit
+ if [ -z "$FILTER_LABEL" ]; then
+ ((displayed++))
+ echo "[$displayed] Commit: $hash"
+ echo " Subject: $subject"
+ echo " PR: Not found in GitHub"
+ echo ""
+ fi
+ else
+ IFS='|' read -r pr_num pr_title pr_desc pr_labels <<< "$pr_response"
+
+ # Check if filter matches
+ if [ -n "$FILTER_LABEL" ]; then
+ # Only show if the label is in the labels list
+ if ! echo "$pr_labels" | grep -q "$FILTER_LABEL"; then
+ continue
+ fi
+ fi
+
+ ((displayed++))
+ echo "[$displayed] PR #$pr_num - $pr_title"
+ echo " Commit: $hash"
+ if [ -n "$pr_desc" ] && [ "$pr_desc" != "No description" ]; then
+ # Truncate description to 200 chars
+ desc_short="${pr_desc:0:200}"
+ [ ${#pr_desc} -gt 200 ] && desc_short+="..."
+ echo " Description: $desc_short"
+ fi
+ if [ -n "$pr_labels" ] && [ "$pr_labels" != "" ]; then
+ echo " Labels: $pr_labels"
+ fi
+ echo ""
+ fi
+done <<< "$commits"
+
+echo ""
+if [ -n "$FILTER_LABEL" ]; then
+ echo "Done. Showing $displayed PRs with label '$FILTER_LABEL' from $displayed commits checked."
+else
+ echo "Done. Showing $displayed commits from $HEAD_BRANCH not in $BASE_BRANCH."
+fi
diff --git a/platformio.ini b/platformio.ini
index 48f534d5d..7f39cc5f0 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -29,6 +29,7 @@ build_flags = -Wno-missing-field-initializers
-DUSE_THREAD_NAMES
-DTINYGPS_OPTION_NO_CUSTOM_FIELDS
-DPB_ENABLE_MALLOC=1
+ -DPB_VALIDATE_UTF8=1
-DRADIOLIB_EXCLUDE_CC1101=1
-DRADIOLIB_EXCLUDE_NRF24=1
-DRADIOLIB_EXCLUDE_RF69=1
@@ -66,7 +67,7 @@ monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
- https://github.com/meshtastic/esp8266-oled-ssd1306/archive/21e484f409cde18d44012caef84c244eb5ca28f3.zip
+ https://github.com/meshtastic/esp8266-oled-ssd1306/archive/6bfd1f135e1ebe37afd6050bb4b9964cea3fcfda.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
diff --git a/protobufs b/protobufs
index 4d5b500df..249a80855 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 4d5b500df5af68a4f57d3e19705cc3bb1136358c
+Subproject commit 249a80855a2adb76fb0904dac8bf6285d45f330f
diff --git a/src/Power.cpp b/src/Power.cpp
index 2e3217a1c..b40b41623 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -94,16 +94,31 @@ static const adc_atten_t atten = ADC_ATTENUATION;
#endif
#endif // BATTERY_PIN && ARCH_ESP32
+#ifdef EXT_PWR_DETECT
+#ifndef EXT_PWR_DETECT_MODE
+#define EXT_PWR_DETECT_MODE INPUT
+// If using internal pull resistors, we can infer EXT_PWR_DETECT_VALUE
+#elif EXT_PWR_DETECT_MODE == INPUT_PULLUP
+#define EXT_PWR_DETECT_VALUE LOW
+#elif EXT_PWR_DETECT_MODE == INPUT_PULLDOWN
+#define EXT_PWR_DETECT_VALUE HIGH
+#endif
+#ifndef EXT_PWR_DETECT_VALUE
+#define EXT_PWR_DETECT_VALUE HIGH
+#endif
+#endif
+
#ifdef EXT_CHRG_DETECT
#ifndef EXT_CHRG_DETECT_MODE
-static const uint8_t ext_chrg_detect_mode = INPUT;
-#else
-static const uint8_t ext_chrg_detect_mode = EXT_CHRG_DETECT_MODE;
+#define EXT_CHRG_DETECT_MODE INPUT
+// If using internal pull resistors, we can infer EXT_CHRG_DETECT_VALUE
+#elif EXT_CHRG_DETECT_MODE == INPUT_PULLUP
+#define EXT_CHRG_DETECT_VALUE LOW
+#elif EXT_CHRG_DETECT_MODE == INPUT_PULLDOWN
+#define EXT_CHRG_DETECT_VALUE HIGH
#endif
#ifndef EXT_CHRG_DETECT_VALUE
-static const uint8_t ext_chrg_detect_value = HIGH;
-#else
-static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
+#define EXT_CHRG_DETECT_VALUE HIGH
#endif
#endif
@@ -469,28 +484,14 @@ class AnalogBatteryLevel : public HasBatteryLevel
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
#endif
- /// If we see a battery voltage higher than physics allows - assume charger is
- /// pumping in power On some boards we don't have the power management chip
- /// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power
- /// source
+ // Detect if an external power source is connected if we don’t have a PMIC;
+ // Firstly prefer EXT_PWR_DETECT GPIO if available,
+ // secondly try an nRF52-specific routine on some variants,
+ // lastly provide a fallback to indicate external power when fully charged.
virtual bool isVbusIn() override
{
#ifdef EXT_PWR_DETECT
-#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
- // if external powered that pin will be pulled down
- if (digitalRead(EXT_PWR_DETECT) == LOW) {
- return true;
- }
- // if it's not LOW - check the battery
-#else
- // if external powered that pin will be pulled up
- if (digitalRead(EXT_PWR_DETECT) == HIGH) {
- return true;
- }
- // if it's not HIGH - check the battery
-#endif
- // If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it.
- return false;
+ return digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE;
// technically speaking this should work for all(?) NRF52 boards
// but needs testing across multiple devices. NRF52 USB would not even work if
@@ -511,9 +512,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
}
#endif
#if defined(ELECROW_ThinkNode_M6)
- return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value || isVbusIn();
+ return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE || isVbusIn();
#elif EXT_CHRG_DETECT
- return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
+ return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE;
#elif defined(BATTERY_CHARGING_INV)
return !digitalRead(BATTERY_CHARGING_INV);
#else
@@ -646,14 +647,10 @@ Power::Power() : OSThread("Power")
bool Power::analogInit()
{
#ifdef EXT_PWR_DETECT
-#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
- pinMode(EXT_PWR_DETECT, INPUT_PULLUP);
-#else
- pinMode(EXT_PWR_DETECT, INPUT);
-#endif
+ pinMode(EXT_PWR_DETECT, EXT_PWR_DETECT_MODE);
#endif
#ifdef EXT_CHRG_DETECT
- pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
+ pinMode(EXT_CHRG_DETECT, EXT_CHRG_DETECT_MODE);
#endif
#ifdef BATTERY_PIN
@@ -746,37 +743,17 @@ bool Power::setup()
found = true;
#endif
}
-#ifdef EXT_PWR_DETECT
- attachInterrupt(
- EXT_PWR_DETECT,
- []() {
- power->setIntervalFromNow(0);
- runASAP = true;
- },
- CHANGE);
-#endif
-#ifdef BATTERY_CHARGING_INV
- attachInterrupt(
- BATTERY_CHARGING_INV,
- []() {
- power->setIntervalFromNow(0);
- runASAP = true;
- },
- CHANGE);
-#endif
-#ifdef EXT_CHRG_DETECT
- attachInterrupt(
- EXT_CHRG_DETECT,
- []() {
- power->setIntervalFromNow(0);
- runASAP = true;
- BaseType_t higherWake = 0;
- },
- CHANGE);
-#endif
+ attachPowerInterrupts();
enabled = found;
low_voltage_counter = 0;
+#ifdef ARCH_ESP32
+ // Register callbacks for before and after lightsleep
+ // Used to detach and reattach interrupts
+ lsObserver.observe(¬ifyLightSleep);
+ lsEndObserver.observe(¬ifyLightSleepEnd);
+#endif
+
return found;
}
@@ -1023,6 +1000,14 @@ int32_t Power::runOnce()
powerFSM.trigger(EVENT_POWER_CONNECTED);
}
+#ifdef PMU_POWER_BUTTON_IS_CANCEL
+ // cancel action also turns the screen on and off.
+ if (PMU->isPekeyShortPressIrq()) {
+ LOG_INFO("Input: Corona Button Click");
+ InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_CANCEL, .kbchar = 0, .touchX = 0, .touchY = 0};
+ inputBroker->injectInputEvent(&event);
+ }
+#endif
/*
Other things we could check if we cared...
@@ -1039,13 +1024,6 @@ int32_t Power::runOnce()
LOG_DEBUG("Battery removed");
}
*/
-#ifndef T_WATCH_S3 // FIXME - why is this triggering on the T-Watch S3?
- if (PMU->isPekeyLongPressIrq()) {
- LOG_DEBUG("PEK long button press");
- if (screen)
- screen->setOn(false);
- }
-#endif
PMU->clearIrqStatus();
}
@@ -1055,6 +1033,97 @@ int32_t Power::runOnce()
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
}
+#ifdef ARCH_ESP32
+
+// Detach our class' interrupts before lightsleep
+// Allows sleep.cpp to configure its own interrupts, which wake the device on user-button press
+int Power::beforeLightSleep(void *unused)
+{
+ LOG_WARN("Detaching power interrupts for sleep");
+ detachPowerInterrupts();
+ return 0; // Indicates success
+}
+
+// Reconfigure our interrupts
+// Our class' interrupts were disconnected during sleep, to allow the user button to wake the device from sleep
+int Power::afterLightSleep(esp_sleep_wakeup_cause_t cause)
+{
+ attachPowerInterrupts();
+ return 0; // Indicates success
+}
+
+#endif
+
+/*
+ * Attach (or re-attach) hardware interrupts for power management
+ * Public method. Used outside class when waking from MCU sleep
+ */
+void Power::attachPowerInterrupts()
+{
+#ifdef EXT_PWR_DETECT
+ attachInterrupt(
+ EXT_PWR_DETECT,
+ []() {
+ power->setIntervalFromNow(0);
+ runASAP = true;
+ },
+ CHANGE);
+#endif
+#ifdef BATTERY_CHARGING_INV
+ attachInterrupt(
+ BATTERY_CHARGING_INV,
+ []() {
+ power->setIntervalFromNow(0);
+ runASAP = true;
+ },
+ CHANGE);
+#endif
+#ifdef EXT_CHRG_DETECT
+ attachInterrupt(
+ EXT_CHRG_DETECT,
+ []() {
+ power->setIntervalFromNow(0);
+ runASAP = true;
+ BaseType_t higherWake = 0;
+ },
+ CHANGE);
+#endif
+#ifdef PMU_IRQ
+ if (PMU) {
+ attachInterrupt(
+ PMU_IRQ,
+ []() {
+ pmu_irq = true;
+ power->setIntervalFromNow(0);
+ runASAP = true;
+ },
+ FALLING);
+ }
+#endif
+}
+
+/*
+ * Detach the "normal" button interrupts.
+ * Public method. Used before attaching a "wake-on-button" interrupt for MCU sleep
+ */
+void Power::detachPowerInterrupts()
+{
+#ifdef EXT_PWR_DETECT
+ detachInterrupt(EXT_PWR_DETECT);
+#endif
+#ifdef BATTERY_CHARGING_INV
+ detachInterrupt(BATTERY_CHARGING_INV);
+#endif
+#ifdef EXT_CHRG_DETECT
+ detachInterrupt(EXT_CHRG_DETECT);
+#endif
+#ifdef PMU_IRQ
+ if (PMU) {
+ detachInterrupt(PMU_IRQ);
+ }
+#endif
+}
+
/**
* Init the power manager chip
*
@@ -1368,21 +1437,16 @@ bool Power::axpChipInit()
uint64_t pmuIrqMask = 0;
if (PMU->getChipModel() == XPOWERS_AXP192) {
- pmuIrqMask = XPOWERS_AXP192_VBUS_INSERT_IRQ | XPOWERS_AXP192_BAT_INSERT_IRQ | XPOWERS_AXP192_PKEY_SHORT_IRQ;
+ pmuIrqMask = XPOWERS_AXP192_VBUS_INSERT_IRQ | XPOWERS_AXP192_VBUS_REMOVE_IRQ | XPOWERS_AXP192_PKEY_SHORT_IRQ;
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
- pmuIrqMask = XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_PKEY_SHORT_IRQ;
+ pmuIrqMask = XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_VBUS_REMOVE_IRQ | XPOWERS_AXP2101_PKEY_SHORT_IRQ;
}
pinMode(PMU_IRQ, INPUT);
- attachInterrupt(
- PMU_IRQ, [] { pmu_irq = true; }, FALLING);
- // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ
- // because it occurs repeatedly while there is no battery also it could cause
- // inadvertent waking from light sleep just because the battery filled we
- // don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while
- // no battery installed we don't look at AXPXXX_VBUS_REMOVED_IRQ because we
- // don't have anything hooked to vbus
+ // We wake on IRQ, so only enable the IRQs that we care about.
+ // we want USB plug and unplug to update the screen and LED status,
+ // and short press on the power button to trigger the "cancel" action in the UI (which also turns the screen on and off).
PMU->enableIRQ(pmuIrqMask);
PMU->clearIrqStatus();
@@ -1848,7 +1912,7 @@ class SerialBatteryLevel : public HasBatteryLevel
{
#if defined(EXT_CHRG_DETECT)
- return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
+ return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE;
#endif
return false;
@@ -1857,7 +1921,7 @@ class SerialBatteryLevel : public HasBatteryLevel
virtual bool isCharging() override
{
#ifdef EXT_CHRG_DETECT
- return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
+ return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE;
#endif
// by default, we check the battery voltage only
@@ -1879,10 +1943,10 @@ SerialBatteryLevel serialBatteryLevel;
bool Power::serialBatteryInit()
{
#ifdef EXT_PWR_DETECT
- pinMode(EXT_PWR_DETECT, INPUT);
+ pinMode(EXT_PWR_DETECT, EXT_PWR_DETECT_MODE);
#endif
#ifdef EXT_CHRG_DETECT
- pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
+ pinMode(EXT_CHRG_DETECT, EXT_CHRG_DETECT_MODE);
#endif
bool result = serialBatteryLevel.runOnce();
diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp
index b11f37cf0..a1610109c 100644
--- a/src/PowerFSM.cpp
+++ b/src/PowerFSM.cpp
@@ -58,6 +58,35 @@ static bool isPowered()
return !isPowerSavingMode && powerStatus && (!powerStatus->getHasBattery() || powerStatus->getHasUSB());
}
+#if defined(T5_S3_EPAPER_PRO)
+static void t5BacklightOffForSleep()
+{
+ t5BacklightSetForcedBySleep(true);
+}
+
+static void t5BacklightWakeFromSleep()
+{
+ t5BacklightSetForcedBySleep(false);
+}
+
+static void t5BacklightOffForTimeout()
+{
+ t5BacklightSetForcedByTimeout(true);
+ t5TouchSetForcedByTimeout(true);
+}
+
+static void t5BacklightOnFromUserInput()
+{
+ t5BacklightHandleUserInput();
+ t5TouchHandleUserInput();
+}
+#else
+static void t5BacklightOffForSleep() {}
+static void t5BacklightWakeFromSleep() {}
+static void t5BacklightOffForTimeout() {}
+static void t5BacklightOnFromUserInput() {}
+#endif
+
static void sdsEnter()
{
LOG_POWERFSM("State: SDS");
@@ -87,6 +116,7 @@ static void lsEnter()
LOG_POWERFSM("lsEnter begin, ls_secs=%u", config.power.ls_secs);
if (screen)
screen->setOn(false);
+ t5BacklightOffForSleep();
secsSlept = 0; // How long have we been sleeping this time
// LOG_INFO("lsEnter end");
@@ -159,6 +189,8 @@ static void lsIdle()
static void lsExit()
{
LOG_POWERFSM("State: lsExit");
+ // Lift the light-sleep force-off gate when leaving LS.
+ t5BacklightWakeFromSleep();
}
static void nbEnter()
@@ -180,6 +212,8 @@ static void darkEnter()
setBluetoothEnable(true);
if (screen)
screen->setOn(false);
+ // Screen timeout enters DARK; ensure backlight also turns off.
+ t5BacklightOffForTimeout();
}
static void serialEnter()
@@ -289,12 +323,13 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateNB, &stateNB, EVENT_PACKET_FOR_PHONE, NULL, "Received packet, resetting win wake");
// Handle press events - note: we ignore button presses when in API mode
- powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
- powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
- powerFSM.add_transition(&stateDARK, isPowered() ? &statePOWER : &stateON, EVENT_PRESS, NULL, "Press");
- powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_PRESS, NULL, "Press");
- powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, NULL, "Press"); // reenter On to restart our timers
- powerFSM.add_transition(&stateSERIAL, &stateSERIAL, EVENT_PRESS, NULL,
+ powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, t5BacklightOnFromUserInput, "Press");
+ powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, t5BacklightOnFromUserInput, "Press");
+ powerFSM.add_transition(&stateDARK, isPowered() ? &statePOWER : &stateON, EVENT_PRESS, t5BacklightOnFromUserInput, "Press");
+ powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_PRESS, t5BacklightOnFromUserInput, "Press");
+ powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, t5BacklightOnFromUserInput,
+ "Press"); // reenter On to restart our timers
+ powerFSM.add_transition(&stateSERIAL, &stateSERIAL, EVENT_PRESS, t5BacklightOnFromUserInput,
"Press"); // Allow button to work while in serial API
// Handle critically low power battery by forcing deep sleep
@@ -314,11 +349,13 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateSERIAL, &stateSHUTDOWN, EVENT_SHUTDOWN, NULL, "Shutdown");
// Inputbroker
- powerFSM.add_transition(&stateLS, &stateON, EVENT_INPUT, NULL, "Input Device");
- powerFSM.add_transition(&stateNB, &stateON, EVENT_INPUT, NULL, "Input Device");
- powerFSM.add_transition(&stateDARK, &stateON, EVENT_INPUT, NULL, "Input Device");
- powerFSM.add_transition(&stateON, &stateON, EVENT_INPUT, NULL, "Input Device"); // restarts the sleep timer
- powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_INPUT, NULL, "Input Device"); // restarts the sleep timer
+ powerFSM.add_transition(&stateLS, &stateON, EVENT_INPUT, t5BacklightOnFromUserInput, "Input Device");
+ powerFSM.add_transition(&stateNB, &stateON, EVENT_INPUT, t5BacklightOnFromUserInput, "Input Device");
+ powerFSM.add_transition(&stateDARK, &stateON, EVENT_INPUT, t5BacklightOnFromUserInput, "Input Device");
+ powerFSM.add_transition(&stateON, &stateON, EVENT_INPUT, t5BacklightOnFromUserInput,
+ "Input Device"); // restarts the sleep timer
+ powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_INPUT, t5BacklightOnFromUserInput,
+ "Input Device"); // restarts the sleep timer
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
diff --git a/src/airtime.h b/src/airtime.h
index 3ed7b6d7c..8e3e6c557 100644
--- a/src/airtime.h
+++ b/src/airtime.h
@@ -19,8 +19,8 @@
TX_LOG + RX_LOG = Total air time for a particular meshtastic channel.
- TX_LOG + RX_LOG = Total air time for a particular meshtastic channel, including
- other lora radios.
+ TX_LOG + RX_ALL_LOG = Total air time for a particular meshtastic channel, including
+ other lora radios.
RX_ALL_LOG - RX_LOG = Other lora radios on our frequency channel.
*/
diff --git a/src/configuration.h b/src/configuration.h
index 84dabee4e..efd9ddcf7 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -499,6 +499,7 @@ along with this program. If not, see .
#define MESHTASTIC_EXCLUDE_PKI 1
#define MESHTASTIC_EXCLUDE_POWER_FSM 1
#define MESHTASTIC_EXCLUDE_TZ 1
+#define MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH 1
#endif
// Turn off all optional modules
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index e298663a0..e3471c32a 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -415,30 +415,45 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#if !defined(M5STACK_UNITC6L)
case INA_ADDR: // Same as SHT2X
case INA_ADDR_ALTERNATE:
- case INA_ADDR_WAVESHARE_UPS:
- registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
- LOG_DEBUG("Register MFG_UID: 0x%x", registerValue);
- if (registerValue == 0x5449) {
- registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 2);
- LOG_DEBUG("Register DIE_UID: 0x%x", registerValue);
+ case INA_ADDR_WAVESHARE_UPS: {
+ uint16_t mfg = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
- if (registerValue == 0x2260) {
+ LOG_DEBUG("Register MFG_UID: 0x%x", mfg);
+
+ // Only read DIE_UID for vendors we recognize as INA-compatible to avoid
+ // an extra I2C transaction + delay on other devices sharing this address.
+ if (mfg == 0x5449 || mfg == 0x190F) {
+ uint16_t die = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 2);
+ LOG_DEBUG("Register DIE_UID: 0x%x", die);
+
+ // TI INA226 or fully compatible clones (e.g. TPA626)
+ if (mfg == 0x5449 && die == 0x2260) {
logFoundDevice("INA226", (uint8_t)addr.address);
type = INA226;
- } else {
+ }
+ // Silergy SQ52201 (INA226-compatible with different IDs)
+ else if (mfg == 0x190F && die == 0x0000) {
+ logFoundDevice("INA226 (SQ52201)", (uint8_t)addr.address);
+ type = INA226;
+ }
+ // TI INA260
+ else if (mfg == 0x5449) {
logFoundDevice("INA260", (uint8_t)addr.address);
type = INA260;
}
+ }
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
- } else if (detectSHT21SerialNumber(i2cBus, (uint8_t)addr.address)) {
+ if (type == NONE && detectSHT21SerialNumber(i2cBus, (uint8_t)addr.address)) {
logFoundDevice("SHTXX (SHT2X)", (uint8_t)addr.address);
type = SHTXX;
+ }
#endif
- } else { // Assume INA219 if none of the above ones are found
+ else { // Assume INA219 if none of the above ones are found
logFoundDevice("INA219", (uint8_t)addr.address);
type = INA219;
}
break;
+ }
case INA3221_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
LOG_DEBUG("Register MFG_UID FE: 0x%x", registerValue);
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 3790510eb..9be88f015 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -39,6 +39,7 @@ along with this program. If not, see .
#include "draw/NodeListRenderer.h"
#include "draw/NotificationRenderer.h"
#include "draw/UIRenderer.h"
+#include "graphics/TFTColorRegions.h"
#include "modules/CannedMessageModule.h"
#if !MESHTASTIC_EXCLUDE_GPS
@@ -54,6 +55,7 @@ along with this program. If not, see .
#include "gps/RTC.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TFTPalette.h"
#include "graphics/emotes.h"
#include "graphics/images.h"
#include "input/TouchScreenImpl1.h"
@@ -69,12 +71,6 @@ along with this program. If not, see .
#include "target_specific.h"
extern MessageStore messageStore;
-#if USE_TFTDISPLAY
-extern uint16_t TFT_MESH;
-#else
-uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
-#endif
-
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
#include "mesh/wifi/WiFiAPClient.h"
#endif
@@ -109,6 +105,27 @@ namespace graphics
// A text message frame + debug frame + all the node infos
FrameCallback *normalFrames;
static uint32_t targetFramerate = IDLE_FRAMERATE;
+#if GRAPHICS_TFT_COLORING_ENABLED
+static inline void prepareFrameColorRegions()
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+ clearTFTColorRegions();
+ // Full-frame FrameMono inversion for themes that need it (e.g. light themes).
+ if (isThemeFullFrameInvert()) {
+ setAndRegisterTFTColorRole(TFTColorRole::FrameMono, getThemeBodyFg(), getThemeBodyBg(), 0, 0, screen->getWidth(),
+ screen->getHeight());
+ }
+#endif
+}
+#endif
+
+static inline void updateUiFrame(OLEDDisplayUi *ui)
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+ prepareFrameColorRegions();
+#endif
+ ui->update();
+}
// Global variables for alert banner - explicitly define with extern "C" linkage to prevent optimization
uint32_t logo_timeout = 5000; // 4 seconds for EACH logo
@@ -227,7 +244,7 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
ui->setTargetFPS(60);
- ui->update();
+ updateUiFrame(ui);
}
// Called to trigger a banner with custom message and duration
@@ -249,7 +266,7 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
ui->setTargetFPS(60);
- ui->update();
+ updateUiFrame(ui);
}
// Called to trigger a banner with custom message and duration
@@ -273,7 +290,7 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
ui->setTargetFPS(60);
- ui->update();
+ updateUiFrame(ui);
}
void Screen::showTextInput(const char *header, const char *initialText, uint32_t durationMs,
@@ -296,7 +313,7 @@ void Screen::showTextInput(const char *header, const char *initialText, uint32_t
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
ui->setTargetFPS(60);
- ui->update();
+ updateUiFrame(ui);
}
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -388,30 +405,6 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
{
graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
- int32_t rawRGB = uiconfig.screen_rgb_color;
-
- // Only validate the combined value once
- if (rawRGB > 0 && rawRGB <= 255255255) {
- LOG_INFO("Setting screen RGB color to user chosen: 0x%06X", rawRGB);
- // Extract each component as a normal int first
- int r = (rawRGB >> 16) & 0xFF;
- int g = (rawRGB >> 8) & 0xFF;
- int b = rawRGB & 0xFF;
- if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
- TFT_MESH = COLOR565(static_cast(r), static_cast(g), static_cast(b));
- }
-#ifdef TFT_MESH_OVERRIDE
- } else if (rawRGB == 0) {
- LOG_INFO("Setting screen RGB color to TFT_MESH_OVERRIDE: 0x%04X", TFT_MESH_OVERRIDE);
- // Default to TFT_MESH_OVERRIDE if available
- TFT_MESH = TFT_MESH_OVERRIDE;
-#endif
- } else {
- // Default best readable yellow color
- LOG_INFO("Setting screen RGB color to default: (255,255,128)");
- TFT_MESH = COLOR565(255, 255, 128);
- }
-
#if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64)
dispdev = new SH1106Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@@ -475,9 +468,13 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#endif
#if defined(USE_ST7789)
- static_cast(dispdev)->setRGB(TFT_MESH);
+ // Keep firmware and ST7789 driver region structs layout-compatible:
+ // we pass `graphics::colorRegions` through a type cast below.
+ static_assert(sizeof(graphics::TFTColorRegion) == sizeof(::TFTColorRegion),
+ "graphics::TFTColorRegion layout must match ST7789 TFTColorRegion");
+ static_cast(dispdev)->setRGB(TFTPalette::White, (::TFTColorRegion *)colorRegions);
#elif defined(USE_ST7796)
- static_cast(dispdev)->setRGB(TFT_MESH);
+ static_cast(dispdev)->setRGB(TFTPalette::White);
#endif
ui = new OLEDDisplayUi(dispdev);
@@ -664,16 +661,16 @@ void Screen::setup()
static_cast(dispdev)->setSubtype(7);
#endif
-#if defined(USE_ST7789) && defined(TFT_MESH)
- // Apply custom RGB color (e.g. Heltec T114/T190)
- static_cast(dispdev)->setRGB(TFT_MESH);
+#if defined(USE_ST7789)
+ static_assert(sizeof(graphics::TFTColorRegion) == sizeof(::TFTColorRegion),
+ "graphics::TFTColorRegion layout must match ST7789 TFTColorRegion");
+ static_cast(dispdev)->setRGB(TFTPalette::White, (::TFTColorRegion *)colorRegions);
#endif
#if defined(MUZI_BASE)
dispdev->delayPoweron = true;
#endif
-#if defined(USE_ST7796) && defined(TFT_MESH)
- // Custom text color, if defined in variant.h
- static_cast(dispdev)->setRGB(TFT_MESH);
+#if defined(USE_ST7796)
+ static_cast(dispdev)->setRGB(TFTPalette::White);
#endif
// Initialize display and UI system
@@ -719,7 +716,7 @@ void Screen::setup()
#endif
{
const char *region = myRegion ? myRegion->name : nullptr;
- graphics::UIRenderer::drawIconScreen(region, display, state, x, y);
+ graphics::UIRenderer::drawBootIconScreen(region, display, state, x, y);
}
};
ui->setFrames(alertFrames, 1);
@@ -759,9 +756,9 @@ void Screen::setup()
// Turn on display and trigger first draw
handleSetOn(true);
graphics::currentResolution = graphics::determineScreenResolution(dispdev->height(), dispdev->width());
- ui->update();
+ updateUiFrame(ui);
#ifndef USE_EINK
- ui->update(); // Some SSD1306 clones drop the first draw, so run twice
+ updateUiFrame(ui); // Some SSD1306 clones drop the first draw, so run twice
#endif
serialSinceMsec = millis();
@@ -834,7 +831,7 @@ void Screen::forceDisplay(bool forceUiUpdate)
do {
startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
delay(10);
- ui->update();
+ updateUiFrame(ui);
} while (ui->getUiState()->lastUpdate < startUpdate);
// Return to normal frame rate
@@ -905,9 +902,9 @@ int32_t Screen::runOnce()
static FrameCallback bootOEMFrames[] = {graphics::UIRenderer::drawOEMBootScreen};
static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]);
ui->setFrames(bootOEMFrames, bootOEMFrameCount);
- ui->update();
+ updateUiFrame(ui);
#ifndef USE_EINK
- ui->update();
+ updateUiFrame(ui);
#endif
showingOEMBootScreen = false;
}
@@ -998,7 +995,7 @@ int32_t Screen::runOnce()
// this must be before the frameState == FIXED check, because we always
// want to draw at least one FIXED frame before doing forceDisplay
- ui->update();
+ updateUiFrame(ui);
// Switch to a low framerate (to save CPU) when we are not in transition
// but we should only call setTargetFPS when framestate changes, because
@@ -1060,7 +1057,7 @@ void Screen::setSSLFrames()
// LOG_DEBUG("Show SSL frames");
static FrameCallback sslFrames[] = {NotificationRenderer::drawSSLScreen};
ui->setFrames(sslFrames, 1);
- ui->update();
+ updateUiFrame(ui);
}
}
@@ -1096,7 +1093,7 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
do {
startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
delay(1);
- ui->update();
+ updateUiFrame(ui);
} while (ui->getUiState()->lastUpdate < startUpdate);
#if defined(USE_EINK_PARALLELDISPLAY)
@@ -1471,9 +1468,15 @@ void Screen::blink()
dispdev->setBrightness(254);
while (count > 0) {
dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight());
+#if GRAPHICS_TFT_COLORING_ENABLED
+ prepareFrameColorRegions();
+#endif
dispdev->display();
delay(50);
dispdev->clear();
+#if GRAPHICS_TFT_COLORING_ENABLED
+ prepareFrameColorRegions();
+#endif
dispdev->display();
delay(50);
count = count - 1;
@@ -1607,6 +1610,9 @@ void Screen::setFastFramerate()
{
#if defined(M5STACK_UNITC6L)
dispdev->clear();
+#if GRAPHICS_TFT_COLORING_ENABLED
+ prepareFrameColorRegions();
+#endif
dispdev->display();
#endif
// We are about to start a transition so speed up fps
@@ -1818,7 +1824,7 @@ int Screen::handleInputEvent(const InputEvent *event)
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP
- ui->update();
+ updateUiFrame(ui);
return 0;
}
@@ -1833,7 +1839,7 @@ int Screen::handleInputEvent(const InputEvent *event)
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP
- ui->update();
+ updateUiFrame(ui);
menuHandler::handleMenuSwitch(dispdev);
return 0;
diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp
index ec50654ae..becd3e75d 100644
--- a/src/graphics/SharedUIDisplay.cpp
+++ b/src/graphics/SharedUIDisplay.cpp
@@ -1,16 +1,20 @@
#include "configuration.h"
#if HAS_SCREEN
#include "MeshService.h"
+#include "NodeDB.h"
#include "RTC.h"
#include "draw/NodeListRenderer.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TFTColorRegions.h"
+#include "graphics/TFTPalette.h"
#include "graphics/draw/UIRenderer.h"
#include "main.h"
#include "meshtastic/config.pb.h"
#include "modules/ExternalNotificationModule.h"
#include "power.h"
#include
+#include
#include
namespace graphics
@@ -65,6 +69,12 @@ uint32_t lastBlinkShared = 0;
bool isMailIconVisible = true;
uint32_t lastMailBlink = 0;
+static inline bool useClockHeaderAccentTheme(uint32_t themeId)
+{
+ return themeId == ThemeID::Pink || themeId == ThemeID::Creamsicle || themeId == ThemeID::MeshtasticGreen ||
+ themeId == ThemeID::ClassicRed || themeId == ThemeID::MonochromeWhite;
+}
+
// *********************************
// * Rounded Header when inverted *
// *********************************
@@ -85,7 +95,8 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
// *************************
// * Common Header Drawing *
// *************************
-void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, bool show_date)
+void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, bool show_date,
+ bool transparent_background, bool use_title_color_override, uint16_t title_color_override)
{
constexpr int HEADER_OFFSET_Y = 1;
y += HEADER_OFFSET_Y;
@@ -100,30 +111,93 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
const int screenW = display->getWidth();
const int screenH = display->getHeight();
+ const int headerHeight = highlightHeight + 2;
+ const uint16_t headerColorForRoles = getThemeHeaderBg();
+ // Color TFT headers use a fixed dark background + white glyphs.
+ // Keep legacy inverted bitmap behavior only for monochrome displays.
+ const bool useInvertedHeaderStyle = (isInverted && !force_no_invert && !isTFTColoringEnabled() && !transparent_background);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ int statusLeftEndX = 0;
+ int statusRightStartX = screenW;
+ const bool isClockHeader = transparent_background && show_date && (!titleStr || titleStr[0] == '\0');
+ const auto activeThemeId = getActiveTheme().id;
+ const bool useClockHeaderAccent = isClockHeader && useClockHeaderAccentTheme(activeThemeId);
+#endif
+
+ {
+ const uint16_t headerColor = getThemeHeaderBg();
+ const uint16_t headerTextColor = getThemeHeaderText();
+ const uint16_t headerTitleColorForRole = use_title_color_override ? title_color_override : headerTextColor;
+ uint16_t headerStatusColor = getThemeHeaderStatus();
+#if GRAPHICS_TFT_COLORING_ENABLED
+ // Clock frame uses transparent header + date + empty title.
+ // For accent clock themes (Pink/Creamsicle + classic monochrome), tint
+ // status items (battery outline, %, date, mail icon) to the header accent.
+ if (useClockHeaderAccent) {
+ headerStatusColor = getThemeHeaderBg();
+ }
+
+ if (transparent_background) {
+ // Transparent clock headers should inherit whatever body off-color is
+ // already active under the header (important for light/inverted themes).
+ const uint16_t transparentBgColor = resolveTFTOffColorAt(0, headerHeight + 1, getThemeBodyBg());
+ setAndRegisterTFTColorRole(TFTColorRole::HeaderBackground, transparentBgColor, transparentBgColor, 0, 0, screenW,
+ headerHeight);
+ setTFTColorRole(TFTColorRole::HeaderTitle, headerTitleColorForRole, transparentBgColor);
+ setTFTColorRole(TFTColorRole::HeaderStatus, headerStatusColor, transparentBgColor);
+ } else if (useInvertedHeaderStyle) {
+ setAndRegisterTFTColorRole(TFTColorRole::HeaderBackground, headerColor, TFTPalette::Black, 0, 0, screenW,
+ headerHeight);
+ setTFTColorRole(TFTColorRole::HeaderTitle, headerColor, headerTitleColorForRole);
+ setTFTColorRole(TFTColorRole::HeaderStatus, headerColor, headerStatusColor);
+ } else {
+ setAndRegisterTFTColorRole(TFTColorRole::HeaderBackground, TFTPalette::Black, headerColor, 0, 0, screenW,
+ headerHeight);
+ setTFTColorRole(TFTColorRole::HeaderTitle, headerTitleColorForRole, headerColor);
+ setTFTColorRole(TFTColorRole::HeaderStatus, headerStatusColor, headerColor);
+ }
+#endif
- if (!force_no_invert) {
// === Inverted Header Background ===
- if (isInverted) {
+ if (useInvertedHeaderStyle) {
display->setColor(BLACK);
- display->fillRect(0, 0, screenW, highlightHeight + 2);
+ display->fillRect(0, 0, screenW, headerHeight);
display->setColor(WHITE);
drawRoundedHighlight(display, x, y, screenW, highlightHeight, 2);
display->setColor(BLACK);
} else {
display->setColor(BLACK);
- display->fillRect(0, 0, screenW, highlightHeight + 2);
- display->setColor(WHITE);
- if (currentResolution == ScreenResolution::High) {
- display->drawLine(0, 20, screenW, 20);
- } else {
- display->drawLine(0, 14, screenW, 14);
+ display->fillRect(0, 0, screenW, headerHeight);
+// Keep the legacy white separator for monochrome displays only when header background is visible.
+#if !GRAPHICS_TFT_COLORING_ENABLED
+ if (!transparent_background) {
+ display->setColor(WHITE);
+ if (currentResolution == ScreenResolution::High) {
+ display->drawLine(0, 20, screenW, 20);
+ } else {
+ display->drawLine(0, 14, screenW, 14);
+ }
}
+#endif
}
+ if (transparent_background) {
+ display->setColor(WHITE);
+ }
+
+#if GRAPHICS_TFT_COLORING_ENABLED
+ // TFT role coloring expects foreground glyph bits to be "set".
+ display->setColor(WHITE);
+#endif
+
// === Screen Title ===
const char *headerTitle = titleStr ? titleStr : "";
const int titleWidth = UIRenderer::measureStringWithEmotes(display, headerTitle);
const int titleX = (SCREEN_WIDTH - titleWidth) / 2;
+#if GRAPHICS_TFT_COLORING_ENABLED
+ const int titleRegionWidth = titleWidth + (config.display.heading_bold ? 3 : 2);
+ registerTFTColorRegion(TFTColorRole::HeaderTitle, titleX - 1, y, titleRegionWidth, FONT_HEIGHT_SMALL);
+#endif
UIRenderer::drawStringWithEmotes(display, titleX, y, headerTitle, FONT_HEIGHT_SMALL, 1, config.display.heading_bold);
}
display->setTextAlignment(TEXT_ALIGN_LEFT);
@@ -152,6 +226,17 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
bool useHorizontalBattery = (currentResolution == ScreenResolution::High && screenW >= screenH);
const int textY = y + (highlightHeight - FONT_HEIGHT_SMALL) / 2;
+ bool hasBatteryFillRegion = false;
+ int16_t batteryFillRegionX = 0;
+ int16_t batteryFillRegionY = 0;
+ int16_t batteryFillRegionW = 0;
+ int16_t batteryFillRegionH = 0;
+#if GRAPHICS_TFT_COLORING_ENABLED
+ uint16_t batteryFillColor = getThemeBatteryFillColor(chargePercent);
+ if (useClockHeaderAccent) {
+ batteryFillColor = getThemeHeaderBg();
+ }
+#endif
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
@@ -180,6 +265,15 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
display->drawLine(batteryX + 5, batteryY + 12, batteryX + 10, batteryY + 12);
int fillWidth = 14 * chargePercent / 100;
display->fillRect(batteryX + 1, batteryY + 1, fillWidth, 11);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (fillWidth > 0) {
+ hasBatteryFillRegion = true;
+ batteryFillRegionX = batteryX + 1;
+ batteryFillRegionY = batteryY + 1;
+ batteryFillRegionW = fillWidth;
+ batteryFillRegionH = 11;
+ }
+#endif
}
batteryX += 18; // Icon + 2 pixels
} else {
@@ -194,21 +288,41 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int fillHeight = 8 * chargePercent / 100;
int fillY = batteryY - fillHeight;
display->fillRect(batteryX + 1, fillY + 10, 5, fillHeight);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (fillHeight > 0) {
+ hasBatteryFillRegion = true;
+ batteryFillRegionX = batteryX + 1;
+ batteryFillRegionY = fillY + 10;
+ batteryFillRegionW = 5;
+ batteryFillRegionH = fillHeight;
+ }
+#endif
}
batteryX += 9; // Icon + 2 pixels
}
}
+#if GRAPHICS_TFT_COLORING_ENABLED
+ statusLeftEndX = batteryX + 2;
+#endif
if (chargePercent != 101) {
// === Battery % Display ===
char chargeStr[4];
snprintf(chargeStr, sizeof(chargeStr), "%d", chargePercent);
int chargeNumWidth = display->getStringWidth(chargeStr);
+ const int percentWidth = display->getStringWidth("%");
+ const int percentX = batteryX + chargeNumWidth - 1;
display->drawString(batteryX, textY, chargeStr);
- display->drawString(batteryX + chargeNumWidth - 1, textY, "%");
+ display->drawString(percentX, textY, "%");
+#if GRAPHICS_TFT_COLORING_ENABLED
+ statusLeftEndX = percentX + percentWidth + 2;
+#endif
if (isBold) {
display->drawString(batteryX + 1, textY, chargeStr);
- display->drawString(batteryX + chargeNumWidth, textY, "%");
+ display->drawString(percentX + 1, textY, "%");
+#if GRAPHICS_TFT_COLORING_ENABLED
+ statusLeftEndX = percentX + percentWidth + 3;
+#endif
}
}
@@ -253,6 +367,9 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
timeStrWidth = display->getStringWidth(timeStr);
}
timeX = screenW - xOffset - timeStrWidth + 3;
+#if GRAPHICS_TFT_COLORING_ENABLED
+ statusRightStartX = timeX - (useHorizontalBattery ? 22 : 16);
+#endif
// === Show Mail or Mute Icon to the Left of Time ===
int iconRightEdge = timeX - 2;
@@ -278,7 +395,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int iconW = 16, iconH = 12;
int iconX = iconRightEdge - iconW;
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
- if (isInverted && !force_no_invert) {
+ if (useInvertedHeaderStyle) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
display->setColor(BLACK);
@@ -293,7 +410,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
} else {
int iconX = iconRightEdge - (mail_width - 2);
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
- if (isInverted && !force_no_invert) {
+ if (useInvertedHeaderStyle) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
display->setColor(BLACK);
@@ -309,7 +426,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int iconX = iconRightEdge - mute_symbol_big_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
- if (isInverted && !force_no_invert) {
+ if (useInvertedHeaderStyle) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
display->setColor(BLACK);
@@ -323,7 +440,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int iconX = iconRightEdge - mute_symbol_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
- if (isInverted && !force_no_invert) {
+ if (useInvertedHeaderStyle) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_width + 2, mute_symbol_height + 2);
display->setColor(BLACK);
@@ -351,7 +468,9 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
} else {
// === No Time Available: Mail/Mute Icon Moves to Far Right ===
int iconRightEdge = screenW - xOffset;
-
+#if GRAPHICS_TFT_COLORING_ENABLED
+ statusRightStartX = screenW - (useHorizontalBattery ? 22 : 12);
+#endif
bool showMail = false;
#ifndef USE_EINK
@@ -393,6 +512,16 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
}
}
}
+#endif
+#if GRAPHICS_TFT_COLORING_ENABLED
+ registerTFTColorRegion(TFTColorRole::HeaderStatus, 0, 0, statusLeftEndX, headerHeight);
+ if (statusRightStartX < screenW) {
+ registerTFTColorRegion(TFTColorRole::HeaderStatus, statusRightStartX, 0, screenW - statusRightStartX, headerHeight);
+ }
+ if (hasBatteryFillRegion) {
+ registerTFTColorRegionDirect(batteryFillRegionX, batteryFillRegionY, batteryFillRegionW, batteryFillRegionH,
+ batteryFillColor, headerColorForRoles);
+ }
#endif
display->setColor(WHITE); // Reset for other UI
}
@@ -430,14 +559,23 @@ void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
return;
const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1;
+ const int footerY = SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale);
+ const int footerH = (connection_icon_height * scale) + (2 * scale);
+ const int iconX = 0;
+ const int iconY = SCREEN_HEIGHT - (connection_icon_height * scale);
+ const int iconW = connection_icon_width * scale;
+ const int iconH = connection_icon_height * scale;
+
+#if GRAPHICS_TFT_COLORING_ENABLED
+ // Only tint the link glyph itself on TFT; keep the footer background black.
+ setAndRegisterTFTColorRole(TFTColorRole::ConnectionIcon, TFTPalette::Blue, TFTPalette::Black, iconX, iconY, iconW, iconH);
+#endif
+
display->setColor(BLACK);
- display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale),
- (connection_icon_height * scale) + (2 * scale));
+ display->fillRect(0, footerY, SCREEN_WIDTH, footerH);
display->setColor(WHITE);
if (currentResolution == ScreenResolution::High) {
const int bytesPerRow = (connection_icon_width + 7) / 8;
- int iconX = 0;
- int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
for (int yy = 0; yy < connection_icon_height; ++yy) {
const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
@@ -451,65 +589,127 @@ void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
}
} else {
- display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
- connection_icon);
+ display->drawXbm(iconX, iconY, connection_icon_width, connection_icon_height, connection_icon);
}
}
bool isAllowedPunctuation(char c)
{
- const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
- return allowed.find(c) != std::string::npos;
+ switch (c) {
+ case '.':
+ case ',':
+ case '!':
+ case '?':
+ case ';':
+ case ':':
+ case '-':
+ case '_':
+ case '(':
+ case ')':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ case '\'':
+ case '"':
+ case '@':
+ case '#':
+ case '$':
+ case '/':
+ case '\\':
+ case '&':
+ case '+':
+ case '=':
+ case '%':
+ case '~':
+ case '^':
+ case ' ':
+ return true;
+ default:
+ return false;
+ }
}
-static void replaceAll(std::string &s, const std::string &from, const std::string &to)
+static inline size_t utf8CodePointLength(unsigned char lead)
{
- if (from.empty())
- return;
- size_t pos = 0;
- while ((pos = s.find(from, pos)) != std::string::npos) {
- s.replace(pos, from.size(), to);
- pos += to.size();
+ if ((lead & 0x80) == 0x00) {
+ return 1;
}
+ if ((lead & 0xE0) == 0xC0) {
+ return 2;
+ }
+ if ((lead & 0xF0) == 0xE0) {
+ return 3;
+ }
+ if ((lead & 0xF8) == 0xF0) {
+ return 4;
+ }
+ return 1;
}
std::string sanitizeString(const std::string &input)
{
+ static constexpr char kReplacementChar = static_cast(0xBF); // Inverted question mark in ISO-8859-1.
std::string output;
+ output.reserve(input.size());
bool inReplacement = false;
-
- // Make a mutable copy so we can normalize UTF-8 “smart punctuation” into ASCII first.
- std::string s = input;
-
- // Curly single quotes: ‘ ’
- replaceAll(s, "\xE2\x80\x98", "'"); // U+2018
- replaceAll(s, "\xE2\x80\x99", "'"); // U+2019
-
- // Curly double quotes: “ ”
- replaceAll(s, "\xE2\x80\x9C", "\""); // U+201C
- replaceAll(s, "\xE2\x80\x9D", "\""); // U+201D
-
- // En dash / Em dash: – —
- replaceAll(s, "\xE2\x80\x93", "-"); // U+2013
- replaceAll(s, "\xE2\x80\x94", "-"); // U+2014
-
- // Non-breaking space
- replaceAll(s, "\xC2\xA0", " "); // U+00A0
-
- // Now do your original sanitize pass over the normalized string.
- for (unsigned char uc : s) {
- char c = static_cast(uc);
- if (std::isalnum(uc) || isAllowedPunctuation(c)) {
- output += c;
- inReplacement = false;
- } else {
+ const size_t inputSize = input.size();
+ size_t i = 0;
+ while (i < inputSize) {
+ const unsigned char byte0 = static_cast(input[i]);
+ char normalized = '\0';
+ size_t consumed = 0;
+ if (byte0 < 0x80) {
+ normalized = static_cast(byte0);
+ consumed = 1;
+ } else if ((i + 2) < inputSize && byte0 == 0xE2 && static_cast(input[i + 1]) == 0x80) {
+ // Smart punctuation: ' ' \" \" - -
+ switch (static_cast(input[i + 2])) {
+ case 0x98:
+ case 0x99:
+ normalized = '\'';
+ consumed = 3;
+ break;
+ case 0x9C:
+ case 0x9D:
+ normalized = '\"';
+ consumed = 3;
+ break;
+ case 0x93:
+ case 0x94:
+ normalized = '-';
+ consumed = 3;
+ break;
+ default:
+ break;
+ }
+ } else if ((i + 1) < inputSize && byte0 == 0xC2 && static_cast(input[i + 1]) == 0xA0) {
+ // Non-breaking space.
+ normalized = ' ';
+ consumed = 2;
+ }
+ if (consumed == 0) {
+ size_t seqLen = utf8CodePointLength(byte0);
+ if (seqLen > (inputSize - i)) {
+ seqLen = 1;
+ }
if (!inReplacement) {
- output += static_cast(0xBF); // ISO-8859-1 for inverted question mark
+ output.push_back(kReplacementChar);
inReplacement = true;
}
+ i += seqLen;
+ continue;
}
+ const unsigned char normalizedUc = static_cast(normalized);
+ if (std::isalnum(normalizedUc) || isAllowedPunctuation(normalized)) {
+ output.push_back(normalized);
+ inReplacement = false;
+ } else if (!inReplacement) {
+ output.push_back(kReplacementChar);
+ inReplacement = true;
+ }
+ i += consumed;
}
-
return output;
}
diff --git a/src/graphics/SharedUIDisplay.h b/src/graphics/SharedUIDisplay.h
index 35e767056..95244d099 100644
--- a/src/graphics/SharedUIDisplay.h
+++ b/src/graphics/SharedUIDisplay.h
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
#include
namespace graphics
@@ -52,7 +53,8 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
// Shared battery/time/mail header
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
- bool show_date = false);
+ bool show_date = false, bool transparent_background = false, bool use_title_color_override = false,
+ uint16_t title_color_override = 0);
// Shared battery/time/mail header
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y);
diff --git a/src/graphics/TFTColorRegions.cpp b/src/graphics/TFTColorRegions.cpp
new file mode 100644
index 000000000..877835ea6
--- /dev/null
+++ b/src/graphics/TFTColorRegions.cpp
@@ -0,0 +1,819 @@
+#include "TFTColorRegions.h"
+#include "NodeDB.h"
+#include "TFTPalette.h"
+
+#include
+
+namespace graphics
+{
+TFTColorRegion colorRegions[MAX_TFT_COLOR_REGIONS];
+
+namespace
+{
+
+struct TFTRoleColorsBe {
+ uint16_t onColorBe;
+ uint16_t offColorBe;
+};
+
+static uint8_t colorRegionCount = 0;
+static constexpr uint32_t kFnv1aOffsetBasis = 2166136261u;
+static constexpr uint32_t kFnv1aPrime = 16777619u;
+
+static constexpr uint16_t toBe565(uint16_t color)
+{
+ return static_cast((color >> 8) | (color << 8));
+}
+
+static constexpr bool kRoleIsBody[static_cast(TFTColorRole::Count)] = {
+ false, // HeaderBackground
+ false, // HeaderTitle
+ false, // HeaderStatus
+ true, // SignalBars
+ true, // ConnectionIcon
+ true, // UtilizationFill
+ true, // FavoriteNode
+ true, // ActionMenuBorder
+ true, // ActionMenuBody
+ true, // ActionMenuTitle
+ true, // FrameMono
+ false, // BootSplash
+ true, // FavoriteNodeBGHighlight
+ false, // NavigationBar
+ false // NavigationArrow
+};
+
+static inline bool isBodyColorRole(TFTColorRole role)
+{
+ return kRoleIsBody[static_cast(role)];
+}
+
+static inline bool isMonochromeTheme(uint32_t themeId)
+{
+ return themeId == ThemeID::MeshtasticGreen || themeId == ThemeID::ClassicRed || themeId == ThemeID::MonochromeWhite;
+}
+
+static inline uint16_t getMonochromeAccent(uint32_t themeId)
+{
+ return (themeId == ThemeID::MeshtasticGreen) ? TFTPalette::MeshtasticGreen
+ : (themeId == ThemeID::ClassicRed) ? TFTPalette::ClassicRed
+ : TFTPalette::White;
+}
+
+static inline void replaceColor(uint16_t &value, uint16_t from, uint16_t to)
+{
+ if (value == from) {
+ value = to;
+ }
+}
+
+static inline uint32_t fnv1aAppendByte(uint32_t hash, uint8_t value)
+{
+ return (hash ^ value) * kFnv1aPrime;
+}
+
+static inline uint32_t fnv1aAppendU16(uint32_t hash, uint16_t value)
+{
+ hash = fnv1aAppendByte(hash, static_cast(value & 0xFF));
+ hash = fnv1aAppendByte(hash, static_cast((value >> 8) & 0xFF));
+ return hash;
+}
+
+// Compile-time header color overrides (backward-compatible)
+#ifdef TFT_HEADER_BG_COLOR_OVERRIDE
+static constexpr uint16_t kHeaderBackground = TFT_HEADER_BG_COLOR_OVERRIDE;
+#else
+static constexpr uint16_t kHeaderBackground = TFTPalette::DarkGray;
+#endif
+
+#ifdef TFT_HEADER_TITLE_COLOR_OVERRIDE
+static constexpr uint16_t kTitleColor = TFT_HEADER_TITLE_COLOR_OVERRIDE;
+#else
+static constexpr uint16_t kTitleColor = TFTPalette::White;
+#endif
+
+#ifdef TFT_HEADER_STATUS_COLOR_OVERRIDE
+static constexpr uint16_t kStatusColor = TFT_HEADER_STATUS_COLOR_OVERRIDE;
+#else
+static constexpr uint16_t kStatusColor = TFTPalette::White;
+#endif
+
+// Theme definitions
+// Stored in kThemes[] and looked up by matching uiconfig.screen_rgb_color
+// against each entry's .uniqueIdentifier field.
+
+static const TFTThemeDef kThemes[] = {
+
+ // Default Dark (ThemeID::DefaultDark = 0)
+ {
+ ThemeID::DefaultDark, // id
+ "Default Dark", // name
+ 0, // uniqueIdentifier
+ // roles[TFTColorRole::Count]
+ {
+ {kHeaderBackground, TFTPalette::Black}, // HeaderBackground
+ {kHeaderBackground, kTitleColor}, // HeaderTitle
+ {kHeaderBackground, kStatusColor}, // HeaderStatus
+ {TFTPalette::Good, TFTPalette::Black}, // SignalBars
+ {TFTPalette::Blue, TFTPalette::Black}, // ConnectionIcon
+ {TFTPalette::Good, TFTPalette::Black}, // UtilizationFill
+ {TFTPalette::Yellow, TFTPalette::Black}, // FavoriteNode
+ {TFTPalette::DarkGray, TFTPalette::Black}, // ActionMenuBorder
+ {TFTPalette::White, TFTPalette::Black}, // ActionMenuBody
+ {TFTPalette::DarkGray, TFTPalette::White}, // ActionMenuTitle
+ {TFTPalette::Black, TFTPalette::White}, // FrameMono
+ {TFTPalette::White, TFTPalette::Black}, // BootSplash
+ {TFTPalette::Yellow, TFTPalette::Black}, // FavoriteNodeBGHighlight
+ {kStatusColor, kHeaderBackground}, // NavigationBar (icon fg, bar bg)
+ {kTitleColor, TFTPalette::Black}, // NavigationArrow (arrow fg, body bg)
+ },
+ TFTPalette::Good, // batteryFillGood
+ TFTPalette::Medium, // batteryFillMedium
+ TFTPalette::Bad, // batteryFillBad
+ false, // fullFrameInvert
+ true, // visible
+ },
+
+ // Default Light (ThemeID::DefaultLight = 1)
+ {
+ ThemeID::DefaultLight, // id
+ "Default Light", // name
+ 1, // uniqueIdentifier
+ {
+ {TFTPalette::LightGray, TFTPalette::Black}, // HeaderBackground
+ {TFTPalette::LightGray, TFTPalette::Black}, // HeaderTitle
+ {TFTPalette::LightGray, TFTPalette::Black}, // HeaderStatus
+ {TFTPalette::Good, TFTPalette::White}, // SignalBars
+ {TFTPalette::Blue, TFTPalette::White}, // ConnectionIcon
+ {TFTPalette::Good, TFTPalette::White}, // UtilizationFill
+ {TFTPalette::Black, TFTPalette::Yellow}, // FavoriteNode
+ {TFTPalette::DarkGray, TFTPalette::White}, // ActionMenuBorder
+ {TFTPalette::Black, TFTPalette::White}, // ActionMenuBody
+ {TFTPalette::DarkGray, TFTPalette::Black}, // ActionMenuTitle
+ {TFTPalette::Black, TFTPalette::White}, // FrameMono
+ {TFTPalette::White, TFTPalette::Black}, // BootSplash
+ {TFTPalette::Black, TFTPalette::Yellow}, // FavoriteNodeBGHighlight
+ {TFTPalette::Black, TFTPalette::LightGray}, // NavigationBar (icon fg, bar bg)
+ {TFTPalette::Black, TFTPalette::White}, // NavigationArrow (arrow fg, body bg)
+ },
+ TFTPalette::Good, // batteryFillGood
+ TFTPalette::Medium, // batteryFillMedium
+ TFTPalette::Bad, // batteryFillBad
+ true, // fullFrameInvert
+ true, // visible
+ },
+
+ // Christmas (ThemeID::Christmas = 2)
+ {
+ ThemeID::Christmas, // id
+ "Christmas", // name
+ 2, // uniqueIdentifier
+ {
+ {TFTPalette::ChristmasRed, TFTPalette::Black}, // HeaderBackground
+ {TFTPalette::ChristmasRed, TFTPalette::Gold}, // HeaderTitle
+ {TFTPalette::ChristmasRed, TFTPalette::Gold}, // HeaderStatus
+ {TFTPalette::ChristmasGreen, TFTPalette::Pine}, // SignalBars
+ {TFTPalette::Gold, TFTPalette::Pine}, // ConnectionIcon
+ {TFTPalette::ChristmasGreen, TFTPalette::Pine}, // UtilizationFill
+ {TFTPalette::Gold, TFTPalette::Pine}, // FavoriteNode
+ {TFTPalette::ChristmasRed, TFTPalette::Pine}, // ActionMenuBorder
+ {TFTPalette::White, TFTPalette::Pine}, // ActionMenuBody
+ {TFTPalette::ChristmasRed, TFTPalette::White}, // ActionMenuTitle
+ {TFTPalette::Pine, TFTPalette::White}, // FrameMono
+ {TFTPalette::White, TFTPalette::ChristmasRed}, // BootSplash
+ {TFTPalette::Gold, TFTPalette::Pine}, // FavoriteNodeBGHighlight
+ {TFTPalette::Gold, TFTPalette::ChristmasRed}, // NavigationBar (icon fg, bar bg)
+ {TFTPalette::Gold, TFTPalette::Pine}, // NavigationArrow (arrow fg, body bg)
+ },
+ TFTPalette::ChristmasGreen, // batteryFillGood
+ TFTPalette::Gold, // batteryFillMedium
+ TFTPalette::ChristmasRed, // batteryFillBad
+ true, // fullFrameInvert
+ false, // visible
+ },
+
+ // Pink (ThemeID::Pink = 3) light variant
+ {
+ ThemeID::Pink, // id
+ "Pink", // name
+ 3, // uniqueIdentifier
+ {
+ {TFTPalette::HotPink, TFTPalette::Black}, // HeaderBackground
+ {TFTPalette::HotPink, TFTPalette::White}, // HeaderTitle
+ {TFTPalette::HotPink, TFTPalette::White}, // HeaderStatus
+ {TFTPalette::DeepPink, TFTPalette::PalePink}, // SignalBars
+ {TFTPalette::HotPink, TFTPalette::PalePink}, // ConnectionIcon
+ {TFTPalette::DeepPink, TFTPalette::PalePink}, // UtilizationFill
+ {TFTPalette::Black, TFTPalette::HotPink}, // FavoriteNode
+ {TFTPalette::HotPink, TFTPalette::PalePink}, // ActionMenuBorder
+ {TFTPalette::Black, TFTPalette::PalePink}, // ActionMenuBody
+ {TFTPalette::HotPink, TFTPalette::White}, // ActionMenuTitle
+ {TFTPalette::Black, TFTPalette::White}, // FrameMono
+ {TFTPalette::White, TFTPalette::HotPink}, // BootSplash
+ {TFTPalette::Black, TFTPalette::HotPink}, // FavoriteNodeBGHighlight
+ {TFTPalette::White, TFTPalette::HotPink}, // NavigationBar (icon fg, bar bg)
+ {TFTPalette::HotPink, TFTPalette::PalePink}, // NavigationArrow (arrow fg, body bg)
+ },
+ TFTPalette::DeepPink, // batteryFillGood
+ TFTPalette::HotPink, // batteryFillMedium
+ TFTPalette::Bad, // batteryFillBad
+ true, // fullFrameInvert
+ true, // visible
+ },
+
+ // Blue (ThemeID::Blue = 4) dark variant
+ {
+ ThemeID::Blue, // id
+ "Blue", // name
+ 4, // uniqueIdentifier
+ {
+ {TFTPalette::DeepBlue, TFTPalette::Black}, // HeaderBackground
+ {TFTPalette::DeepBlue, TFTPalette::White}, // HeaderTitle
+ {TFTPalette::DeepBlue, TFTPalette::SkyBlue}, // HeaderStatus
+ {TFTPalette::SkyBlue, TFTPalette::Navy}, // SignalBars
+ {TFTPalette::SkyBlue, TFTPalette::Navy}, // ConnectionIcon
+ {TFTPalette::SkyBlue, TFTPalette::Navy}, // UtilizationFill
+ {TFTPalette::SkyBlue, TFTPalette::Navy}, // FavoriteNode
+ {TFTPalette::DeepBlue, TFTPalette::Navy}, // ActionMenuBorder
+ {TFTPalette::White, TFTPalette::Navy}, // ActionMenuBody
+ {TFTPalette::DeepBlue, TFTPalette::White}, // ActionMenuTitle
+ {TFTPalette::Navy, TFTPalette::White}, // FrameMono
+ {TFTPalette::White, TFTPalette::DeepBlue}, // BootSplash
+ {TFTPalette::SkyBlue, TFTPalette::Navy}, // FavoriteNodeBGHighlight
+ {TFTPalette::SkyBlue, TFTPalette::DeepBlue}, // NavigationBar (icon fg, bar bg)
+ {TFTPalette::SkyBlue, TFTPalette::Black}, // NavigationArrow (arrow fg, body bg)
+ },
+ TFTPalette::SkyBlue, // batteryFillGood
+ TFTPalette::Medium, // batteryFillMedium
+ TFTPalette::Bad, // batteryFillBad
+ true, // fullFrameInvert
+ true, // visible
+ },
+
+ // Creamsicle (ThemeID::Creamsicle = 5)light variant
+ {
+ ThemeID::Creamsicle, // id
+ "Creamsicle", // name
+ 5, // uniqueIdentifier
+ {
+ {TFTPalette::CreamOrange, TFTPalette::Black}, // HeaderBackground
+ {TFTPalette::CreamOrange, TFTPalette::White}, // HeaderTitle
+ {TFTPalette::CreamOrange, TFTPalette::White}, // HeaderStatus
+ {TFTPalette::DeepOrange, TFTPalette::Cream}, // SignalBars
+ {TFTPalette::CreamOrange, TFTPalette::Cream}, // ConnectionIcon
+ {TFTPalette::DeepOrange, TFTPalette::Cream}, // UtilizationFill
+ {TFTPalette::Black, TFTPalette::CreamOrange}, // FavoriteNode
+ {TFTPalette::CreamOrange, TFTPalette::Cream}, // ActionMenuBorder
+ {TFTPalette::Black, TFTPalette::Cream}, // ActionMenuBody
+ {TFTPalette::CreamOrange, TFTPalette::White}, // ActionMenuTitle
+ {TFTPalette::Black, TFTPalette::White}, // FrameMono
+ {TFTPalette::White, TFTPalette::CreamOrange}, // BootSplash
+ {TFTPalette::Black, TFTPalette::CreamOrange}, // FavoriteNodeBGHighlight
+ {TFTPalette::White, TFTPalette::CreamOrange}, // NavigationBar (icon fg, bar bg)
+ {TFTPalette::CreamOrange, TFTPalette::White}, // NavigationArrow (arrow fg, body bg)
+ },
+ TFTPalette::DeepOrange, // batteryFillGood
+ TFTPalette::Gold, // batteryFillMedium
+ TFTPalette::Bad, // batteryFillBad
+ true, // fullFrameInvert
+ true, // visible
+ },
+
+ // Meshtastic Green (ThemeID::MeshtasticGreen = 6) classic monochrome
+ // Pure single-color-on-black look. Every role maps foreground pixels to
+ // the theme color and background pixels to Black.
+ {
+ ThemeID::MeshtasticGreen, // id
+ "Meshtastic Green", // name
+ 6, // uniqueIdentifier
+ {
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // HeaderBackground
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // HeaderTitle
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // HeaderStatus
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // SignalBars
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // ConnectionIcon
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // UtilizationFill
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // FavoriteNode
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // ActionMenuBorder
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // ActionMenuBody
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // ActionMenuTitle
+ {TFTPalette::Black, TFTPalette::MeshtasticGreen}, // FrameMono (bodyBg, bodyFg)
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // BootSplash
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // FavoriteNodeBGHighlight
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // NavigationBar
+ {TFTPalette::MeshtasticGreen, TFTPalette::Black}, // NavigationArrow
+ },
+ TFTPalette::Black, // batteryFillGood
+ TFTPalette::Black, // batteryFillMedium
+ TFTPalette::Black, // batteryFillBad
+ true, // fullFrameInvert
+ true, // visible
+ },
+
+ // Classic Red (ThemeID::ClassicRed = 7) classic monochrome
+ {
+ ThemeID::ClassicRed, // id
+ "Classic Red", // name
+ 7, // uniqueIdentifier
+ {
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // HeaderBackground
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // HeaderTitle
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // HeaderStatus
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // SignalBars
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // ConnectionIcon
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // UtilizationFill
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // FavoriteNode
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // ActionMenuBorder
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // ActionMenuBody
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // ActionMenuTitle
+ {TFTPalette::Black, TFTPalette::ClassicRed}, // FrameMono (bodyBg, bodyFg)
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // BootSplash
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // FavoriteNodeBGHighlight
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // NavigationBar
+ {TFTPalette::ClassicRed, TFTPalette::Black}, // NavigationArrow
+ },
+ TFTPalette::Black, // batteryFillGood
+ TFTPalette::Black, // batteryFillMedium
+ TFTPalette::Black, // batteryFillBad
+ true, // fullFrameInvert
+ true, // visible
+ },
+
+ // Monochrome White (ThemeID::MonochromeWhite = 8) classic monochrome
+ {
+ ThemeID::MonochromeWhite, // id
+ "Monochrome White", // name
+ 8, // uniqueIdentifier
+ {
+ {TFTPalette::White, TFTPalette::Black}, // HeaderBackground
+ {TFTPalette::White, TFTPalette::Black}, // HeaderTitle
+ {TFTPalette::White, TFTPalette::Black}, // HeaderStatus
+ {TFTPalette::White, TFTPalette::Black}, // SignalBars
+ {TFTPalette::White, TFTPalette::Black}, // ConnectionIcon
+ {TFTPalette::White, TFTPalette::Black}, // UtilizationFill
+ {TFTPalette::White, TFTPalette::Black}, // FavoriteNode
+ {TFTPalette::White, TFTPalette::Black}, // ActionMenuBorder
+ {TFTPalette::White, TFTPalette::Black}, // ActionMenuBody
+ {TFTPalette::White, TFTPalette::Black}, // ActionMenuTitle
+ {TFTPalette::Black, TFTPalette::White}, // FrameMono (bodyBg, bodyFg)
+ {TFTPalette::White, TFTPalette::Black}, // BootSplash
+ {TFTPalette::White, TFTPalette::Black}, // FavoriteNodeBGHighlight
+ {TFTPalette::White, TFTPalette::Black}, // NavigationBar
+ {TFTPalette::White, TFTPalette::Black}, // NavigationArrow
+ },
+ TFTPalette::Black, // batteryFillGood
+ TFTPalette::Black, // batteryFillMedium
+ TFTPalette::Black, // batteryFillBad
+ true, // fullFrameInvert
+ true, // visible
+ },
+};
+
+static constexpr size_t kInternalThemeCount = sizeof(kThemes) / sizeof(kThemes[0]);
+
+// Resolve the kThemes[] index for the currently persisted theme. Called at
+// boot (indirectly via getActiveTheme()) and whenever the active theme is
+// queried, so uiconfig.screen_rgb_color remains the single source of truth.
+// Matches against .uniqueIdentifier - that's the field whose value is stored
+// in the user's config. Falls back to 0 (DefaultDark) if no match is found,
+// which gracefully handles removed or retired themes.
+static inline size_t resolveThemeIndex()
+{
+ const uint32_t savedIdentifier = uiconfig.screen_rgb_color & 0x1F;
+ for (size_t i = 0; i < kInternalThemeCount; i++) {
+ if (kThemes[i].uniqueIdentifier == savedIdentifier)
+ return i;
+ }
+ return 0; // Default Dark fallback
+}
+
+static inline bool normalizeRegion(int16_t &x, int16_t &y, int16_t &width, int16_t &height)
+{
+ if (width <= 0 || height <= 0) {
+ return false;
+ }
+
+ if (x < 0) {
+ width += x;
+ x = 0;
+ }
+ if (y < 0) {
+ height += y;
+ y = 0;
+ }
+
+ return width > 0 && height > 0;
+}
+
+static inline void appendColorRegion(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t onColorBe, uint16_t offColorBe)
+{
+ // Keep the last slot permanently disabled as a sentinel for ST7789 scans.
+ // This leaves MAX_TFT_COLOR_REGIONS - 1 usable entries.
+ if (colorRegionCount >= MAX_TFT_COLOR_REGIONS - 1) {
+ memmove(&colorRegions[0], &colorRegions[1], sizeof(TFTColorRegion) * (MAX_TFT_COLOR_REGIONS - 2));
+ colorRegionCount = MAX_TFT_COLOR_REGIONS - 2;
+ }
+
+ TFTColorRegion ®ion = colorRegions[colorRegionCount++];
+ region.x = x;
+ region.y = y;
+ region.width = width;
+ region.height = height;
+ region.onColorBe = onColorBe;
+ region.offColorBe = offColorBe;
+ region.enabled = true;
+
+ // Keep one disabled sentinel after the active range for ST7789 countColorRegions().
+ if (colorRegionCount < MAX_TFT_COLOR_REGIONS) {
+ colorRegions[colorRegionCount].enabled = false;
+ }
+ colorRegions[MAX_TFT_COLOR_REGIONS - 1].enabled = false;
+}
+
+// Current working role colors (big-endian). Initialised to Dark defaults;
+// call loadThemeDefaults() after boot / theme change to refresh.
+static TFTRoleColorsBe roleColors[static_cast(TFTColorRole::Count)] = {
+ {toBe565(kHeaderBackground), toBe565(TFTPalette::Black)}, // HeaderBackground
+ {toBe565(kHeaderBackground), toBe565(kTitleColor)}, // HeaderTitle
+ {toBe565(kHeaderBackground), toBe565(kStatusColor)}, // HeaderStatus
+ {toBe565(TFTPalette::Good), toBe565(TFTPalette::Black)}, // SignalBars
+ {toBe565(TFTPalette::Blue), toBe565(TFTPalette::Black)}, // ConnectionIcon
+ {toBe565(TFTPalette::Good), toBe565(TFTPalette::Black)}, // UtilizationFill
+ {toBe565(TFTPalette::Yellow), toBe565(TFTPalette::Black)}, // FavoriteNode
+ {toBe565(TFTPalette::DarkGray), toBe565(TFTPalette::Black)}, // ActionMenuBorder
+ {toBe565(TFTPalette::White), toBe565(TFTPalette::Black)}, // ActionMenuBody
+ {toBe565(TFTPalette::DarkGray), toBe565(TFTPalette::White)}, // ActionMenuTitle
+ {toBe565(TFTPalette::Black), toBe565(TFTPalette::White)}, // FrameMono
+ {toBe565(TFTPalette::White), toBe565(TFTPalette::Black)}, // BootSplash
+ {toBe565(TFTPalette::Yellow), toBe565(TFTPalette::Black)}, // FavoriteNodeBGHighlight
+ {toBe565(kStatusColor), toBe565(kHeaderBackground)}, // NavigationBar
+ {toBe565(kTitleColor), toBe565(TFTPalette::Black)} // NavigationArrow
+};
+
+} // namespace
+
+// Theme accessors
+
+const TFTThemeDef &getActiveTheme()
+{
+ return kThemes[resolveThemeIndex()];
+}
+
+// Visible-theme accessors
+// These iterate only themes flagged .visible = true, preserving kThemes[]
+// order. Menu code should use these so hidden themes don't appear in the
+// picker while still applying correctly if their ID is persisted.
+
+size_t getVisibleThemeCount()
+{
+ size_t count = 0;
+ for (size_t i = 0; i < kInternalThemeCount; i++) {
+ if (kThemes[i].visible)
+ count++;
+ }
+ return count;
+}
+
+const TFTThemeDef &getVisibleThemeByIndex(size_t visibleIndex)
+{
+ size_t seen = 0;
+ for (size_t i = 0; i < kInternalThemeCount; i++) {
+ if (!kThemes[i].visible)
+ continue;
+ if (seen == visibleIndex)
+ return kThemes[i];
+ seen++;
+ }
+ // Fallback: return first theme (never trust a bad index).
+ return kThemes[0];
+}
+
+size_t getActiveVisibleThemeIndex()
+{
+ const size_t active = resolveThemeIndex();
+ if (!kThemes[active].visible)
+ return SIZE_MAX;
+ size_t visibleIdx = 0;
+ for (size_t i = 0; i < active; i++) {
+ if (kThemes[i].visible)
+ visibleIdx++;
+ }
+ return visibleIdx;
+}
+
+uint16_t getThemeHeaderBg()
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+#ifdef TFT_HEADER_BG_COLOR_OVERRIDE
+ return TFT_HEADER_BG_COLOR_OVERRIDE;
+#else
+ return kThemes[resolveThemeIndex()].roles[static_cast(TFTColorRole::HeaderBackground)].onColor;
+#endif
+#else
+ return TFTPalette::DarkGray;
+#endif
+}
+
+uint16_t getThemeHeaderText()
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+#ifdef TFT_HEADER_TITLE_COLOR_OVERRIDE
+ return TFT_HEADER_TITLE_COLOR_OVERRIDE;
+#else
+ return kThemes[resolveThemeIndex()].roles[static_cast(TFTColorRole::HeaderTitle)].offColor;
+#endif
+#else
+ return TFTPalette::White;
+#endif
+}
+
+uint16_t getThemeHeaderStatus()
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+#ifdef TFT_HEADER_STATUS_COLOR_OVERRIDE
+ return TFT_HEADER_STATUS_COLOR_OVERRIDE;
+#else
+ return kThemes[resolveThemeIndex()].roles[static_cast(TFTColorRole::HeaderStatus)].offColor;
+#endif
+#else
+ return TFTPalette::White;
+#endif
+}
+
+uint16_t getThemeBodyBg()
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+ return kThemes[resolveThemeIndex()].roles[static_cast(TFTColorRole::FrameMono)].onColor;
+#else
+ return TFTPalette::Black;
+#endif
+}
+
+uint16_t getThemeBodyFg()
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+ return kThemes[resolveThemeIndex()].roles[static_cast(TFTColorRole::FrameMono)].offColor;
+#else
+ return TFTPalette::White;
+#endif
+}
+
+bool isThemeFullFrameInvert()
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+ return kThemes[resolveThemeIndex()].fullFrameInvert;
+#else
+ return false;
+#endif
+}
+
+uint16_t getThemeBatteryFillColor(int batteryPercent)
+{
+ const TFTThemeDef &theme = kThemes[resolveThemeIndex()];
+ if (batteryPercent <= 20) {
+ return theme.batteryFillBad;
+ }
+ if (batteryPercent <= 50) {
+ return theme.batteryFillMedium;
+ }
+ return theme.batteryFillGood;
+}
+
+void loadThemeDefaults()
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+ const TFTThemeDef &theme = kThemes[resolveThemeIndex()];
+ for (uint8_t i = 0; i < static_cast(TFTColorRole::Count); i++) {
+ roleColors[i].onColorBe = toBe565(theme.roles[i].onColor);
+ roleColors[i].offColorBe = toBe565(theme.roles[i].offColor);
+ }
+#endif
+}
+
+// Role color assignment with theme-aware transforms
+
+void setTFTColorRole(TFTColorRole role, uint16_t onColor, uint16_t offColor)
+{
+#if !GRAPHICS_TFT_COLORING_ENABLED
+ return;
+#endif
+
+ const uint8_t index = static_cast(role);
+ if (index >= static_cast(TFTColorRole::Count)) {
+ return;
+ }
+
+ const uint32_t themeId = uiconfig.screen_rgb_color & 0x1F;
+ const bool isHighlightRole = (role == TFTColorRole::FavoriteNode || role == TFTColorRole::FavoriteNodeBGHighlight);
+ const bool isBodyRole = !isHighlightRole && isBodyColorRole(role);
+
+ // Classic monochrome themes collapse all non-black accents into one tone.
+ if (isMonochromeTheme(themeId)) {
+ if (onColor != TFTPalette::Black) {
+ onColor = getMonochromeAccent(themeId);
+ }
+ } else {
+ switch (themeId) {
+ case ThemeID::DefaultLight:
+ if (isHighlightRole) {
+ // High-contrast highlight chips on light UI.
+ onColor = TFTPalette::Black;
+ offColor = TFTPalette::Yellow;
+ } else if (isBodyRole) {
+ // Invert body colors for readability on white frames.
+ if (offColor == TFTPalette::Black && role != TFTColorRole::ActionMenuTitle) {
+ offColor = TFTPalette::White;
+ }
+ replaceColor(onColor, TFTPalette::White, TFTPalette::Black);
+ }
+ break;
+ case ThemeID::Christmas:
+ if (isHighlightRole || isBodyRole) {
+ replaceColor(onColor, TFTPalette::Yellow, TFTPalette::Gold);
+ replaceColor(offColor, TFTPalette::Black, TFTPalette::Pine);
+ }
+ break;
+ case ThemeID::Pink:
+ if (isHighlightRole) {
+ onColor = TFTPalette::Black;
+ offColor = TFTPalette::HotPink;
+ } else if (isBodyRole) {
+ replaceColor(offColor, TFTPalette::Black, TFTPalette::PalePink);
+ replaceColor(onColor, TFTPalette::White, TFTPalette::Black);
+ replaceColor(onColor, TFTPalette::Yellow, TFTPalette::DeepPink);
+ }
+ break;
+ case ThemeID::Creamsicle:
+ if (isHighlightRole) {
+ onColor = TFTPalette::Black;
+ offColor = TFTPalette::CreamOrange;
+ } else if (isBodyRole) {
+ replaceColor(offColor, TFTPalette::Black, TFTPalette::Cream);
+ replaceColor(onColor, TFTPalette::White, TFTPalette::Black);
+ replaceColor(onColor, TFTPalette::Yellow, TFTPalette::DeepOrange);
+ }
+ break;
+ case ThemeID::Blue:
+ if (isHighlightRole || isBodyRole) {
+ replaceColor(onColor, TFTPalette::Yellow, TFTPalette::SkyBlue);
+ replaceColor(offColor, TFTPalette::Black, TFTPalette::Navy);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ roleColors[index].onColorBe = toBe565(onColor);
+ roleColors[index].offColorBe = toBe565(offColor);
+}
+
+// Region registration
+
+void registerTFTColorRegion(TFTColorRole role, int16_t x, int16_t y, int16_t width, int16_t height)
+{
+#if !GRAPHICS_TFT_COLORING_ENABLED
+ return;
+#endif
+
+ const uint8_t roleIndex = static_cast(role);
+ if (roleIndex >= static_cast(TFTColorRole::Count)) {
+ return;
+ }
+
+ if (!normalizeRegion(x, y, width, height)) {
+ return;
+ }
+
+ const TFTRoleColorsBe &colors = roleColors[roleIndex];
+ appendColorRegion(x, y, width, height, colors.onColorBe, colors.offColorBe);
+}
+
+void setAndRegisterTFTColorRole(TFTColorRole role, uint16_t onColor, uint16_t offColor, int16_t x, int16_t y, int16_t width,
+ int16_t height)
+{
+#if !GRAPHICS_TFT_COLORING_ENABLED
+ (void)role;
+ (void)onColor;
+ (void)offColor;
+ (void)x;
+ (void)y;
+ (void)width;
+ (void)height;
+ return;
+#else
+ setTFTColorRole(role, onColor, offColor);
+ registerTFTColorRegion(role, x, y, width, height);
+#endif
+}
+
+void registerTFTColorRegionDirect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t onColor, uint16_t offColor)
+{
+#if !GRAPHICS_TFT_COLORING_ENABLED
+ return;
+#endif
+
+ if (!normalizeRegion(x, y, width, height))
+ return;
+
+ appendColorRegion(x, y, width, height, toBe565(onColor), toBe565(offColor));
+}
+
+void registerTFTActionMenuRegions(int16_t boxLeft, int16_t boxTop, int16_t boxWidth, int16_t boxHeight)
+{
+#if !GRAPHICS_TFT_COLORING_ENABLED
+ (void)boxLeft;
+ (void)boxTop;
+ (void)boxWidth;
+ (void)boxHeight;
+ return;
+#else
+ // Use theme-appropriate menu colors.
+ const TFTThemeDef &theme = kThemes[resolveThemeIndex()];
+ const TFTThemeRoleColor &menuBody = theme.roles[static_cast(TFTColorRole::ActionMenuBody)];
+ const TFTThemeRoleColor &menuBorder = theme.roles[static_cast(TFTColorRole::ActionMenuBorder)];
+
+ // Fill role includes a 1px shadow guard so stale frame edges are overwritten uniformly.
+ setAndRegisterTFTColorRole(TFTColorRole::ActionMenuBody, menuBody.onColor, menuBody.offColor, boxLeft - 1, boxTop - 1,
+ boxWidth + 2, boxHeight + 2);
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, boxLeft, boxTop - 2, boxWidth, 1);
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, boxLeft, boxTop + boxHeight + 1, boxWidth, 1);
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, boxLeft - 2, boxTop, 1, boxHeight);
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, boxLeft + boxWidth + 1, boxTop, 1, boxHeight);
+
+ setAndRegisterTFTColorRole(TFTColorRole::ActionMenuBorder, menuBorder.onColor, menuBorder.offColor, boxLeft, boxTop, boxWidth,
+ 1);
+ registerTFTColorRegion(TFTColorRole::ActionMenuBorder, boxLeft, boxTop + boxHeight - 1, boxWidth, 1);
+ registerTFTColorRegion(TFTColorRole::ActionMenuBorder, boxLeft, boxTop, 1, boxHeight);
+ registerTFTColorRegion(TFTColorRole::ActionMenuBorder, boxLeft + boxWidth - 1, boxTop, 1, boxHeight);
+#endif
+}
+
+// Frame signature & utilities
+
+uint32_t getTFTColorFrameSignature()
+{
+#if !GRAPHICS_TFT_COLORING_ENABLED
+ return 0;
+#else
+ uint32_t hash = kFnv1aOffsetBasis;
+ hash = fnv1aAppendByte(hash, colorRegionCount);
+ for (uint8_t i = 0; i < colorRegionCount; i++) {
+ const TFTColorRegion &r = colorRegions[i];
+ hash = fnv1aAppendU16(hash, static_cast(r.x));
+ hash = fnv1aAppendU16(hash, static_cast(r.y));
+ hash = fnv1aAppendU16(hash, static_cast(r.width));
+ hash = fnv1aAppendU16(hash, static_cast(r.height));
+ hash = fnv1aAppendU16(hash, r.onColorBe);
+ hash = fnv1aAppendU16(hash, r.offColorBe);
+ }
+
+ return hash;
+#endif
+}
+
+uint8_t getTFTColorRegionCount()
+{
+#if !GRAPHICS_TFT_COLORING_ENABLED
+ return 0;
+#else
+ return colorRegionCount;
+#endif
+}
+
+void clearTFTColorRegions()
+{
+ for (uint8_t i = 0; i < colorRegionCount; i++) {
+ colorRegions[i].enabled = false;
+ }
+ if (colorRegionCount < MAX_TFT_COLOR_REGIONS) {
+ colorRegions[colorRegionCount].enabled = false;
+ }
+ colorRegionCount = 0;
+}
+
+uint16_t resolveTFTColorPixel(int16_t x, int16_t y, bool isset, uint16_t defaultOnColor, uint16_t defaultOffColor)
+{
+ for (int i = static_cast(colorRegionCount) - 1; i >= 0; i--) {
+ const TFTColorRegion &r = colorRegions[i];
+ if (x >= r.x && x < r.x + r.width && y >= r.y && y < r.y + r.height) {
+ return isset ? r.onColorBe : r.offColorBe;
+ }
+ }
+ return isset ? defaultOnColor : defaultOffColor;
+}
+
+uint16_t resolveTFTOffColorAt(int16_t x, int16_t y, uint16_t defaultOffColor)
+{
+#if !GRAPHICS_TFT_COLORING_ENABLED
+ (void)x;
+ (void)y;
+ return defaultOffColor;
+#else
+ const uint16_t defaultOffBe = toBe565(defaultOffColor);
+ const uint16_t sampledBe = resolveTFTColorPixel(x, y, false, defaultOffBe, defaultOffBe);
+ return static_cast((sampledBe >> 8) | (sampledBe << 8));
+#endif
+}
+
+} // namespace graphics
diff --git a/src/graphics/TFTColorRegions.h b/src/graphics/TFTColorRegions.h
new file mode 100644
index 000000000..fd35bdb1a
--- /dev/null
+++ b/src/graphics/TFTColorRegions.h
@@ -0,0 +1,163 @@
+#pragma once
+
+#include "configuration.h"
+#include
+
+namespace graphics
+{
+
+struct TFTColorRegion {
+ int16_t x;
+ int16_t y;
+ int16_t width;
+ int16_t height;
+ uint16_t onColorBe;
+ uint16_t offColorBe;
+ // Required by ST7789 driver: it scans until the first disabled entry.
+ bool enabled = false;
+};
+
+static constexpr size_t MAX_TFT_COLOR_REGIONS = 48;
+extern TFTColorRegion colorRegions[MAX_TFT_COLOR_REGIONS];
+
+enum class TFTColorRole : uint8_t {
+ HeaderBackground = 0,
+ HeaderTitle,
+ HeaderStatus,
+ SignalBars,
+ ConnectionIcon,
+ UtilizationFill,
+ FavoriteNode,
+ ActionMenuBorder,
+ ActionMenuBody,
+ ActionMenuTitle,
+ FrameMono,
+ BootSplash,
+ FavoriteNodeBGHighlight,
+ NavigationBar,
+ NavigationArrow,
+ Count
+};
+
+#if HAS_TFT || defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
+ defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
+ defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR)
+#define GRAPHICS_TFT_COLORING_ENABLED 1
+#else
+#define GRAPHICS_TFT_COLORING_ENABLED 0
+#endif
+
+static constexpr bool kTFTColoringEnabled = GRAPHICS_TFT_COLORING_ENABLED != 0;
+constexpr bool isTFTColoringEnabled()
+{
+ return kTFTColoringEnabled;
+}
+
+void setTFTColorRole(TFTColorRole role, uint16_t onColor, uint16_t offColor);
+void registerTFTColorRegion(TFTColorRole role, int16_t x, int16_t y, int16_t width, int16_t height);
+// Convenience helper for the common "set role then register one region" flow.
+void setAndRegisterTFTColorRole(TFTColorRole role, uint16_t onColor, uint16_t offColor, int16_t x, int16_t y, int16_t width,
+ int16_t height);
+// Register a region using explicit colors (no role lookup). Use when the
+// color comes from a theme field rather than a role (e.g. battery fill).
+void registerTFTColorRegionDirect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t onColor, uint16_t offColor);
+void registerTFTActionMenuRegions(int16_t boxLeft, int16_t boxTop, int16_t boxWidth, int16_t boxHeight);
+uint32_t getTFTColorFrameSignature();
+uint8_t getTFTColorRegionCount();
+void clearTFTColorRegions();
+uint16_t resolveTFTColorPixel(int16_t x, int16_t y, bool isset, uint16_t defaultOnColor, uint16_t defaultOffColor);
+// Resolve effective region-mapped OFF color at a coordinate in native-endian RGB565.
+uint16_t resolveTFTOffColorAt(int16_t x, int16_t y, uint16_t defaultOffColor);
+
+// -- Theme engine ------------------------------------------------------
+// Each theme has four fields that work together:
+//
+// id - ThemeID:: constant, used for in-code references.
+// name - human-readable label shown in the theme picker.
+// uniqueIdentifier - the stable numeric value persisted to
+// uiconfig.screen_rgb_color and restored at boot.
+// This is a CONTRACT with saved configs on disk - once
+// assigned, never reuse or renumber, even if the theme is
+// deleted or the kThemes[] array is reordered.
+// visible - controls whether a theme appears in the picker menu.
+// Hidden themes can still be restored and applied if their
+// uniqueIdentifier is persisted.
+//
+// Display order in the menu is controlled by kThemes[] array order among
+// themes where visible == true, NOT by any numeric value above.
+//
+// To add a new theme:
+// 1. Add a unique constant in ThemeID below (next unused value).
+// 2. Add a kThemes[] entry at the desired menu position, with a unique
+// uniqueIdentifier that has never been used by any prior theme.
+// 3. Set visible=true if it should appear in the picker.
+//
+// To retire a theme without breaking saved configs:
+// - Preferred: keep the entry and set visible=false so existing saved
+// uniqueIdentifier values still resolve to the same theme.
+// - If you remove the entry, resolveThemeIndex() falls back to DefaultDark
+// when the persisted uniqueIdentifier no longer matches any theme.
+// - Do NOT reuse a retired uniqueIdentifier for a future theme.
+namespace ThemeID
+{
+constexpr uint32_t DefaultDark = 0;
+constexpr uint32_t DefaultLight = 1;
+constexpr uint32_t Christmas = 2;
+constexpr uint32_t Pink = 3;
+constexpr uint32_t Blue = 4;
+constexpr uint32_t Creamsicle = 5;
+constexpr uint32_t MeshtasticGreen = 6;
+constexpr uint32_t ClassicRed = 7;
+constexpr uint32_t MonochromeWhite = 8;
+} // namespace ThemeID
+
+// Per-role color pair stored in native (little-endian) RGB565 format.
+struct TFTThemeRoleColor {
+ uint16_t onColor;
+ uint16_t offColor;
+};
+
+// Complete theme definition.
+struct TFTThemeDef {
+ uint32_t id; // ThemeID constant - in-code identifier for this theme.
+ const char *name; // Human-readable label shown in the theme picker.
+ uint32_t uniqueIdentifier; // Stable persisted value copied into uiconfig.screen_rgb_color.
+ // Never reuse or renumber - see file-level notes above.
+ TFTThemeRoleColor roles[static_cast(TFTColorRole::Count)];
+ uint16_t batteryFillGood;
+ uint16_t batteryFillMedium;
+ uint16_t batteryFillBad;
+ bool fullFrameInvert; // Apply full-frame FrameMono inversion (ST7789 light themes)
+ bool visible; // Show in the theme picker menu. Hidden themes still apply
+ // correctly if their uniqueIdentifier is persisted (dev/legacy themes).
+};
+
+// Count of themes whose .visible flag is true. Use this when building menus.
+size_t getVisibleThemeCount();
+
+// Access the Nth visible theme (0 .. getVisibleThemeCount()-1). Hidden themes
+// are skipped, preserving kThemes[] order among the visible entries.
+const TFTThemeDef &getVisibleThemeByIndex(size_t visibleIndex);
+
+// Return the theme that matches uiconfig.screen_rgb_color (falls back to Dark).
+const TFTThemeDef &getActiveTheme();
+
+// Return the visible-theme index for the currently active theme, or SIZE_MAX
+// if the active theme is hidden (so menus can show "no selection").
+size_t getActiveVisibleThemeIndex();
+
+// Convenience accessors - safe to call even when coloring is compiled out.
+uint16_t getThemeHeaderBg();
+uint16_t getThemeHeaderText();
+uint16_t getThemeHeaderStatus();
+uint16_t getThemeBodyBg();
+uint16_t getThemeBodyFg();
+bool isThemeFullFrameInvert();
+uint16_t getThemeBatteryFillColor(int batteryPercent);
+
+// Reinitialise default roleColors from the active theme. Call after a
+// theme change so that any role registered without a prior setTFTColorRole()
+// picks up theme-appropriate defaults.
+void loadThemeDefaults();
+
+} // namespace graphics
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index bc680dd1b..1b74fa16a 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -1220,7 +1220,9 @@ static LGFX *tft = nullptr;
#endif
#include "SPILock.h"
+#include "TFTColorRegions.h"
#include "TFTDisplay.h"
+#include "TFTPalette.h"
#include
#ifdef UNPHONE
@@ -1230,6 +1232,25 @@ extern unPhone unphone;
GpioPin *TFTDisplay::backlightEnable = NULL;
+namespace
+{
+static constexpr uint8_t kFullRepaintChunkRows = 8;
+
+static inline uint16_t getThemeDefaultOnColor()
+{
+ return graphics::TFTPalette::White;
+}
+
+static inline uint16_t getThemeDefaultOffColor()
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+ return graphics::getThemeBodyBg();
+#else
+ return TFT_BLACK;
+#endif
+}
+} // namespace
+
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
{
LOG_DEBUG("TFTDisplay!");
@@ -1269,14 +1290,15 @@ TFTDisplay::~TFTDisplay()
free(linePixelBuffer);
linePixelBuffer = nullptr;
}
+ if (repaintChunkBuffer != nullptr) {
+ free(repaintChunkBuffer);
+ repaintChunkBuffer = nullptr;
+ }
}
// Write the buffer to the display memory
void TFTDisplay::display(bool fromBlank)
{
- if (fromBlank)
- tft->fillScreen(TFT_BLACK);
-
concurrency::LockGuard g(spiLock);
uint32_t x, y;
@@ -1285,12 +1307,70 @@ void TFTDisplay::display(bool fromBlank)
uint32_t x_FirstPixelUpdate;
uint32_t x_LastPixelUpdate;
bool isset, dblbuf_isset;
- uint16_t colorTftMesh, colorTftBlack;
+ uint16_t colorTftWhite, colorTftBlack;
bool somethingChanged = false;
- // Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
- colorTftMesh = __builtin_bswap16(TFT_MESH);
- colorTftBlack = __builtin_bswap16(TFT_BLACK);
+ // Theme defaults for non-role pixels.
+ const uint16_t defaultOnColor = getThemeDefaultOnColor();
+ const uint16_t defaultOffColor = getThemeDefaultOffColor();
+ static uint16_t lastDefaultOnColor = 0;
+ static uint16_t lastDefaultOffColor = 0;
+ static bool haveLastDefaults = false;
+ const bool themeDefaultsChanged =
+ !haveLastDefaults || (defaultOnColor != lastDefaultOnColor) || (defaultOffColor != lastDefaultOffColor);
+ const bool forceFullRepaint = fromBlank || themeDefaultsChanged;
+
+ // If theme defaults changed, reset panel background immediately so stale pixels don't linger.
+ if (forceFullRepaint) {
+ tft->fillScreen(defaultOffColor);
+ }
+
+ colorTftWhite = (defaultOnColor >> 8) | ((defaultOnColor & 0xFF) << 8);
+ colorTftBlack = (defaultOffColor >> 8) | ((defaultOffColor & 0xFF) << 8);
+
+#if GRAPHICS_TFT_COLORING_ENABLED
+ static uint32_t lastColorFrameSignature = 0;
+ const bool hasColorRegions = graphics::getTFTColorRegionCount() > 0;
+ const uint32_t colorFrameSignature = graphics::getTFTColorFrameSignature();
+ const bool forceFullColorRepaint = forceFullRepaint || (colorFrameSignature != lastColorFrameSignature);
+
+ // When region roles/layout changed, color can differ even with identical monochrome glyph bits.
+ // Repaint full frame only for those frames, then return to diff-based updates.
+ if (forceFullColorRepaint) {
+ for (uint32_t yStart = 0; yStart < displayHeight; yStart += kFullRepaintChunkRows) {
+ const uint32_t rowsThisChunk = min(kFullRepaintChunkRows, displayHeight - yStart);
+ for (uint32_t row = 0; row < rowsThisChunk; row++) {
+ y = yStart + row;
+ y_byteIndex = (y / 8) * displayWidth;
+ y_byteMask = (1 << (y & 7));
+
+ uint16_t *chunkRow = repaintChunkBuffer + (row * displayWidth);
+ for (x = 0; x < displayWidth; x++) {
+ isset = (buffer[x + y_byteIndex] & y_byteMask) != 0;
+ if (hasColorRegions) {
+ chunkRow[x] = graphics::resolveTFTColorPixel(static_cast(x), static_cast(y), isset,
+ colorTftWhite, colorTftBlack);
+ } else {
+ chunkRow[x] = isset ? colorTftWhite : colorTftBlack;
+ }
+ }
+ }
+#if defined(HACKADAY_COMMUNICATOR)
+ tft->draw16bitBeRGBBitmap(0, yStart, repaintChunkBuffer, displayWidth, rowsThisChunk);
+#else
+ tft->pushImage(0, yStart, displayWidth, rowsThisChunk, repaintChunkBuffer);
+#endif
+ }
+
+ memcpy(buffer_back, buffer, displayBufferSize);
+ lastColorFrameSignature = colorFrameSignature;
+ haveLastDefaults = true;
+ lastDefaultOnColor = defaultOnColor;
+ lastDefaultOffColor = defaultOffColor;
+ graphics::clearTFTColorRegions();
+ return;
+ }
+#endif
y = 0;
while (y < displayHeight) {
@@ -1299,7 +1379,7 @@ void TFTDisplay::display(bool fromBlank)
// Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas.
if (y_byteMask == 1) {
- if (!fromBlank) {
+ if (!forceFullRepaint) {
for (x = 0; x < displayWidth; x++) {
if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex])
break;
@@ -1317,13 +1397,14 @@ void TFTDisplay::display(bool fromBlank)
}
}
- // Step 2: Scan each of the 8 rows individually. Find the first pixel in each row that needs updating
- for (x_FirstPixelUpdate = 0; x_FirstPixelUpdate < displayWidth; x_FirstPixelUpdate++) {
- isset = buffer[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
+ // Step 2: Scan this row for changed span (first and last changed pixel).
+ uint32_t x_FirstChanged = 0;
+ for (x_FirstChanged = 0; x_FirstChanged < displayWidth; x_FirstChanged++) {
+ isset = buffer[x_FirstChanged + y_byteIndex] & y_byteMask;
- if (!fromBlank) {
+ if (!forceFullRepaint) {
// get src pixel in the page based ordering the OLED lib uses
- dblbuf_isset = buffer_back[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
+ dblbuf_isset = buffer_back[x_FirstChanged + y_byteIndex] & y_byteMask;
if (isset != dblbuf_isset) {
break;
}
@@ -1333,37 +1414,45 @@ void TFTDisplay::display(bool fromBlank)
}
// Did we find a pixel that needs updating on this row?
- if (x_FirstPixelUpdate < displayWidth) {
- // Align the first pixel for update to an even number so the total alignment of
- // the data will be at 32-bit boundary, which is required by GDMA SPI transfers.
- x_FirstPixelUpdate &= ~1;
-
- // Step 3a: copy rest of the pixels in this row into the pixel line buffer,
- // while also recording the last pixel in the row that needs updating.
- // Since the first changed pixel will be looked up, the x_LastPixelUpdate will be set.
- for (x = x_FirstPixelUpdate; x < displayWidth; x++) {
- isset = buffer[x + y_byteIndex] & y_byteMask;
- linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack;
-
- if (!fromBlank) {
- dblbuf_isset = buffer_back[x + y_byteIndex] & y_byteMask;
+ if (x_FirstChanged < displayWidth) {
+ uint32_t x_LastChanged = displayWidth - 1;
+ while (x_LastChanged > x_FirstChanged) {
+ isset = buffer[x_LastChanged + y_byteIndex] & y_byteMask;
+ if (!forceFullRepaint) {
+ dblbuf_isset = buffer_back[x_LastChanged + y_byteIndex] & y_byteMask;
if (isset != dblbuf_isset) {
- x_LastPixelUpdate = x;
+ break;
}
} else if (isset) {
- x_LastPixelUpdate = x;
+ break;
}
+ x_LastChanged--;
}
- // Step 3b: Round up the last pixel to odd number to maintain 32-bit alignment for SPIs.
- // Most displays will have even number of pixels in a row -- this will be in bounds
- // of the displayWidth. (Hopefully odd displays will just ignore that extra pixel.)
- x_LastPixelUpdate |= 1;
- // Ensure the last pixel index does not exceed the display width.
+
+ // Align the first pixel for update to an even number so the total alignment of
+ // the data will be at 32-bit boundary, which is required by GDMA SPI transfers.
+ x_FirstPixelUpdate = x_FirstChanged & ~1U;
+ x_LastPixelUpdate = x_LastChanged | 1U;
if (x_LastPixelUpdate >= displayWidth) {
x_LastPixelUpdate = displayWidth - 1;
}
int y_offset = 0;
+ // Step 3: Copy only the changed span into the pixel line buffer.
+ for (x = x_FirstPixelUpdate; x <= x_LastPixelUpdate; x++) {
+ isset = buffer[x + y_byteIndex] & y_byteMask;
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (hasColorRegions) {
+ linePixelBuffer[x] = graphics::resolveTFTColorPixel(static_cast(x), static_cast(y), isset,
+ colorTftWhite, colorTftBlack);
+ } else {
+ linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
+ }
+#else
+ linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
+#endif
+ }
+
#if defined(CO5300_CS)
uint8_t lines_updated = 2;
if (y % 2 == 0) {
@@ -1372,7 +1461,16 @@ void TFTDisplay::display(bool fromBlank)
uint32_t bufferIndex = 1;
for (x = x_FirstPixelUpdate; x < x_LastPixelUpdate; x++) {
isset = buffer[x + y_byteIndex] & y_byteMask;
- linePixelBuffer[bufferIndex++ + x_LastPixelUpdate] = isset ? colorTftMesh : colorTftBlack;
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (hasColorRegions) {
+ linePixelBuffer[x] = graphics::resolveTFTColorPixel(static_cast(x), static_cast(y),
+ isset, colorTftWhite, colorTftBlack);
+ } else {
+ linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
+ }
+#else
+ linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
+#endif
}
} else {
y_offset = -1;
@@ -1383,7 +1481,16 @@ void TFTDisplay::display(bool fromBlank)
uint32_t bufferIndex = 0;
for (x = x_FirstPixelUpdate; x < x_LastPixelUpdate; x++) {
isset = buffer[x + y_byteIndex] & y_byteMask;
- linePixelBuffer[bufferIndex++ + x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack;
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (hasColorRegions) {
+ linePixelBuffer[x] = graphics::resolveTFTColorPixel(static_cast(x), static_cast(y),
+ isset, colorTftWhite, colorTftBlack);
+ } else {
+ linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
+ }
+#else
+ linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
+#endif
}
}
#else
@@ -1396,8 +1503,8 @@ void TFTDisplay::display(bool fromBlank)
#else
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
- tft->pushRect(x_FirstPixelUpdate, y + y_offset, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), lines_updated,
- &linePixelBuffer[x_FirstPixelUpdate]);
+ tft->pushImage(x_FirstPixelUpdate, y + y_offset, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), lines_updated,
+ &linePixelBuffer[x_FirstPixelUpdate]);
#endif
somethingChanged = true;
}
@@ -1406,6 +1513,14 @@ void TFTDisplay::display(bool fromBlank)
// Copy the Buffer to the Back Buffer
if (somethingChanged)
memcpy(buffer_back, buffer, displayBufferSize);
+
+#if GRAPHICS_TFT_COLORING_ENABLED
+ lastColorFrameSignature = colorFrameSignature;
+#endif
+ haveLastDefaults = true;
+ lastDefaultOnColor = defaultOnColor;
+ lastDefaultOffColor = defaultOffColor;
+ graphics::clearTFTColorRegions();
}
void TFTDisplay::sdlLoop()
@@ -1619,7 +1734,7 @@ bool TFTDisplay::connect()
#else
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
#endif
- tft->fillScreen(TFT_BLACK);
+ tft->fillScreen(getThemeDefaultOffColor());
if (this->linePixelBuffer == NULL) {
#if defined(CO5300_CS)
@@ -1633,6 +1748,14 @@ bool TFTDisplay::connect()
return false;
}
}
+ if (this->repaintChunkBuffer == NULL) {
+ this->repaintChunkBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth * kFullRepaintChunkRows);
+
+ if (!this->repaintChunkBuffer) {
+ LOG_ERROR("Not enough memory to create TFT repaint chunk buffer\n");
+ return false;
+ }
+ }
return true;
}
diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h
index a64922d23..2c86f05d2 100644
--- a/src/graphics/TFTDisplay.h
+++ b/src/graphics/TFTDisplay.h
@@ -63,4 +63,5 @@ class TFTDisplay : public OLEDDisplay
virtual bool connect() override;
uint16_t *linePixelBuffer = nullptr;
-};
\ No newline at end of file
+ uint16_t *repaintChunkBuffer = nullptr;
+};
diff --git a/src/graphics/TFTPalette.h b/src/graphics/TFTPalette.h
new file mode 100644
index 000000000..516a9f057
--- /dev/null
+++ b/src/graphics/TFTPalette.h
@@ -0,0 +1,70 @@
+#pragma once
+
+#include
+
+namespace graphics
+{
+namespace TFTPalette
+{
+
+constexpr uint16_t rgb565(uint8_t red, uint8_t green, uint8_t blue)
+{
+ return static_cast(((red & 0xF8) << 8) | ((green & 0xFC) << 3) | ((blue & 0xF8) >> 3));
+}
+
+constexpr uint16_t Black = 0x0000;
+constexpr uint16_t White = 0xFFFF;
+constexpr uint16_t DarkGray = 0x4208;
+constexpr uint16_t Gray = 0x8410;
+constexpr uint16_t LightGray = 0xC618;
+
+constexpr uint16_t Red = rgb565(255, 0, 0);
+constexpr uint16_t Green = rgb565(0, 255, 0);
+constexpr uint16_t Blue = rgb565(0, 130, 252);
+constexpr uint16_t Yellow = rgb565(255, 255, 0);
+constexpr uint16_t Orange = rgb565(255, 165, 0);
+constexpr uint16_t Cyan = rgb565(0, 255, 255);
+constexpr uint16_t Magenta = rgb565(255, 0, 255);
+
+constexpr uint16_t Good = Green;
+constexpr uint16_t Medium = Yellow;
+constexpr uint16_t Bad = Red;
+
+// Christmas / seasonal accent colors
+constexpr uint16_t ChristmasRed = rgb565(178, 34, 34);
+constexpr uint16_t ChristmasGreen = rgb565(0, 128, 0);
+constexpr uint16_t Gold = rgb565(255, 215, 0);
+constexpr uint16_t Pine = rgb565(15, 35, 10);
+
+// Pink theme colors (light variant)
+constexpr uint16_t HotPink = rgb565(255, 105, 180);
+constexpr uint16_t PalePink = rgb565(255, 228, 235);
+constexpr uint16_t DeepPink = rgb565(200, 50, 120);
+
+// Blue theme colors (dark variant)
+constexpr uint16_t SkyBlue = rgb565(100, 180, 255);
+constexpr uint16_t Navy = rgb565(15, 15, 50);
+constexpr uint16_t DeepBlue = rgb565(30, 60, 120);
+
+// Creamsicle theme colors (light variant)
+constexpr uint16_t CreamOrange = rgb565(255, 140, 50);
+constexpr uint16_t DeepOrange = rgb565(220, 100, 20);
+constexpr uint16_t Cream = rgb565(255, 248, 235);
+
+// Classic monochrome theme accent colors (single-color-on-black themes)
+constexpr uint16_t MeshtasticGreen = rgb565(0x67, 0xEA, 0x94);
+constexpr uint16_t ClassicRed = rgb565(255, 64, 64);
+// Monochrome White reuses TFTPalette::White above.
+
+// Fast contrast picker for monochrome glyph overlays on arbitrary RGB565 backgrounds.
+// Uses channel-sum brightness approximation to keep code size small.
+constexpr uint16_t pickReadableMonoFg(uint16_t backgroundColor)
+{
+ const uint16_t r = (backgroundColor >> 11) & 0x1F;
+ const uint16_t g = (backgroundColor >> 5) & 0x3F;
+ const uint16_t b = backgroundColor & 0x1F;
+ return ((r + g + b) >= 70) ? DarkGray : White;
+}
+
+} // namespace TFTPalette
+} // namespace graphics
diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
index 66bbe1bfe..5045174be 100644
--- a/src/graphics/draw/ClockRenderer.cpp
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -145,7 +145,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
// === Set Title, Blank for Clock
const char *titleStr = "";
// === Header ===
- graphics::drawCommonHeader(display, x, y, titleStr, true, true);
+ graphics::drawCommonHeader(display, x, y, titleStr, true, true, true);
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
char timeString[16];
@@ -293,11 +293,15 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
// Draw an analog clock
void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
+#if GRAPHICS_TFT_COLORING_ENABLED
+ // Clear previous frame pixels so moving hands don't leave stale artifacts on TFT light theme.
+ display->clear();
+#endif
display->setTextAlignment(TEXT_ALIGN_LEFT);
// === Set Title, Blank for Clock
const char *titleStr = "";
// === Header ===
- graphics::drawCommonHeader(display, x, y, titleStr, true, true);
+ graphics::drawCommonHeader(display, x, y, titleStr, true, true, true);
// clock face center coordinates
int16_t centerX = display->getWidth() / 2;
@@ -478,4 +482,4 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
} // namespace ClockRenderer
} // namespace graphics
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/draw/CompassRenderer.cpp b/src/graphics/draw/CompassRenderer.cpp
index fe54d68e7..1ee02194c 100644
--- a/src/graphics/draw/CompassRenderer.cpp
+++ b/src/graphics/draw/CompassRenderer.cpp
@@ -9,113 +9,61 @@ namespace graphics
{
namespace CompassRenderer
{
-
-// Point helper class for compass calculations
-struct Point {
- float x, y;
- Point(float x, float y) : x(x), y(y) {}
-
- void rotate(float angle)
- {
- float cos_a = cosf(angle);
- float sin_a = sinf(angle);
- float new_x = x * cos_a - y * sin_a;
- float new_y = x * sin_a + y * cos_a;
- x = new_x;
- y = new_y;
- }
-
- void scale(float factor)
- {
- x *= factor;
- y *= factor;
- }
-
- void translate(float dx, float dy)
- {
- x += dx;
- y += dy;
- }
-};
-
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading, int16_t radius)
{
- // Show the compass heading (not implemented in original)
- // This could draw a "N" indicator or north arrow
- // For now, we'll draw a simple north indicator
- // const float radius = 17.0f;
if (currentResolution == ScreenResolution::High) {
radius += 4;
}
- float northX = 0.0f;
- float northY = -radius;
- if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
- const float c = cosf(-myHeading);
- const float s = sinf(-myHeading);
- const float rx = northX * c - northY * s;
- const float ry = northX * s + northY * c;
- northX = rx;
- northY = ry;
- }
- northX += compassX;
- northY += compassY;
+
+ const float northAngle = (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) ? -myHeading : 0.0f;
+ const int16_t nX = compassX + static_cast((radius - 1) * sinf(northAngle));
+ const int16_t nY = compassY - static_cast((radius - 1) * cosf(northAngle));
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
+#if !GRAPHICS_TFT_COLORING_ENABLED
display->setColor(BLACK);
const int16_t nLabelWidth = display->getStringWidth("N");
if (currentResolution == ScreenResolution::High) {
- display->fillRect(northX - 8, northY - 1, nLabelWidth + 3, FONT_HEIGHT_SMALL - 6);
+ display->fillRect(nX - 8, nY - 1, nLabelWidth + 3, FONT_HEIGHT_SMALL - 6);
} else {
- display->fillRect(northX - 4, northY - 1, nLabelWidth + 2, FONT_HEIGHT_SMALL - 6);
+ display->fillRect(nX - 4, nY - 1, nLabelWidth + 2, FONT_HEIGHT_SMALL - 6);
}
- display->setColor(WHITE);
- display->drawString(northX, northY - 3, "N");
-}
-
-void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
-{
- Point tip(0.0f, -0.5f), tail(0.0f, 0.35f); // pointing up initially
- float arrowOffsetX = 0.14f, arrowOffsetY = 0.9f;
- Point leftArrow(tip.x - arrowOffsetX, tip.y + arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y + arrowOffsetY);
-
- Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow};
-
- for (int i = 0; i < 4; i++) {
- arrowPoints[i]->rotate(headingRadian);
- arrowPoints[i]->scale(compassDiam * 0.6);
- arrowPoints[i]->translate(compassX, compassY);
- }
-
-#ifdef USE_EINK
- display->drawTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
-#else
- display->fillTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
#endif
- display->drawTriangle(tip.x, tip.y, leftArrow.x, leftArrow.y, tail.x, tail.y);
+ display->setColor(WHITE);
+ display->drawString(nX, nY - 3, "N");
}
void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, float bearing)
{
- float radians = bearing * DEG_TO_RAD;
+ const float radians = bearing * DEG_TO_RAD;
+ const float sinA = sinf(radians);
+ const float cosA = cosf(radians);
+ const float tipHalf = size * 0.5f;
+ const float lx = -(size / 6.0f);
+ const float ly = size / 4.0f;
+ const float rx = (size / 6.0f);
+ const float ry = size / 4.0f;
+ const float tx = 0.0f;
+ const float ty = size / 4.5f;
- Point tip(0, -size / 2);
- Point left(-size / 6, size / 4);
- Point right(size / 6, size / 4);
- Point tail(0, size / 4.5);
+ const int16_t tipX = static_cast(x + (tipHalf * sinA));
+ const int16_t tipY = static_cast(y - (tipHalf * cosA));
+ const int16_t leftX = static_cast(x + (lx * cosA) - (ly * sinA));
+ const int16_t leftY = static_cast(y + (lx * sinA) + (ly * cosA));
+ const int16_t rightX = static_cast(x + (rx * cosA) - (ry * sinA));
+ const int16_t rightY = static_cast(y + (rx * sinA) + (ry * cosA));
+ const int16_t tailX = static_cast(x + (tx * cosA) - (ty * sinA));
+ const int16_t tailY = static_cast(y + (tx * sinA) + (ty * cosA));
- tip.rotate(radians);
- left.rotate(radians);
- right.rotate(radians);
- tail.rotate(radians);
+ display->fillTriangle(tipX, tipY, leftX, leftY, tailX, tailY);
+ display->fillTriangle(tipX, tipY, rightX, rightY, tailX, tailY);
+}
- tip.translate(x, y);
- left.translate(x, y);
- right.translate(x, y);
- tail.translate(x, y);
-
- display->fillTriangle(tip.x, tip.y, left.x, left.y, tail.x, tail.y);
- display->fillTriangle(tip.x, tip.y, right.x, right.y, tail.x, tail.y);
+void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
+{
+ const int16_t size = static_cast(compassDiam * 0.6f);
+ drawArrowToNode(display, compassX, compassY, size, headingRadian * RAD_TO_DEG);
}
bool getHeadingRadians(double lat, double lon, float &headingRadian)
diff --git a/src/graphics/draw/CompassRenderer.h b/src/graphics/draw/CompassRenderer.h
index d77623847..41adf6e64 100644
--- a/src/graphics/draw/CompassRenderer.h
+++ b/src/graphics/draw/CompassRenderer.h
@@ -1,6 +1,7 @@
#pragma once
#include "graphics/Screen.h"
+#include "mesh/generated/meshtastic/mesh.pb.h"
#include
#include
diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp
index 98e907255..982049b4b 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -11,6 +11,8 @@
#include "gps/RTC.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TFTColorRegions.h"
+#include "graphics/TFTPalette.h"
#include "graphics/TimeFormatters.h"
#include "graphics/images.h"
#include "main.h"
@@ -469,9 +471,11 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
int chUtil_y = getTextPositions(display)[line] + 3;
int chutil_bar_width = (currentResolution == ScreenResolution::High) ? 100 : 50;
+ int chutil_bar_max_fill = chutil_bar_width - 2; // Account for border
int chutil_bar_height = (currentResolution == ScreenResolution::High) ? 12 : 7;
int extraoffset = (currentResolution == ScreenResolution::High) ? 6 : 3;
int chutil_percent = airTime->channelUtilizationPercent();
+ const int raw_chutil_percent = chutil_percent;
int centerofscreen = SCREEN_WIDTH / 2;
int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
@@ -479,7 +483,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->drawString(starting_position, getTextPositions(display)[line], chUtil);
- // Force 56% or higher to show a full 100% bar, text would still show related percent.
+ // Force 61% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) {
chutil_percent = 100;
}
@@ -492,9 +496,9 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
float weight3 = 0.20; // Weight for 40–100%
float totalWeight = weight1 + weight2 + weight3;
- int seg1 = chutil_bar_width * (weight1 / totalWeight);
- int seg2 = chutil_bar_width * (weight2 / totalWeight);
- int seg3 = chutil_bar_width * (weight3 / totalWeight);
+ int seg1 = chutil_bar_max_fill * (weight1 / totalWeight);
+ int seg2 = chutil_bar_max_fill * (weight2 / totalWeight);
+ int seg3 = chutil_bar_max_fill - seg1 - seg2; // Remainder absorbs rounding errors
int fillRight = 0;
@@ -511,7 +515,17 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
// Fill progress
if (fillRight > 0) {
- display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ uint16_t UtilizationFillColor = TFTPalette::Good;
+ if (raw_chutil_percent >= 60) {
+ UtilizationFillColor = TFTPalette::Bad;
+ } else if (raw_chutil_percent >= 35) {
+ UtilizationFillColor = TFTPalette::Medium;
+ }
+ setAndRegisterTFTColorRole(TFTColorRole::UtilizationFill, UtilizationFillColor, TFTPalette::Black,
+ starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
+#endif
+ display->fillRect(starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
}
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
@@ -584,6 +598,17 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
display->setColor(WHITE);
display->drawRect(barX, barY, adjustedBarWidth, barHeight);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ uint16_t UtilizationFillColor = TFTPalette::Good;
+ if (percent >= 80) {
+ UtilizationFillColor = TFTPalette::Bad;
+ } else if (percent >= 60) {
+ UtilizationFillColor = TFTPalette::Medium;
+ }
+ setAndRegisterTFTColorRole(TFTColorRole::UtilizationFill, UtilizationFillColor, TFTPalette::Black, barX + 1, barY + 1,
+ fillWidth - 1, barHeight - 2);
+#endif
+
display->fillRect(barX, barY, fillWidth, barHeight);
display->setColor(WHITE);
#endif
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index e92ba4839..f31cb405b 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -11,6 +11,7 @@
#include "buzz.h"
#include "graphics/Screen.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TFTColorRegions.h"
#include "graphics/draw/MessageRenderer.h"
#include "graphics/draw/UIRenderer.h"
#include "input/RotaryEncoderInterruptImpl1.h"
@@ -30,8 +31,6 @@
#include
#include
-extern uint16_t TFT_MESH;
-
namespace graphics
{
@@ -2028,109 +2027,6 @@ void menuHandler::switchToMUIMenu()
screen->showOverlayBanner(bannerOptions);
}
-void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
-{
- static const ScreenColorOption colorOptions[] = {
- {"Back", OptionsAction::Back},
- {"Default", OptionsAction::Select, ScreenColor(0, 0, 0, true)},
- {"Meshtastic Green", OptionsAction::Select, ScreenColor(0x67, 0xEA, 0x94)},
- {"Yellow", OptionsAction::Select, ScreenColor(255, 255, 128)},
- {"Red", OptionsAction::Select, ScreenColor(255, 64, 64)},
- {"Orange", OptionsAction::Select, ScreenColor(255, 160, 20)},
- {"Purple", OptionsAction::Select, ScreenColor(204, 153, 255)},
- {"Blue", OptionsAction::Select, ScreenColor(0, 0, 255)},
- {"Teal", OptionsAction::Select, ScreenColor(16, 102, 102)},
- {"Cyan", OptionsAction::Select, ScreenColor(0, 255, 255)},
- {"Ice", OptionsAction::Select, ScreenColor(173, 216, 230)},
- {"Pink", OptionsAction::Select, ScreenColor(255, 105, 180)},
- {"White", OptionsAction::Select, ScreenColor(255, 255, 255)},
- {"Gray", OptionsAction::Select, ScreenColor(128, 128, 128)},
- };
-
- constexpr size_t colorCount = sizeof(colorOptions) / sizeof(colorOptions[0]);
- static std::array colorLabels{};
-
- auto bannerOptions = createStaticBannerOptions(
- "Select Screen Color", colorOptions, colorLabels, [display](const ScreenColorOption &option, int) -> void {
- if (option.action == OptionsAction::Back) {
- menuQueue = SystemBaseMenu;
- screen->runNow();
- return;
- }
-
- if (!option.hasValue) {
- return;
- }
-
-#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \
- HAS_TFT || defined(HACKADAY_COMMUNICATOR)
- const ScreenColor &color = option.value;
- if (color.useVariant) {
- LOG_INFO("Setting color to system default or defined variant");
- } else {
- LOG_INFO("Setting color to %s", option.label);
- }
-
- uint8_t r = color.r;
- uint8_t g = color.g;
- uint8_t b = color.b;
-
- display->setColor(BLACK);
- display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- display->setColor(WHITE);
-
- if (color.useVariant || (r == 0 && g == 0 && b == 0)) {
-#ifdef TFT_MESH_OVERRIDE
- TFT_MESH = TFT_MESH_OVERRIDE;
-#else
- TFT_MESH = COLOR565(255, 255, 128);
-#endif
- } else {
- TFT_MESH = COLOR565(r, g, b);
- }
-
-#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
- static_cast(screen->getDisplayDevice())->setRGB(TFT_MESH);
-#endif
-
- screen->setFrames(graphics::Screen::FOCUS_SYSTEM);
- if (color.useVariant || (r == 0 && g == 0 && b == 0)) {
- uiconfig.screen_rgb_color = 0;
- } else {
- uiconfig.screen_rgb_color =
- (static_cast(r) << 16) | (static_cast(g) << 8) | static_cast(b);
- }
- LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
- saveUIConfig();
-#endif
- });
-
- int initialSelection = 0;
- if (uiconfig.screen_rgb_color == 0) {
- initialSelection = 1;
- } else {
- uint32_t currentColor = uiconfig.screen_rgb_color;
- for (size_t i = 0; i < colorCount; ++i) {
- if (!colorOptions[i].hasValue) {
- continue;
- }
- const ScreenColor &color = colorOptions[i].value;
- if (color.useVariant) {
- continue;
- }
- uint32_t encoded =
- (static_cast(color.r) << 16) | (static_cast(color.g) << 8) | static_cast(color.b);
- if (encoded == currentColor) {
- initialSelection = static_cast(i);
- break;
- }
- }
- }
- bannerOptions.InitialSelected = initialSelection;
-
- screen->showOverlayBanner(bannerOptions);
-}
-
void menuHandler::rebootMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
@@ -2318,9 +2214,9 @@ void menuHandler::screenOptionsMenu()
bool hasSupportBrightness = false;
#endif
- enum optionsNumbers { Back, Brightness, ScreenColor, FrameToggles, DisplayUnits, MessageBubbles };
- static const char *optionsArray[6] = {"Back"};
- static int optionsEnumArray[6] = {Back};
+ enum optionsNumbers { Back, Brightness, FrameToggles, DisplayUnits, MessageBubbles, Theme };
+ static const char *optionsArray[7] = {"Back"};
+ static int optionsEnumArray[7] = {Back};
int options = 1;
// Only show brightness for B&W displays
@@ -2329,13 +2225,6 @@ void menuHandler::screenOptionsMenu()
optionsEnumArray[options++] = Brightness;
}
- // Only show screen color for TFT displays
-#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \
- HAS_TFT || defined(HACKADAY_COMMUNICATOR)
- optionsArray[options] = "Screen Color";
- optionsEnumArray[options++] = ScreenColor;
-#endif
-
optionsArray[options] = "Frame Visibility";
optionsEnumArray[options++] = FrameToggles;
@@ -2345,6 +2234,11 @@ void menuHandler::screenOptionsMenu()
optionsArray[options] = "Message Bubbles";
optionsEnumArray[options++] = MessageBubbles;
+#if GRAPHICS_TFT_COLORING_ENABLED
+ optionsArray[options] = "Theme";
+ optionsEnumArray[options++] = Theme;
+#endif
+
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Display Options";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -2354,9 +2248,6 @@ void menuHandler::screenOptionsMenu()
if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::BrightnessPicker;
screen->runNow();
- } else if (selected == ScreenColor) {
- menuHandler::menuQueue = menuHandler::TftColorMenuPicker;
- screen->runNow();
} else if (selected == FrameToggles) {
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
@@ -2366,6 +2257,9 @@ void menuHandler::screenOptionsMenu()
} else if (selected == MessageBubbles) {
menuHandler::menuQueue = menuHandler::MessageBubblesMenu;
screen->runNow();
+ } else if (selected == Theme) {
+ menuHandler::menuQueue = menuHandler::ThemeMenu;
+ screen->runNow();
} else {
menuQueue = SystemBaseMenu;
screen->runNow();
@@ -2649,6 +2543,53 @@ void menuHandler::messageBubblesMenu()
screen->showOverlayBanner(bannerOptions);
}
+void menuHandler::themeMenu()
+{
+ // Build menu dynamically from the theme table.
+ // Only visible themes appear!
+ // Slot budget: 1 for "Back" + up to kMaxThemesInMenu visible themes.
+ // Bump kMaxThemesInMenu if you add more themes than will fit here.
+ constexpr size_t kMaxThemesInMenu = 15;
+ const size_t visibleCount = getVisibleThemeCount();
+ static const char *optionsArray[kMaxThemesInMenu + 1] = {"Back"};
+ const size_t shownCount = (visibleCount < kMaxThemesInMenu) ? visibleCount : kMaxThemesInMenu;
+ const int options = static_cast(shownCount) + 1; // +1 for Back
+
+ for (size_t i = 0; i < shownCount; i++) {
+ optionsArray[i + 1] = getVisibleThemeByIndex(i).name;
+ }
+
+ BannerOverlayOptions bannerOptions;
+ bannerOptions.message = "Theme";
+ bannerOptions.optionsArrayPtr = optionsArray;
+ bannerOptions.optionsCount = options;
+
+ // Highlight the currently active theme (visible index + 1 for the Back
+ // offset). If the active theme is hidden, leave selection on "Back".
+ const size_t activeVisible = getActiveVisibleThemeIndex();
+ bannerOptions.InitialSelected = (activeVisible == SIZE_MAX) ? 0 : static_cast(activeVisible) + 1;
+
+ bannerOptions.bannerCallback = [](int selected) -> void {
+ if (selected == 0) {
+ // Back
+ menuHandler::menuQueue = menuHandler::ScreenOptionsMenu;
+ screen->runNow();
+ } else {
+ // Selection is an index into the VISIBLE themes (1-based, slot 0 is Back).
+ const size_t visibleIdx = static_cast(selected - 1);
+ if (visibleIdx < getVisibleThemeCount()) {
+ // Persist the theme's uniqueIdentifier so boot-time
+ // resolveThemeIndex() can restore this theme on next startup.
+ uiconfig.screen_rgb_color = COLOR565(255, 255, (getVisibleThemeByIndex(visibleIdx).uniqueIdentifier & 0x1F) << 3);
+ loadThemeDefaults();
+ saveUIConfig();
+ screen->runNow();
+ }
+ }
+ };
+ screen->showOverlayBanner(bannerOptions);
+}
+
void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{
if (menuQueue != MenuNone)
@@ -2724,9 +2665,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case MuiPicker:
switchToMUIMenu();
break;
- case TftColorMenuPicker:
- TFTColorPickerMenu(display);
- break;
case BrightnessPicker:
BrightnessPickerMenu();
break;
@@ -2799,6 +2737,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case MessageBubblesMenu:
messageBubblesMenu();
break;
+ case ThemeMenu:
+ themeMenu();
+ break;
}
menuQueue = MenuNone;
}
@@ -2810,4 +2751,4 @@ void menuHandler::saveUIConfig()
} // namespace graphics
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 4a0360412..3ac9e606e 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -30,7 +30,6 @@ class menuHandler
ResetNodeDbMenu,
BuzzerModeMenuPicker,
MuiPicker,
- TftColorMenuPicker,
BrightnessPicker,
RebootMenu,
ShutdownMenu,
@@ -55,7 +54,8 @@ class menuHandler
NodeNameLengthMenu,
FrameToggles,
DisplayUnits,
- MessageBubblesMenu
+ MessageBubblesMenu,
+ ThemeMenu
};
static screenMenus menuQueue;
static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu
@@ -89,7 +89,6 @@ class menuHandler
static void GPSPositionBroadcastMenu();
static void BuzzerModeMenu();
static void switchToMUIMenu();
- static void TFTColorPickerMenu(OLEDDisplay *display);
static void nodeListMenu();
static void resetNodeDBMenu();
static void BrightnessPickerMenu();
@@ -110,6 +109,7 @@ class menuHandler
static void frameTogglesMenu();
static void displayUnitsMenu();
static void messageBubblesMenu();
+ static void themeMenu();
static void textMessageMenu();
private:
@@ -136,23 +136,10 @@ template struct MenuOption {
MenuOption(const char *labelIn, OptionsAction actionIn) : label(labelIn), action(actionIn), hasValue(false), value() {}
};
-struct ScreenColor {
- uint8_t r;
- uint8_t g;
- uint8_t b;
- bool useVariant;
-
- explicit ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false)
- : r(rIn), g(gIn), b(bIn), useVariant(variantIn)
- {
- }
-};
-
using RadioPresetOption = MenuOption;
using LoraRegionOption = MenuOption;
using TimezoneOption = MenuOption;
using CompassOption = MenuOption;
-using ScreenColorOption = MenuOption;
using GPSToggleOption = MenuOption;
using GPSFormatOption = MenuOption;
using NodeNameOption = MenuOption;
diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp
index 2fd9bf541..2260c57df 100644
--- a/src/graphics/draw/MessageRenderer.cpp
+++ b/src/graphics/draw/MessageRenderer.cpp
@@ -11,6 +11,8 @@
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TFTColorRegions.h"
+#include "graphics/TFTPalette.h"
#include "graphics/TimeFormatters.h"
#include "graphics/emotes.h"
#include "main.h"
@@ -254,6 +256,76 @@ struct MessageBlock {
bool mine;
};
+#if GRAPHICS_TFT_COLORING_ENABLED
+static void setDarkModeBubbleRoleColors(uint32_t themeId, bool mine)
+{
+ uint16_t bubbleOnColor;
+ uint16_t bubbleOffColor;
+
+ if (themeId == ThemeID::Blue) {
+ bubbleOnColor = mine ? TFTPalette::Navy : TFTPalette::White;
+ bubbleOffColor = mine ? TFTPalette::SkyBlue : TFTPalette::DeepBlue;
+ } else {
+ bubbleOnColor = mine ? TFTPalette::Black : getThemeBodyFg();
+ bubbleOffColor = mine ? TFTPalette::SkyBlue : TFTPalette::DarkGray;
+ }
+
+ setTFTColorRole(TFTColorRole::ActionMenuBody, bubbleOnColor, bubbleOffColor);
+}
+
+static void registerRoundedBubbleFillRegion(int x, int y, int w, int h, int radius)
+{
+ if (w <= 0 || h <= 0) {
+ return;
+ }
+
+ if (radius <= 0 || w < 3 || h < 3) {
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, x, y, w, h);
+ return;
+ }
+
+ // Keep region count low so we don't churn MAX_TFT_COLOR_REGIONS while
+ // scrolling long message lists (which can flatten older bubble corners).
+ int capRows = 0;
+ if (radius >= 4 && h >= 5) {
+ capRows = 2; // 5 regions total (2 top caps + middle + 2 bottom caps)
+ } else if (radius >= 2 && h >= 3) {
+ capRows = 1; // 3 regions total
+ }
+ if (capRows <= 0) {
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, x, y, w, h);
+ return;
+ }
+
+ for (int row = 0; row < capRows; ++row) {
+ int inset = 0;
+ if (radius >= 4) {
+ inset = (row == 0) ? 2 : 1;
+ } else if (radius >= 2) {
+ inset = 1;
+ }
+ const int stripW = w - (inset * 2);
+ if (stripW <= 0) {
+ continue;
+ }
+
+ const int topY = y + row;
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, x + inset, topY, stripW, 1);
+
+ const int bottomY = y + h - 1 - row;
+ if (bottomY != topY) {
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, x + inset, bottomY, stripW, 1);
+ }
+ }
+
+ const int middleY = y + capRows;
+ const int middleH = h - (capRows * 2);
+ if (middleH > 0) {
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, x, middleY, w, middleH);
+ }
+}
+#endif
+
static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool isHeaderLine)
{
if (isHeaderLine) {
@@ -648,6 +720,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
const int contentBottom = scrollBottom; // already excludes nav line
const int rightEdge = SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN;
const int bubbleGapY = std::max(1, MESSAGE_BLOCK_GAP / 2);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ const uint32_t themeId = getActiveTheme().id;
+ // Blue is a dark variant but uses full frame inversion, Keep it on the same filled bubble style as Default Dark.
+ const bool useDarkModeBubbleFill = showBubbles && (!isThemeFullFrameInvert() || themeId == ThemeID::Blue);
+#endif
std::vector lineTop;
lineTop.resize(cachedLines.size());
@@ -686,6 +763,17 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]);
int bottomY = visualBottom + BUBBLE_PAD_Y;
+ // On high-res screens, keep a 1px gap under the header
+ if (currentResolution == ScreenResolution::High) {
+ const int minTopY = contentTop + 1;
+ if (topY < minTopY) {
+ // Preserve bubble height when we push it down from the header.
+ const int shift = minTopY - topY;
+ topY = minTopY;
+ bottomY += shift;
+ }
+ }
+
if (bi + 1 < blocks.size()) {
int nextHeaderIndex = (int)blocks[bi + 1].start;
int nextTop = lineTop[nextHeaderIndex];
@@ -735,24 +823,56 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
const int by = topY;
const int bw = bubbleW;
const int bh = bubbleH;
+#if GRAPHICS_TFT_COLORING_ENABLED
+ const bool drawBubbleOutline = !useDarkModeBubbleFill;
+#else
+ const bool drawBubbleOutline = true;
+#endif
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (useDarkModeBubbleFill) {
+ setDarkModeBubbleRoleColors(themeId, b.mine);
+ registerRoundedBubbleFillRegion(bx, by, bw, bh, r);
+ }
+#endif
- // Draw the 4 corner arcs using drawCircleQuads
- display->drawCircleQuads(bx + r, by + r, r, 0x2); // Top-left
- display->drawCircleQuads(bx + bw - r - 1, by + r, r, 0x1); // Top-right
- display->drawCircleQuads(bx + r, by + bh - r - 1, r, 0x4); // Bottom-left
- display->drawCircleQuads(bx + bw - r - 1, by + bh - r - 1, r, 0x8); // Bottom-right
+ if (drawBubbleOutline) {
+ // Draw the 4 corner arcs using drawCircleQuads
+ display->drawCircleQuads(bx + r, by + r, r, 0x2); // Top-left
+ display->drawCircleQuads(bx + bw - r - 1, by + r, r, 0x1); // Top-right
+ display->drawCircleQuads(bx + r, by + bh - r - 1, r, 0x4); // Bottom-left
+ display->drawCircleQuads(bx + bw - r - 1, by + bh - r - 1, r, 0x8); // Bottom-right
- // Draw the 4 edges between corners
- display->drawHorizontalLine(bx + r, by, bw - 2 * r); // Top edge
- display->drawHorizontalLine(bx + r, by + bh - 1, bw - 2 * r); // Bottom edge
- display->drawVerticalLine(bx, by + r, bh - 2 * r); // Left edge
- display->drawVerticalLine(bx + bw - 1, by + r, bh - 2 * r); // Right edge
+ // Draw the 4 edges between corners
+ display->drawHorizontalLine(bx + r, by, bw - 2 * r); // Top edge
+ display->drawHorizontalLine(bx + r, by + bh - 1, bw - 2 * r); // Bottom edge
+ display->drawVerticalLine(bx, by + r, bh - 2 * r); // Left edge
+ display->drawVerticalLine(bx + bw - 1, by + r, bh - 2 * r); // Right edge
+ }
} else if (bubbleW > 1 && bubbleH > 1) {
// Fallback to simple rectangle for very small bubbles
- display->drawRect(bubbleX, topY, bubbleW, bubbleH);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ const bool drawBubbleOutline = !useDarkModeBubbleFill;
+#else
+ const bool drawBubbleOutline = true;
+#endif
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (useDarkModeBubbleFill) {
+ setDarkModeBubbleRoleColors(themeId, b.mine);
+ registerTFTColorRegion(TFTColorRole::ActionMenuBody, bubbleX, topY, bubbleW, bubbleH);
+ }
+#endif
+ if (drawBubbleOutline) {
+ display->drawRect(bubbleX, topY, bubbleW, bubbleH);
+ }
}
}
} // end if (showBubbles)
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (useDarkModeBubbleFill) {
+ // Restore theme role defaults so other screens keep their intended palette.
+ loadThemeDefaults();
+ }
+#endif
// Render visible lines
int lineY = yOffset;
@@ -772,7 +892,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
headerX = x + textIndent;
}
graphics::UIRenderer::drawStringWithEmotes(display, headerX, lineY, cachedLines[i].c_str(), FONT_HEIGHT_SMALL, 1,
- false);
+ true);
// Draw underline just under header text
int underlineY = lineY + FONT_HEIGHT_SMALL;
diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp
index e0c5df124..00ae74b58 100644
--- a/src/graphics/draw/NodeListRenderer.cpp
+++ b/src/graphics/draw/NodeListRenderer.cpp
@@ -11,6 +11,8 @@
#include "gps/RTC.h" // for getTime() function
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TFTColorRegions.h"
+#include "graphics/TFTPalette.h"
#include "graphics/images.h"
#include "meshUtils.h"
#include
@@ -213,6 +215,33 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
}
}
+static inline void applyFavoriteNodeNameColor(OLEDDisplay *display, const meshtastic_NodeInfoLite *node, const char *nodeName,
+ int16_t nameX, int16_t y, int nameMaxWidth)
+{
+ if (!display || !node || !node->is_favorite || !isTFTColoringEnabled() || !nodeName) {
+ return;
+ }
+
+ const int textWidth = UIRenderer::measureStringWithEmotes(display, nodeName);
+ const int regionWidth = min(textWidth, max(0, nameMaxWidth));
+ if (regionWidth <= 0) {
+ return;
+ }
+
+ // Node list rows can begin a couple of pixels inside header space.
+ // Clamp favorite-name color region below the header to avoid black overlap there.
+ const int16_t minContentY = static_cast(FONT_HEIGHT_SMALL + 1);
+ const int16_t regionY = max(y, minContentY);
+ const int16_t yClip = regionY - y;
+ const int16_t regionHeight = static_cast(FONT_HEIGHT_SMALL - yClip);
+ if (regionHeight <= 0) {
+ return;
+ }
+
+ setAndRegisterTFTColorRole(TFTColorRole::FavoriteNode, TFTPalette::Yellow, TFTPalette::Black, nameX, regionY, regionWidth,
+ regionHeight);
+}
+
// =============================
// Entry Renderers
// =============================
@@ -227,6 +256,9 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
char nodeName[96];
UIRenderer::truncateStringWithEmotes(display, getSafeNodeName(display, node, columnWidth).c_str(), nodeName, sizeof(nodeName),
nameMaxWidth);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
+#endif
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char timeStr[10];
@@ -275,14 +307,20 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
int nameMaxWidth = getNodeNameMaxWidth(columnWidth, columnWidth - 25);
int barsOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 20 : 24) : (isLeftCol ? 15 : 19);
- int hopOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 21 : 29) : (isLeftCol ? 13 : 17);
+ constexpr int kBarCount = 4;
+ constexpr int kBarWidth = 2;
+ constexpr int kBarGap = 1;
int barsXOffset = columnWidth - barsOffset;
+ int barsRightEdge = x + barsXOffset + ((kBarCount - 1) * (kBarWidth + kBarGap)) + kBarWidth;
const int nameX = x + ((currentResolution == ScreenResolution::High) ? 6 : 3);
char nodeName[96];
UIRenderer::truncateStringWithEmotes(display, getSafeNodeName(display, node, columnWidth).c_str(), nodeName, sizeof(nodeName),
nameMaxWidth);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
+#endif
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
@@ -304,28 +342,48 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
}
}
- // Draw signal strength bars
- int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
- int barWidth = 2;
- int barStartX = x + barsXOffset;
- int barStartY = y + 1 + (FONT_HEIGHT_SMALL / 2) + 2;
+ const bool isZeroHop = node->has_hops_away && node->hops_away == 0;
- for (int b = 0; b < 4; b++) {
- if (b < bars) {
- int height = (b * 2);
- display->fillRect(barStartX + (b * (barWidth + 1)), barStartY - height, barWidth, height);
+ // Show signal only for direct neighbors (0 hops)
+ if (isZeroHop) {
+ int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
+ int barStartX = x + barsXOffset;
+ int barStartY = y + 1 + (FONT_HEIGHT_SMALL / 2) + 2;
+
+ if (bars > 0) {
+ uint16_t signalBarsColor = TFTPalette::Bad;
+ if (bars >= 3) {
+ signalBarsColor = TFTPalette::Good;
+ } else if (bars == 2) {
+ signalBarsColor = TFTPalette::Medium;
+ }
+
+ // Highest bar reaches 6 px in this renderer.
+ setAndRegisterTFTColorRole(TFTColorRole::SignalBars, signalBarsColor, TFTPalette::Black, barStartX, barStartY - 6,
+ (kBarCount * kBarWidth) + ((kBarCount - 1) * kBarGap), 6);
+ }
+
+ for (int b = 0; b < kBarCount; b++) {
+ if (b < bars) {
+ int height = (b * 2);
+ display->fillRect(barStartX + (b * (kBarWidth + kBarGap)), barStartY - height, kBarWidth, height);
+ }
}
}
- // Draw hop count
- char hopStr[6] = "";
- if (node->has_hops_away && node->hops_away > 0)
- snprintf(hopStr, sizeof(hopStr), "[%d]", node->hops_away);
+ // Draw hop count + hop icon
+ if (node->has_hops_away && node->hops_away > 0) {
+ char hopCount[6];
+ snprintf(hopCount, sizeof(hopCount), "%d", node->hops_away);
- if (hopStr[0] != '\0') {
- int rightEdge = x + columnWidth - hopOffset;
- int textWidth = display->getStringWidth(hopStr);
- display->drawString(rightEdge - textWidth, y, hopStr);
+ const int hopCountWidth = display->getStringWidth(hopCount);
+ const int gap = 1;
+ const int totalWidth = hopCountWidth + gap + hop_width;
+ const int hopX = barsRightEdge - totalWidth;
+ const int iconY = y + (FONT_HEIGHT_SMALL - hop_height) / 2;
+
+ display->drawString(hopX, y, hopCount);
+ display->drawXbm(hopX + hopCountWidth + gap, iconY, hop_width, hop_height, hop);
}
}
@@ -340,6 +398,9 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
char nodeName[96];
UIRenderer::truncateStringWithEmotes(display, getSafeNodeName(display, node, columnWidth).c_str(), nodeName, sizeof(nodeName),
nameMaxWidth);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
+#endif
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char distStr[10] = "";
@@ -445,6 +506,9 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
char nodeName[96];
UIRenderer::truncateStringWithEmotes(display, getSafeNodeName(display, node, columnWidth).c_str(), nodeName, sizeof(nodeName),
nameMaxWidth);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
+#endif
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
@@ -700,6 +764,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
display->setColor(WHITE);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ registerTFTActionMenuRegions(boxLeft, boxTop, boxWidth, boxHeight);
+#endif
// Text
display->drawString(boxLeft + padding, boxTop + padding, buf);
diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp
index 31eb2c3c8..cca60d1e2 100644
--- a/src/graphics/draw/NotificationRenderer.cpp
+++ b/src/graphics/draw/NotificationRenderer.cpp
@@ -7,6 +7,8 @@
#include "UIRenderer.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TFTColorRegions.h"
+#include "graphics/TFTPalette.h"
#include "graphics/images.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
@@ -608,6 +610,9 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
display->setColor(WHITE);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ registerTFTActionMenuRegions(boxLeft, boxTop, boxWidth, boxHeight);
+#endif
// Draw Content
int16_t lineY = boxTop + vPadding;
@@ -630,7 +635,21 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
if (strchr(lineBuffer, 'p') || strchr(lineBuffer, 'g') || strchr(lineBuffer, 'y') || strchr(lineBuffer, 'j')) {
background_yOffset = -1;
}
- display->fillRect(boxLeft, boxTop + 1, boxWidth, effectiveLineHeight - background_yOffset);
+ const int16_t titleBarY = boxTop + 1;
+ const int16_t titleBarHeight = effectiveLineHeight - background_yOffset;
+ display->fillRect(boxLeft, titleBarY, boxWidth, titleBarHeight);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (alertBannerOptions > 0) {
+ const uint16_t titleTextColor =
+ (getActiveTheme().id == ThemeID::DefaultLight) ? TFTPalette::Black : getThemeHeaderText();
+ // Keep title role away from border/corner pixels so rounded-corner masks are not remapped to the title text
+ // color.
+ if (boxWidth > 2 && titleBarHeight > 0) {
+ setAndRegisterTFTColorRole(TFTColorRole::ActionMenuTitle, getThemeHeaderBg(), titleTextColor, boxLeft + 1,
+ titleBarY, boxWidth - 2, titleBarHeight);
+ }
+ }
+#endif
display->setColor(BLACK);
int yOffset = 3;
if (current_notification_type == notificationTypeEnum::node_picker) {
@@ -650,6 +669,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
const int barSpacing = 2;
const int barHeightStep = 2;
const int gap = 6;
+ const int maxBarHeight = totalBars * barHeightStep;
int textWidth = display->getStringWidth(lineBuffer, strlen(lineBuffer), true);
int barsWidth = totalBars * barWidth + (totalBars - 1) * barSpacing + gap;
@@ -664,6 +684,20 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
int baseX = groupStartX + textWidth + gap;
int baseY = lineY + effectiveLineHeight - 1;
+#if GRAPHICS_TFT_COLORING_ENABLED
+ if (graphics::bannerSignalBars > 0) {
+ uint16_t signalBarsColor = TFTPalette::Medium;
+ if (graphics::bannerSignalBars <= 1) {
+ signalBarsColor = TFTPalette::Bad;
+ } else if (graphics::bannerSignalBars >= 4) {
+ signalBarsColor = TFTPalette::Good;
+ }
+ const int activeBars = min(graphics::bannerSignalBars, totalBars);
+ const int regionWidth = activeBars * barWidth + (activeBars - 1) * barSpacing;
+ setAndRegisterTFTColorRole(TFTColorRole::SignalBars, signalBarsColor, TFTPalette::Black, baseX,
+ baseY - maxBarHeight, regionWidth, maxBarHeight);
+ }
+#endif
for (int b = 0; b < totalBars; b++) {
int barHeight = (b + 1) * barHeightStep;
int x = baseX + b * (barWidth + barSpacing);
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index ef2d5e3c5..a28a4fe24 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -13,6 +13,8 @@
#include "gps/GeoCoord.h"
#include "graphics/EmoteRenderer.h"
#include "graphics/SharedUIDisplay.h"
+#include "graphics/TFTColorRegions.h"
+#include "graphics/TFTPalette.h"
#include "graphics/TimeFormatters.h"
#include "graphics/images.h"
#include "main.h"
@@ -30,6 +32,7 @@ namespace graphics
{
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
std::vector graphics::UIRenderer::favoritedNodes;
+static bool gBootSplashBoldPass = false;
static inline void drawSatelliteIcon(OLEDDisplay *display, int16_t x, int16_t y)
{
@@ -41,6 +44,347 @@ static inline void drawSatelliteIcon(OLEDDisplay *display, int16_t x, int16_t y)
}
}
+struct StandardCompassNeedlePoints {
+ int16_t northTipX;
+ int16_t northTipY;
+ int16_t northLeftX;
+ int16_t northLeftY;
+ int16_t northRightX;
+ int16_t northRightY;
+ int16_t southTipX;
+ int16_t southTipY;
+ int16_t southLeftX;
+ int16_t southLeftY;
+ int16_t southRightX;
+ int16_t southRightY;
+};
+
+static inline void swapPoint(int16_t &ax, int16_t &ay, int16_t &bx, int16_t &by)
+{
+ const int16_t tx = ax;
+ const int16_t ty = ay;
+ ax = bx;
+ ay = by;
+ bx = tx;
+ by = ty;
+}
+
+static inline void transformNeedlePoint(float localX, float localY, float sinHeading, float cosHeading, float scale,
+ int16_t centerX, int16_t centerY, int16_t &outX, int16_t &outY)
+{
+ const float x = ((localX * cosHeading) - (localY * sinHeading)) * scale + centerX;
+ const float y = ((localX * sinHeading) + (localY * cosHeading)) * scale + centerY;
+ outX = static_cast(x);
+ outY = static_cast(y);
+}
+
+static float getCompassRingAngleOffset(float heading)
+{
+ return (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) ? -heading : 0.0f;
+}
+
+static inline StandardCompassNeedlePoints computeStandardCompassNeedlePoints(int16_t compassX, int16_t compassY,
+ uint16_t compassDiam, float headingRadian,
+ float centerGapPx)
+{
+ // Standard-style symmetric needle with a narrow waist and a tiny center gap
+ // between north/south halves to prevent seam bleed while rotating.
+ const float scaledDiam = compassDiam * 0.76f;
+ const float gapNormHalf = (centerGapPx * 0.5f) / scaledDiam;
+ const float sinHeading = sinf(headingRadian);
+ const float cosHeading = cosf(headingRadian);
+
+ StandardCompassNeedlePoints points{};
+ transformNeedlePoint(0.0f, -0.5f, sinHeading, cosHeading, scaledDiam, compassX, compassY, points.northTipX, points.northTipY);
+ transformNeedlePoint(-0.09f, -gapNormHalf, sinHeading, cosHeading, scaledDiam, compassX, compassY, points.northLeftX,
+ points.northLeftY);
+ transformNeedlePoint(0.09f, -gapNormHalf, sinHeading, cosHeading, scaledDiam, compassX, compassY, points.northRightX,
+ points.northRightY);
+ transformNeedlePoint(0.0f, 0.5f, sinHeading, cosHeading, scaledDiam, compassX, compassY, points.southTipX, points.southTipY);
+ transformNeedlePoint(-0.09f, gapNormHalf, sinHeading, cosHeading, scaledDiam, compassX, compassY, points.southLeftX,
+ points.southLeftY);
+ transformNeedlePoint(0.09f, gapNormHalf, sinHeading, cosHeading, scaledDiam, compassX, compassY, points.southRightX,
+ points.southRightY);
+ return points;
+}
+
+static inline void drawCompassNorthOnlyLabel(OLEDDisplay *display, int16_t compassX, int16_t compassY, int16_t compassRadius,
+ float heading)
+{
+ int16_t labelRadius = compassRadius;
+ // CompassRenderer::drawCompassNorth() expands radius on high-res by +4.
+ // Compensate so label placement stays aligned with the current UI layout.
+ if (currentResolution == ScreenResolution::High && labelRadius > 4) {
+ labelRadius -= 4;
+ }
+ graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, heading, labelRadius);
+}
+
+static inline void drawMonoCompass(OLEDDisplay *display, int16_t compassX, int16_t compassY, int16_t compassRadius, float heading)
+{
+ const StandardCompassNeedlePoints points =
+ computeStandardCompassNeedlePoints(compassX, compassY, static_cast(compassRadius * 2), -heading, 0.0f);
+
+#ifdef USE_EINK
+ display->setColor(WHITE);
+ display->drawTriangle(points.northTipX, points.northTipY, points.northLeftX, points.northLeftY, points.northRightX,
+ points.northRightY);
+ display->drawTriangle(points.southTipX, points.southTipY, points.southLeftX, points.southLeftY, points.southRightX,
+ points.southRightY);
+#else
+ // OLED variant: same needle geometry as TFT, but monochrome contrast.
+ display->setColor(WHITE);
+ display->fillTriangle(points.northTipX, points.northTipY, points.northLeftX, points.northLeftY, points.northRightX,
+ points.northRightY);
+ display->setColor(BLACK);
+ display->fillTriangle(points.southTipX, points.southTipY, points.southLeftX, points.southLeftY, points.southRightX,
+ points.southRightY);
+ // Keep a white outline so the black half remains visible on dark backgrounds.
+ display->setColor(WHITE);
+ display->drawTriangle(points.southTipX, points.southTipY, points.southLeftX, points.southLeftY, points.southRightX,
+ points.southRightY);
+#endif
+
+ display->drawCircle(compassX, compassY, compassRadius);
+ drawCompassNorthOnlyLabel(display, compassX, compassY, compassRadius, heading);
+}
+
+#if GRAPHICS_TFT_COLORING_ENABLED
+struct NeedleColorBand {
+ int16_t xMin;
+ int16_t xMax;
+ int16_t yMin;
+ int16_t yMax;
+ bool used;
+};
+
+static constexpr int kNeedleBandCount = 6;
+
+static inline void registerNeedleSpan(NeedleColorBand (&bands)[kNeedleBandCount], int16_t bandTop, int16_t bandHeight, int16_t y,
+ int16_t a, int16_t b)
+{
+ if (a > b) {
+ const int16_t t = a;
+ a = b;
+ b = t;
+ }
+
+ int band = (static_cast(y - bandTop) * kNeedleBandCount) / bandHeight;
+ if (band < 0) {
+ band = 0;
+ } else if (band >= kNeedleBandCount) {
+ band = kNeedleBandCount - 1;
+ }
+
+ NeedleColorBand ®ion = bands[band];
+ if (!region.used) {
+ region.used = true;
+ region.xMin = a;
+ region.xMax = b;
+ region.yMin = y;
+ region.yMax = y;
+ return;
+ }
+ if (a < region.xMin)
+ region.xMin = a;
+ if (b > region.xMax)
+ region.xMax = b;
+ if (y < region.yMin)
+ region.yMin = y;
+ if (y > region.yMax)
+ region.yMax = y;
+}
+
+static void drawNeedleHalfAndRegisterBands(OLEDDisplay *display, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2,
+ int16_t y2, uint16_t onColor, uint16_t offColor)
+{
+ // Important for maintainers:
+ // The compass needle rotates continuously, so color-region registration must
+ // track triangle shape (or a close approximation), not only one AABB.
+ // Coarse rectangles can leak south color into north at diagonal angles.
+ // Keep this banded approach unless a replacement preserves per-angle coverage.
+ // Performance note: draw the triangle once via fillTriangle(), then build
+ // band regions in software for accurate color-role registration.
+ display->fillTriangle(x0, y0, x1, y1, x2, y2);
+
+ if (y0 > y1)
+ swapPoint(x0, y0, x1, y1);
+ if (y1 > y2)
+ swapPoint(x1, y1, x2, y2);
+ if (y0 > y1)
+ swapPoint(x0, y0, x1, y1);
+
+ NeedleColorBand bands[kNeedleBandCount] = {};
+
+ const int16_t bandTop = y0;
+ const int16_t bandBottom = y2;
+ const int16_t bandHeight = (bandBottom >= bandTop) ? static_cast(bandBottom - bandTop + 1) : 1;
+
+ const int32_t dx01 = x1 - x0;
+ const int32_t dy01 = y1 - y0;
+ const int32_t dx02 = x2 - x0;
+ const int32_t dy02 = y2 - y0;
+ const int32_t dx12 = x2 - x1;
+ const int32_t dy12 = y2 - y1;
+
+ int32_t sa = 0;
+ int32_t sb = 0;
+ int16_t y = y0;
+
+ const int16_t last = (y1 == y2) ? y1 : static_cast(y1 - 1);
+ for (; y <= last; y++) {
+ const int16_t a = static_cast(x0 + ((dy01 != 0) ? (sa / dy01) : 0));
+ const int16_t b = static_cast(x0 + ((dy02 != 0) ? (sb / dy02) : 0));
+ sa += dx01;
+ sb += dx02;
+ registerNeedleSpan(bands, bandTop, bandHeight, y, a, b);
+ }
+
+ sa = dx12 * static_cast(y - y1);
+ sb = dx02 * static_cast(y - y0);
+ for (; y <= y2; y++) {
+ const int16_t a = static_cast(x1 + ((dy12 != 0) ? (sa / dy12) : 0));
+ const int16_t b = static_cast(x0 + ((dy02 != 0) ? (sb / dy02) : 0));
+ sa += dx12;
+ sb += dx02;
+ registerNeedleSpan(bands, bandTop, bandHeight, y, a, b);
+ }
+
+ for (int i = 0; i < kNeedleBandCount; i++) {
+ if (!bands[i].used)
+ continue;
+ registerTFTColorRegionDirect(bands[i].xMin, bands[i].yMin, bands[i].xMax - bands[i].xMin + 1,
+ bands[i].yMax - bands[i].yMin + 1, onColor, offColor);
+ }
+}
+
+static inline void drawCompassCardinalLabel(OLEDDisplay *display, int16_t x, int16_t y, const char *label, int16_t textWidth)
+{
+ const int16_t labelTop = y - (FONT_HEIGHT_SMALL / 2);
+ const int16_t padX = 1;
+ const int16_t padY = 1;
+
+ // Clear any ring/tick pixels behind the label so letters remain clean.
+ display->setColor(BLACK);
+ display->fillRect(x - (textWidth / 2) - padX, labelTop - padY, textWidth + (padX * 2), FONT_HEIGHT_SMALL + (padY * 2));
+
+ display->setColor(WHITE);
+ display->drawString(x, labelTop, label);
+}
+
+static inline void drawCompassCardinalLabels(OLEDDisplay *display, int16_t compassX, int16_t compassY, int16_t compassRadius,
+ float heading)
+{
+ const float northAngle = getCompassRingAngleOffset(heading);
+ const float radius = compassRadius - 1.0f;
+ const float sinNorth = sinf(northAngle);
+ const float cosNorth = cosf(northAngle);
+
+ const int16_t nX = compassX + static_cast(radius * sinNorth);
+ const int16_t nY = compassY - static_cast(radius * cosNorth);
+ const int16_t eX = compassX + static_cast(radius * cosNorth);
+ const int16_t eY = compassY + static_cast(radius * sinNorth);
+ const int16_t sX = compassX - static_cast(radius * sinNorth);
+ const int16_t sY = compassY + static_cast(radius * cosNorth);
+ const int16_t wX = compassX - static_cast(radius * cosNorth);
+ const int16_t wY = compassY - static_cast(radius * sinNorth);
+
+ display->setFont(FONT_SMALL);
+ display->setTextAlignment(TEXT_ALIGN_CENTER);
+ const int16_t labelWidth = static_cast(display->getStringWidth("N"));
+ drawCompassCardinalLabel(display, nX, nY, "N", labelWidth);
+ drawCompassCardinalLabel(display, eX, eY, "E", labelWidth);
+ drawCompassCardinalLabel(display, sX, sY, "S", labelWidth);
+ drawCompassCardinalLabel(display, wX, wY, "W", labelWidth);
+}
+
+static inline void drawCompassDegreeMarkers(OLEDDisplay *display, int16_t compassX, int16_t compassY, int16_t compassRadius,
+ float heading)
+{
+ const float baseAngle = getCompassRingAngleOffset(heading);
+
+ constexpr int16_t majorLen = 5;
+ constexpr int16_t minorLen = 3;
+
+ display->setColor(WHITE);
+ constexpr float kStepAngle = 15.0f * DEG_TO_RAD;
+ const float sinStep = sinf(kStepAngle);
+ const float cosStep = cosf(kStepAngle);
+ float sinAngle = sinf(baseAngle);
+ float cosAngle = cosf(baseAngle);
+ bool isMajor = true;
+ for (int tick = 0; tick < 24; tick++) {
+ const int16_t tickLen = isMajor ? majorLen : minorLen;
+
+ const int16_t xOuter = compassX + static_cast((compassRadius - 1) * sinAngle);
+ const int16_t yOuter = compassY - static_cast((compassRadius - 1) * cosAngle);
+ const int16_t xInner = compassX + static_cast((compassRadius - tickLen) * sinAngle);
+ const int16_t yInner = compassY - static_cast((compassRadius - tickLen) * cosAngle);
+ display->drawLine(xInner, yInner, xOuter, yOuter);
+
+ // Rotate [sin, cos] by a fixed step instead of recomputing trig 24x/frame.
+ const float nextSin = (sinAngle * cosStep) + (cosAngle * sinStep);
+ const float nextCos = (cosAngle * cosStep) - (sinAngle * sinStep);
+ sinAngle = nextSin;
+ cosAngle = nextCos;
+ isMajor = !isMajor;
+ }
+}
+
+static inline void drawStandardCompassNeedle(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam,
+ float headingRadian, uint16_t needleOffColor)
+{
+ const StandardCompassNeedlePoints points =
+ computeStandardCompassNeedlePoints(compassX, compassY, compassDiam, headingRadian, 9.0f);
+
+ display->setColor(WHITE);
+#ifdef USE_EINK
+ display->drawTriangle(points.northTipX, points.northTipY, points.northLeftX, points.northLeftY, points.northRightX,
+ points.northRightY);
+ display->drawTriangle(points.southTipX, points.southTipY, points.southLeftX, points.southLeftY, points.southRightX,
+ points.southRightY);
+#else
+ // NOTE: do not collapse these to one region per half during "flash
+ // optimization". The needle spins, and coarse rectangles will bleed color
+ // across halves at diagonal angles.
+ drawNeedleHalfAndRegisterBands(display, points.northTipX, points.northTipY, points.northLeftX, points.northLeftY,
+ points.northRightX, points.northRightY, TFTPalette::Red, needleOffColor);
+ drawNeedleHalfAndRegisterBands(display, points.southTipX, points.southTipY, points.southLeftX, points.southLeftY,
+ points.southRightX, points.southRightY, TFTPalette::Blue, needleOffColor);
+#endif
+}
+
+static inline void drawTftCompass(OLEDDisplay *display, int16_t compassX, int16_t compassY, int16_t compassRadius, float heading)
+{
+ // Compass colors should follow whatever background role is already active at this location.
+ const uint16_t compassBgColor = resolveTFTOffColorAt(compassX, compassY, getThemeBodyBg());
+ const uint16_t compassGlyphColor = TFTPalette::pickReadableMonoFg(compassBgColor);
+ const int16_t pad = 2;
+ const int16_t labelPadX = static_cast(display->getStringWidth("W") / 2) + 2;
+ const int16_t labelPadY = static_cast(FONT_HEIGHT_SMALL / 2) + 2;
+ const int16_t boxX = compassX - compassRadius - pad - labelPadX;
+ const int16_t boxY = compassY - compassRadius - pad - labelPadY;
+ const int16_t boxW = (compassRadius * 2) + (pad * 2) + 1 + (labelPadX * 2);
+ const int16_t boxH = (compassRadius * 2) + (pad * 2) + 1 + (labelPadY * 2);
+ // Never let compass-local tint regions override the header role regions.
+ const int16_t bodyTop = static_cast(getTextPositions(display)[1]);
+ int16_t clippedY = boxY;
+ int16_t clippedH = boxH;
+ if (clippedY < bodyTop) {
+ clippedH = static_cast(clippedH - (bodyTop - clippedY));
+ clippedY = bodyTop;
+ }
+ if (clippedH > 0) {
+ registerTFTColorRegionDirect(boxX, clippedY, boxW, clippedH, compassGlyphColor, compassBgColor);
+ }
+
+ drawStandardCompassNeedle(display, compassX, compassY, static_cast(compassRadius * 2), -heading, compassBgColor);
+ display->drawCircle(compassX, compassY, compassRadius);
+ drawCompassDegreeMarkers(display, compassX, compassY, compassRadius, heading);
+ drawCompassCardinalLabels(display, compassX, compassY, compassRadius, heading);
+}
+#endif // GRAPHICS_TFT_COLORING_ENABLED
+
static void drawCompassStatusText(OLEDDisplay *display, int16_t compassX, int16_t compassY, const char *statusLine1,
const char *statusLine2)
{
@@ -50,6 +394,99 @@ static void drawCompassStatusText(OLEDDisplay *display, int16_t compassX, int16_
display->setTextAlignment(TEXT_ALIGN_LEFT);
}
+static void drawBearingCompassOrStatus(OLEDDisplay *display, int16_t compassX, int16_t compassY, int16_t compassRadius,
+ bool showCompass, float myHeading, float bearing, const char *statusLine1,
+ const char *statusLine2)
+{
+ // Shared "favorite node" compass renderer: draw ring, then either heading data or fallback status text.
+ display->drawCircle(compassX, compassY, compassRadius);
+ if (showCompass) {
+ CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
+ CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, bearing);
+ } else {
+ drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
+ }
+}
+
+static void drawDetailedCompassOrStatus(OLEDDisplay *display, int16_t compassX, int16_t compassY, int16_t compassRadius,
+ bool validHeading, float heading, const char *statusLine1, const char *statusLine2)
+{
+ // Shared "position screen" compass renderer: use mono/TFT path only when heading is valid.
+ if (validHeading) {
+#if GRAPHICS_TFT_COLORING_ENABLED
+ drawTftCompass(display, compassX, compassY, compassRadius, heading);
+#else
+ drawMonoCompass(display, compassX, compassY, compassRadius, heading);
+#endif
+ } else {
+ display->drawCircle(compassX, compassY, compassRadius);
+ drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
+ }
+}
+
+static bool computeLandscapeCompassPlacement(OLEDDisplay *display, int16_t xOffset, int16_t topY, int16_t *compassX,
+ int16_t *compassY, int16_t *compassRadius)
+{
+ // Keep compass vertically centered in the body area while reserving footer/nav space.
+ const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
+ const int16_t usableHeight = bottomY - topY - 5;
+ int16_t radius = usableHeight / 2;
+ if (radius < 8) {
+ radius = 8;
+ }
+
+ *compassRadius = radius;
+ *compassX = xOffset + SCREEN_WIDTH - radius - 8;
+ *compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
+ return true;
+}
+
+static bool computeBottomCompassPlacement(OLEDDisplay *display, int16_t xOffset, int16_t yBelowContent, int16_t bottomReserved,
+ int16_t margin, int16_t *compassX, int16_t *compassY, int16_t *compassRadius)
+{
+ // Return false when content leaves no room for a readable compass.
+ int availableHeight = SCREEN_HEIGHT - yBelowContent - bottomReserved - margin;
+ if (availableHeight < FONT_HEIGHT_SMALL * 2) {
+ return false;
+ }
+
+ int16_t radius = static_cast(availableHeight / 2);
+ if (radius < 8) {
+ radius = 8;
+ }
+ if (radius * 2 > SCREEN_WIDTH - 16) {
+ radius = (SCREEN_WIDTH - 16) / 2;
+ }
+
+ *compassRadius = radius;
+ *compassX = xOffset + (SCREEN_WIDTH / 2);
+ *compassY = static_cast(yBelowContent + (availableHeight / 2));
+ return true;
+}
+
+static void drawTruncatedStatusLine(OLEDDisplay *display, int16_t x, int16_t y, const std::string &statusText)
+{
+ // Fixed-buffer truncate helper replaces iterative std::string chopping to keep code size down.
+ char rawStatus[96];
+ snprintf(rawStatus, sizeof(rawStatus), " Status: %s", statusText.c_str());
+
+ char clippedStatus[96];
+ UIRenderer::truncateStringWithEmotes(display, rawStatus, clippedStatus, sizeof(clippedStatus), display->getWidth());
+ display->drawString(x, y, clippedStatus);
+}
+
+static int computeChannelUtilizationFill(int percent, int maxFill)
+{
+ // Compact linear fill mapping for the utilization bar.
+ if (percent <= 0 || maxFill <= 0) {
+ return 0;
+ }
+ if (percent >= 100) {
+ return maxFill;
+ }
+ return (maxFill * percent + 50) / 100;
+}
+
void graphics::UIRenderer::rebuildFavoritedNodes()
{
favoritedNodes.clear();
@@ -331,7 +768,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
snprintf(titlestr, sizeof(titlestr), "*%s*", shortName);
// === Draw battery/time/mail header (common across screens) ===
- graphics::drawCommonHeader(display, x, y, titlestr);
+ graphics::drawCommonHeader(display, x, y, titlestr, false, false, false, true, TFTPalette::Yellow);
// ===== DYNAMIC ROW STACKING WITH YOUR MACROS =====
// 1. Each potential info row has a macro-defined Y position (not regular increments!).
@@ -349,8 +786,13 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
}
+ // Print node's long name (e.g. "Backpack Node")
if (username) {
- // Print node's long name (e.g. "Backpack Node")
+#if GRAPHICS_TFT_COLORING_ENABLED
+ const int usernameWidth = UIRenderer::measureStringWithEmotes(display, username);
+ setAndRegisterTFTColorRole(TFTColorRole::FavoriteNodeBGHighlight, TFTPalette::Yellow, TFTPalette::Black, x,
+ getTextPositions(display)[line], usernameWidth, FONT_HEIGHT_SMALL);
+#endif
UIRenderer::drawStringWithEmotes(display, x, getTextPositions(display)[line++], username, FONT_HEIGHT_SMALL, 1, false);
}
@@ -370,45 +812,15 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
}
if (found) {
- std::string statusLine = std::string(" Status: ") + found->statusText;
- {
- const int screenW = display->getWidth();
- const int ellipseW = display->getStringWidth("...");
- int w = display->getStringWidth(statusLine.c_str());
-
- // Only do work if it overflows
- if (w > screenW) {
- bool truncated = false;
- if (ellipseW > screenW) {
- statusLine.clear();
- } else {
- while (!statusLine.empty()) {
- // remove one char (byte) at a time
- statusLine.pop_back();
- truncated = true;
-
- // Measure candidate with ellipsis appended
- std::string candidate = statusLine + "...";
- if (display->getStringWidth(candidate.c_str()) <= screenW) {
- statusLine = std::move(candidate);
- break;
- }
- }
- if (statusLine.empty() && ellipseW <= screenW) {
- statusLine = "...";
- }
- }
- }
- }
- display->drawString(x, getTextPositions(display)[line++], statusLine.c_str());
+ drawTruncatedStatusLine(display, x, getTextPositions(display)[line++], found->statusText);
}
}
#endif
- // === 2. Signal and Hops (combined on one line, if available) ===
- char signalHopsStr[32] = "";
+ // === 2. Signal/Hops line (if available) ===
bool haveSignal = false;
int bars = 0;
+ const char *qualityLabel = nullptr;
// Helper to get SNR limit based on modem preset
auto getSnrLimit = [](meshtastic_Config_LoRaConfig_ModemPreset preset) -> float {
@@ -429,80 +841,51 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
}
};
- // Calculate signal grade using modem preset and SNR only
- float snrLimit = getSnrLimit(config.lora.modem_preset);
- float snr = node->snr;
-
- // Determine signal quality label and bars using SNR-only grading
- const char *qualityLabel = nullptr;
-
- if (snr > snrLimit + 10) {
- qualityLabel = "Good";
- bars = 4;
- } else if (snr > snrLimit + 6) {
- qualityLabel = "Good";
- bars = 3;
- } else if (snr > snrLimit + 2) {
- qualityLabel = "Good";
- bars = 2;
- } else if (snr > snrLimit - 4) {
- qualityLabel = "Fair";
- bars = 1;
- } else {
- qualityLabel = "Bad";
- bars = 1;
- }
-
// Add extra spacing on the left if we have an API connection to account for the common footer icons
const char *leftSideSpacing =
graphics::isAPIConnected(service->api_state) ? (currentResolution == ScreenResolution::High ? " " : " ") : " ";
+ const bool isZeroHop = node->has_hops_away && node->hops_away == 0;
- // --- Build the Signal/Hops line ---
- // Only show signal if we have valid SNR
- if (snr > -100 && snr != 0) {
- snprintf(signalHopsStr, sizeof(signalHopsStr), "%sSig:%s", leftSideSpacing, qualityLabel);
- haveSignal = true;
- }
-
- if (node->hops_away > 0) {
- size_t len = strlen(signalHopsStr);
- if (haveSignal) {
- snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [#]");
- } else {
- snprintf(signalHopsStr, sizeof(signalHopsStr), "[#]");
- }
- }
-
- if (signalHopsStr[0]) {
- int yPos = getTextPositions(display)[line++];
- int curX = x;
-
- // Split combined string into signal text and hop suffix
- char sigPart[20] = "";
- const char *hopPart = nullptr;
-
- char *bracket = strchr(signalHopsStr, '[');
- if (bracket) {
- size_t n = (size_t)(bracket - signalHopsStr);
- if (n >= sizeof(sigPart))
- n = sizeof(sigPart) - 1;
- memcpy(sigPart, signalHopsStr, n);
- sigPart[n] = '\0';
-
- // Trim trailing spaces
- while (strlen(sigPart) && sigPart[strlen(sigPart) - 1] == ' ') {
- sigPart[strlen(sigPart) - 1] = '\0';
+ // Signal text/bars are only for direct (zero-hop) nodes with valid SNR.
+ if (isZeroHop) {
+ float snr = node->snr;
+ if (snr > -100 && snr != 0) {
+ float snrLimit = getSnrLimit(config.lora.modem_preset);
+ // Determine signal quality label and bars using SNR-only grading.
+ if (snr > snrLimit + 10) {
+ qualityLabel = "Good";
+ bars = 4;
+ } else if (snr > snrLimit + 6) {
+ qualityLabel = "Good";
+ bars = 3;
+ } else if (snr > snrLimit + 2) {
+ qualityLabel = "Good";
+ bars = 2;
+ } else if (snr > snrLimit - 4) {
+ qualityLabel = "Fair";
+ bars = 1;
+ } else {
+ qualityLabel = "Bad";
+ bars = 1;
}
- hopPart = bracket; // "[n Hop(s)]"
- } else {
- strncpy(sigPart, signalHopsStr, sizeof(sigPart) - 1);
- sigPart[sizeof(sigPart) - 1] = '\0';
+ haveSignal = true;
}
+ }
- // Draw signal quality text
- display->drawString(curX, yPos, sigPart);
- curX += display->getStringWidth(sigPart) + 4;
+ const bool showHops = node->has_hops_away && node->hops_away > 0;
+
+ if (haveSignal || showHops) {
+ int yPos = getTextPositions(display)[line++];
+ int curX = x + display->getStringWidth(leftSideSpacing);
+
+ // Draw signal quality text for zero-hop nodes when present.
+ if (haveSignal && qualityLabel) {
+ char signalLabel[20];
+ snprintf(signalLabel, sizeof(signalLabel), "Sig:%s", qualityLabel);
+ display->drawString(curX, yPos, signalLabel);
+ curX += display->getStringWidth(signalLabel) + 4;
+ }
// Draw signal bars (skip on UltraLow, text only)
if (currentResolution != ScreenResolution::UltraLow && haveSignal && bars > 0) {
@@ -521,6 +904,16 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
if (!hi)
maxBarHeight -= 1;
int barY = yPos + (FONT_HEIGHT_SMALL - maxBarHeight) / 2;
+ int totalBarsWidth = (kMaxBars * barWidth) + ((kMaxBars - 1) * barGap);
+
+ uint16_t signalBarsColor = TFTPalette::Good;
+ if (qualityLabel && strcmp(qualityLabel, "Fair") == 0) {
+ signalBarsColor = TFTPalette::Medium;
+ } else if (qualityLabel && strcmp(qualityLabel, "Bad") == 0) {
+ signalBarsColor = TFTPalette::Bad;
+ }
+ setAndRegisterTFTColorRole(TFTColorRole::SignalBars, signalBarsColor, TFTPalette::Black, barX, barY, totalBarsWidth,
+ maxBarHeight);
for (int bi = 0; bi < kMaxBars; bi++) {
int barHeight = maxBarHeight * (bi + 1) / kMaxBars;
@@ -538,29 +931,23 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
}
}
- curX += (kMaxBars * barWidth) + ((kMaxBars - 1) * barGap) + 2;
+ curX += totalBarsWidth + 2;
}
- // Draw hops AFTER the bars as: [ number + hop icon ]
- if (hopPart && node->hops_away > 0) {
+ // Draw hops for non-zero-hop nodes as: number + hop icon.
+ // This path is mutually exclusive with the zero-hop signal-bars path above.
+ if (showHops) {
+ display->drawString(curX, yPos, "Hop:");
+ curX += display->getStringWidth("Hop:") + 2;
- // open bracket
- display->drawString(curX, yPos, "[");
- curX += display->getStringWidth("[") + 1;
-
- // hop count
char hopCount[6];
snprintf(hopCount, sizeof(hopCount), "%d", node->hops_away);
display->drawString(curX, yPos, hopCount);
curX += display->getStringWidth(hopCount) + 2;
- // hop icon
const int iconY = yPos + (FONT_HEIGHT_SMALL - hop_height) / 2;
display->drawXbm(curX, iconY, hop_width, hop_height, hop);
curX += hop_width + 1;
-
- // closing bracket
- display->drawString(curX, yPos, "]");
}
}
@@ -599,48 +986,29 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
bool haveDistance = false;
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
- double lat1 = ourNode->position.latitude_i * 1e-7;
- double lon1 = ourNode->position.longitude_i * 1e-7;
- double lat2 = node->position.latitude_i * 1e-7;
- double lon2 = node->position.longitude_i * 1e-7;
- double earthRadiusKm = 6371.0;
- double dLat = (lat2 - lat1) * DEG_TO_RAD;
- double dLon = (lon2 - lon1) * DEG_TO_RAD;
- double a =
- sin(dLat / 2) * sin(dLat / 2) + cos(lat1 * DEG_TO_RAD) * cos(lat2 * DEG_TO_RAD) * sin(dLon / 2) * sin(dLon / 2);
- double c = 2 * atan2(sqrt(a), sqrt(1 - a));
- double distanceKm = earthRadiusKm * c;
-
+ // Use shared meter conversion, then format display units with lightweight integer rounding.
+ const float distanceMeters =
+ GeoCoord::latLongToMeter(DegD(node->position.latitude_i), DegD(node->position.longitude_i),
+ DegD(ourNode->position.latitude_i), DegD(ourNode->position.longitude_i));
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
- double miles = distanceKm * 0.621371;
- if (miles < 0.1) {
- int feet = (int)(miles * 5280);
- if (feet > 0 && feet < 1000) {
- snprintf(distStr, sizeof(distStr), "%sDistance:%dft", leftSideSpacing, feet);
- haveDistance = true;
- } else if (feet >= 1000) {
- snprintf(distStr, sizeof(distStr), "%sDistance:¼mi", leftSideSpacing);
- haveDistance = true;
- }
+ const int feet = static_cast((distanceMeters * METERS_TO_FEET) + 0.5f);
+ if (feet > 0 && feet < 1000) {
+ snprintf(distStr, sizeof(distStr), "%sDistance:%dft", leftSideSpacing, feet);
+ haveDistance = true;
} else {
- int roundedMiles = (int)(miles + 0.5);
- if (roundedMiles > 0 && roundedMiles < 1000) {
- snprintf(distStr, sizeof(distStr), "%sDistance:%dmi", leftSideSpacing, roundedMiles);
+ const int miles = (feet + 2640) / 5280; // rounded to nearest mile
+ if (miles > 0 && miles < 1000) {
+ snprintf(distStr, sizeof(distStr), "%sDistance:%dmi", leftSideSpacing, miles);
haveDistance = true;
}
}
} else {
- if (distanceKm < 1.0) {
- int meters = (int)(distanceKm * 1000);
- if (meters > 0 && meters < 1000) {
- snprintf(distStr, sizeof(distStr), "%sDistance:%dm", leftSideSpacing, meters);
- haveDistance = true;
- } else if (meters >= 1000) {
- snprintf(distStr, sizeof(distStr), "%sDistance:1km", leftSideSpacing);
- haveDistance = true;
- }
+ const int meters = static_cast(distanceMeters + 0.5f);
+ if (meters > 0 && meters < 1000) {
+ snprintf(distStr, sizeof(distStr), "%sDistance:%dm", leftSideSpacing, meters);
+ haveDistance = true;
} else {
- int km = (int)(distanceKm + 0.5);
+ const int km = (meters + 500) / 1000; // rounded to nearest km
if (km > 0 && km < 1000) {
snprintf(distStr, sizeof(distStr), "%sDistance:%dkm", leftSideSpacing, km);
haveDistance = true;
@@ -725,64 +1093,29 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
}
// --- Compass Rendering: landscape (wide) screens use the original side-aligned logic ---
- if (SCREEN_WIDTH > SCREEN_HEIGHT) {
- if (showCompass || statusLine1) {
+ if (showCompass || statusLine1) {
+ int16_t compassX = 0;
+ int16_t compassY = 0;
+ int16_t compassRadius = 0;
+ if (SCREEN_WIDTH > SCREEN_HEIGHT) {
const int16_t topY = getTextPositions(display)[1];
- const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
- const int16_t usableHeight = bottomY - topY - 5;
- int16_t compassRadius = usableHeight / 2;
- if (compassRadius < 8)
- compassRadius = 8;
- const int16_t compassX = x + SCREEN_WIDTH - compassRadius - 8;
- const int16_t compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
- const int16_t compassDiam = compassRadius * 2;
-
- display->drawCircle(compassX, compassY, compassRadius);
- if (showCompass) {
- CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
- CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearing);
- } else {
- drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
- }
- }
- // else show nothing
- } else {
- // Portrait or square: put compass at the bottom and centered, scaled to fit available space
- if (showCompass || statusLine1) {
- int yBelowContent = (line > 0 && line <= 5) ? (getTextPositions(display)[line - 1] + FONT_HEIGHT_SMALL + 2)
- : getTextPositions(display)[1];
- const int margin = 4;
-// --------- PATCH FOR EINK NAV BAR (ONLY CHANGE BELOW) -----------
+ computeLandscapeCompassPlacement(display, x, topY, &compassX, &compassY, &compassRadius);
+ } else {
+ const int yBelowContent = (line > 0 && line <= 5) ? (getTextPositions(display)[line - 1] + FONT_HEIGHT_SMALL + 2)
+ : getTextPositions(display)[1];
#if defined(USE_EINK)
const int iconSize = (currentResolution == ScreenResolution::High) ? 16 : 8;
const int navBarHeight = iconSize + 6;
#else
const int navBarHeight = 0;
#endif
- // --------- END PATCH FOR EINK NAV BAR -----------
- int availableHeight = SCREEN_HEIGHT - yBelowContent - navBarHeight - margin;
-
- if (availableHeight < FONT_HEIGHT_SMALL * 2)
+ if (!computeBottomCompassPlacement(display, x, yBelowContent, navBarHeight, 4, &compassX, &compassY,
+ &compassRadius)) {
return;
-
- int compassRadius = availableHeight / 2;
- if (compassRadius < 8)
- compassRadius = 8;
- if (compassRadius * 2 > SCREEN_WIDTH - 16)
- compassRadius = (SCREEN_WIDTH - 16) / 2;
-
- int compassX = x + SCREEN_WIDTH / 2;
- int compassY = yBelowContent + availableHeight / 2;
-
- display->drawCircle(compassX, compassY, compassRadius);
- if (showCompass) {
- graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
- graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, bearing);
- } else {
- drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
}
}
- // else show nothing
+ drawBearingCompassOrStatus(display, compassX, compassY, compassRadius, showCompass, myHeading, bearing, statusLine1,
+ statusLine2);
}
#endif
graphics::drawCommonFooter(display, x, y);
@@ -808,12 +1141,6 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
// === Content below header ===
- // Determine if we need to show 4 or 5 rows on the screen
- int rows = 4;
- if (!config.bluetooth.enabled) {
- rows = 5;
- }
-
// === First Row: Region / Channel Utilization and Uptime ===
bool origBold = config.display.heading_bold;
config.display.heading_bold = false;
@@ -877,13 +1204,15 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
// === Third Row: Channel Utilization Bluetooth Off (Only If Actually Off) ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
- snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
+ int chutil_percent = static_cast(airTime->channelUtilizationPercent() + 0.5f);
+ snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%d%%", chutil_percent);
int chUtil_x = (currentResolution == ScreenResolution::High) ? display->getStringWidth(chUtil) + 10
: display->getStringWidth(chUtil) + 5;
int chUtil_y = getTextPositions(display)[line] + 3;
int chutil_bar_width = (currentResolution == ScreenResolution::High) ? 100 : 50;
+ int chutil_bar_max_fill = chutil_bar_width - 2; // Account for border
if (!config.bluetooth.enabled) {
#if defined(USE_EINK)
chutil_bar_width = (currentResolution == ScreenResolution::High) ? 50 : 30;
@@ -896,50 +1225,36 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
if (!config.bluetooth.enabled) {
extraoffset = (currentResolution == ScreenResolution::High) ? 6 : 1;
}
- int chutil_percent = airTime->channelUtilizationPercent();
+ const int raw_chutil_percent = chutil_percent;
- int centerofscreen = SCREEN_WIDTH / 2;
- int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
- int starting_position = centerofscreen - total_line_content_width;
- if (!config.bluetooth.enabled) {
- starting_position = 0;
- }
+ // With BT disabled we pin this row left to make room for the extra "BT off" indicator.
+ const int starting_position = config.bluetooth.enabled ? x : 0;
display->drawString(starting_position, getTextPositions(display)[line], chUtil);
- // Force 56% or higher to show a full 100% bar, text would still show related percent.
+ // Force 61% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) {
chutil_percent = 100;
}
- // Weighting for nonlinear segments
- float milestone1 = 25;
- float milestone2 = 40;
- float weight1 = 0.45; // Weight for 0–25%
- float weight2 = 0.35; // Weight for 25–40%
- float weight3 = 0.20; // Weight for 40–100%
- float totalWeight = weight1 + weight2 + weight3;
-
- int seg1 = chutil_bar_width * (weight1 / totalWeight);
- int seg2 = chutil_bar_width * (weight2 / totalWeight);
- int seg3 = chutil_bar_width * (weight3 / totalWeight);
-
- int fillRight = 0;
-
- if (chutil_percent <= milestone1) {
- fillRight = (seg1 * (chutil_percent / milestone1));
- } else if (chutil_percent <= milestone2) {
- fillRight = seg1 + (seg2 * ((chutil_percent - milestone1) / (milestone2 - milestone1)));
- } else {
- fillRight = seg1 + seg2 + (seg3 * ((chutil_percent - milestone2) / (100 - milestone2)));
- }
+ int fillRight = computeChannelUtilizationFill(chutil_percent, chutil_bar_max_fill);
// Draw outline
display->drawRect(starting_position + chUtil_x, chUtil_y, chutil_bar_width, chutil_bar_height);
// Fill progress
if (fillRight > 0) {
- display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ uint16_t UtilizationFillColor = TFTPalette::Good;
+ if (raw_chutil_percent >= 60) {
+ UtilizationFillColor = TFTPalette::Bad;
+ } else if (raw_chutil_percent >= 35) {
+ UtilizationFillColor = TFTPalette::Medium;
+ }
+ setAndRegisterTFTColorRole(TFTColorRole::UtilizationFill, UtilizationFillColor, TFTPalette::Black,
+ starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
+#endif
+ display->fillRect(starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
}
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line],
@@ -970,9 +1285,8 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
if (SCREEN_WIDTH - UIRenderer::measureStringWithEmotes(display, combinedName) > 10) {
textWidth = UIRenderer::measureStringWithEmotes(display, combinedName);
nameX = (SCREEN_WIDTH - textWidth) / 2;
- UIRenderer::drawStringWithEmotes(
- display, nameX, ((rows == 4) ? getTextPositions(display)[line++] : getTextPositions(display)[line++]) + yOffset,
- combinedName, FONT_HEIGHT_SMALL, 1, false);
+ UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++] + yOffset, combinedName,
+ FONT_HEIGHT_SMALL, 1, false);
} else {
// === LongName Centered ===
textWidth = UIRenderer::measureStringWithEmotes(display, longName);
@@ -1148,6 +1462,9 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
// draw centered icon left to right and centered above the one line of app text
#if defined(M5STACK_UNITC6L)
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
+ if (gBootSplashBoldPass) {
+ display->drawXbm(x + (SCREEN_WIDTH - 50) / 2 + 1, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
+ }
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -1157,6 +1474,9 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
int msgX = x + (SCREEN_WIDTH - msgWidth) / 2;
int msgY = y;
display->drawString(msgX, msgY, upperMsg);
+ if (gBootSplashBoldPass) {
+ display->drawString(msgX + 1, msgY, upperMsg);
+ }
}
// Draw version and short name in bottom middle
char footer[64];
@@ -1169,6 +1489,10 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
int footerX = x + ((SCREEN_WIDTH - footerW) / 2);
UIRenderer::drawStringWithEmotes(display, footerX, y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, footer, FONT_HEIGHT_SMALL, 1,
false);
+ if (gBootSplashBoldPass) {
+ UIRenderer::drawStringWithEmotes(display, footerX + 1, y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, footer, FONT_HEIGHT_SMALL,
+ 1, false);
+ }
screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
@@ -1179,21 +1503,35 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *title = "meshtastic.org";
- display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
+ display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - 5, title);
+ if (gBootSplashBoldPass) {
+ display->drawString(x + getStringCenteredX(title) + 1, y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - 5, title);
+ }
display->setFont(FONT_SMALL);
// Draw region in upper left
- if (upperMsg)
- display->drawString(x + 0, y + 0, upperMsg);
+ if (upperMsg) {
+ display->drawString(x + 5, y + 5, upperMsg);
+ if (gBootSplashBoldPass) {
+ display->drawString(x + 6, y + 5, upperMsg);
+ }
+ }
// Draw version and short name in upper right
const char *version = xstr(APP_VERSION_SHORT);
- int versionX = x + SCREEN_WIDTH - display->getStringWidth(version);
- display->drawString(versionX, y + 0, version);
+ int versionX = x + SCREEN_WIDTH - display->getStringWidth(version) - 5;
+ display->drawString(versionX, y + 5, version);
+ if (gBootSplashBoldPass) {
+ display->drawString(versionX + 1, y + 5, version);
+ }
if (owner.short_name && owner.short_name[0]) {
const char *shortName = owner.short_name;
int shortNameW = UIRenderer::measureStringWithEmotes(display, shortName);
- int shortNameX = x + SCREEN_WIDTH - shortNameW;
- UIRenderer::drawStringWithEmotes(display, shortNameX, y + FONT_HEIGHT_SMALL, shortName, FONT_HEIGHT_SMALL, 1, false);
+ int shortNameX = x + SCREEN_WIDTH - shortNameW - 5;
+ UIRenderer::drawStringWithEmotes(display, shortNameX, y + 5 + FONT_HEIGHT_SMALL, shortName, FONT_HEIGHT_SMALL, 1, false);
+ if (gBootSplashBoldPass) {
+ UIRenderer::drawStringWithEmotes(display, shortNameX + 1, y + 5 + FONT_HEIGHT_SMALL, shortName, FONT_HEIGHT_SMALL, 1,
+ false);
+ }
}
screen->forceDisplay();
@@ -1201,6 +1539,20 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
#endif
}
+void UIRenderer::drawBootIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+{
+#if GRAPHICS_TFT_COLORING_ENABLED
+ // Meshtastic brand green background with black foreground text/icon on TFT startup screen.
+ static constexpr uint16_t kMeshtasticGreen = TFTPalette::rgb565(103, 234, 145);
+ setAndRegisterTFTColorRole(TFTColorRole::BootSplash, TFTPalette::Black, kMeshtasticGreen, x, y, SCREEN_WIDTH, SCREEN_HEIGHT);
+ gBootSplashBoldPass = true;
+#endif
+ drawIconScreen(upperMsg, display, state, x, y);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ gBootSplashBoldPass = false;
+#endif
+}
+
// ****************************
// * My Position Screen *
// ****************************
@@ -1328,45 +1680,23 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
int16_t compassRadius = usableHeight / 2;
if (compassRadius < 8)
compassRadius = 8;
- const int16_t compassDiam = compassRadius * 2;
const int16_t compassX = x + SCREEN_WIDTH - compassRadius - 8;
// Center vertically and nudge down slightly to keep "N" clear of header
const int16_t compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
- display->drawCircle(compassX, compassY, compassRadius);
- if (validHeading) {
- CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, -heading);
-
- // "N" label
- float northAngle = 0;
- if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
- northAngle = -heading;
- float radius = compassRadius;
- int16_t nX = compassX + (radius - 1) * sin(northAngle);
- int16_t nY = compassY - (radius - 1) * cos(northAngle);
- int16_t nLabelWidth = display->getStringWidth("N") + 2;
- int16_t nLabelHeightBox = FONT_HEIGHT_SMALL + 1;
-
- display->setColor(BLACK);
- display->fillRect(nX - nLabelWidth / 2, nY - nLabelHeightBox / 2, nLabelWidth, nLabelHeightBox);
- display->setColor(WHITE);
- display->setFont(FONT_SMALL);
- display->setTextAlignment(TEXT_ALIGN_CENTER);
- display->drawString(nX, nY - FONT_HEIGHT_SMALL / 2, "N");
- } else {
- drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
- }
+ drawDetailedCompassOrStatus(display, compassX, compassY, compassRadius, validHeading, heading, statusLine1,
+ statusLine2);
} else {
// Portrait or square: put compass at the bottom and centered, scaled to fit available space
// For E-Ink screens, account for navigation bar at the bottom!
- int yBelowContent = textPos[5] + FONT_HEIGHT_SMALL + 2;
- const int margin = 4;
- int availableHeight =
+ const int yBelowContent = textPos[5] + FONT_HEIGHT_SMALL + 2;
#if defined(USE_EINK)
- SCREEN_HEIGHT - yBelowContent - 24; // Leave extra space for nav bar on E-Ink
+ const int margin = 4;
+ int availableHeight = SCREEN_HEIGHT - yBelowContent - 24; // Leave extra space for nav bar on E-Ink
#else
- SCREEN_HEIGHT - yBelowContent - margin;
+ const int margin = 4;
+ int availableHeight = SCREEN_HEIGHT - yBelowContent - margin;
#endif
if (availableHeight < FONT_HEIGHT_SMALL * 2)
@@ -1381,29 +1711,8 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
int compassX = x + SCREEN_WIDTH / 2;
int compassY = yBelowContent + availableHeight / 2;
- display->drawCircle(compassX, compassY, compassRadius);
- if (validHeading) {
- CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, -heading);
-
- // "N" label
- float northAngle = 0;
- if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
- northAngle = -heading;
- float radius = compassRadius;
- int16_t nX = compassX + (radius - 1) * sin(northAngle);
- int16_t nY = compassY - (radius - 1) * cos(northAngle);
- int16_t nLabelWidth = display->getStringWidth("N") + 2;
- int16_t nLabelHeightBox = FONT_HEIGHT_SMALL + 1;
-
- display->setColor(BLACK);
- display->fillRect(nX - nLabelWidth / 2, nY - nLabelHeightBox / 2, nLabelWidth, nLabelHeightBox);
- display->setColor(WHITE);
- display->setFont(FONT_SMALL);
- display->setTextAlignment(TEXT_ALIGN_CENTER);
- display->drawString(nX, nY - FONT_HEIGHT_SMALL / 2, "N");
- } else {
- drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
- }
+ drawDetailedCompassOrStatus(display, compassX, compassY, compassRadius, validHeading, heading, statusLine1,
+ statusLine2);
}
}
#endif
@@ -1475,18 +1784,21 @@ void UIRenderer::drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *sta
#endif
// Navigation bar overlay implementation
-static int8_t lastFrameIndex = -1;
+static int16_t lastFrameIndex = -1;
static uint32_t lastFrameChangeTime = 0;
constexpr uint32_t ICON_DISPLAY_DURATION_MS = 2000;
// cppcheck-suppress constParameterPointer; signature must match OverlayCallback typedef from OLEDDisplayUi library
void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state)
{
- int currentFrame = state->currentFrame;
+ uint8_t frameToHighlight = state->currentFrame;
+ if (state->frameState == IN_TRANSITION && state->transitionFrameTarget < screen->indicatorIcons.size()) {
+ frameToHighlight = state->transitionFrameTarget;
+ }
// Detect frame change and record time
- if (currentFrame != lastFrameIndex) {
- lastFrameIndex = currentFrame;
+ if (frameToHighlight != lastFrameIndex) {
+ lastFrameIndex = frameToHighlight;
lastFrameChangeTime = millis();
}
@@ -1505,15 +1817,15 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
usableWidth = iconSize;
const size_t iconsPerPage = usableWidth / (iconSize + spacing);
- const size_t currentPage = currentFrame / iconsPerPage;
+ const size_t currentPage = frameToHighlight / iconsPerPage;
const size_t pageStart = currentPage * iconsPerPage;
const size_t pageEnd = min(pageStart + iconsPerPage, totalIcons);
const int totalWidth = (pageEnd - pageStart) * iconSize + (pageEnd - pageStart - 1) * spacing;
const int xStart = (SCREEN_WIDTH - totalWidth) / 2;
- bool navBarVisible = millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS;
- int y = navBarVisible ? (SCREEN_HEIGHT - iconSize - 1) : SCREEN_HEIGHT;
+ const bool navBarVisible = millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS;
+ const int y = navBarVisible ? (SCREEN_HEIGHT - iconSize - 1) : SCREEN_HEIGHT;
#if defined(USE_EINK)
// Only show bar briefly after switching frames
@@ -1544,25 +1856,54 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
// Pre-calculate bounding rect
const int rectX = xStart - 2 - bigOffset;
+ const int rectY = y - 2;
const int rectWidth = totalWidth + 4 + (bigOffset * 2);
const int rectHeight = iconSize + 6;
// Clear background and draw border
display->setColor(BLACK);
- display->fillRect(rectX + 1, y - 2, rectWidth - 2, rectHeight - 2);
+#if GRAPHICS_TFT_COLORING_ENABLED
+ // NavigationBar and NavigationArrow roles are fully defined in the theme table.
+ // We must call setTFTColorRole() before registerTFTColorRegion() because
+ // registerTFTColorRegion() snapshots colors from the roleColors[] working array,
+ // and loadThemeDefaults() isn't guaranteed to have run since boot.
+ const TFTThemeDef &theme = getActiveTheme();
+ const auto &navBarRole = theme.roles[static_cast(TFTColorRole::NavigationBar)];
+ const auto &navArrowRole = theme.roles[static_cast(TFTColorRole::NavigationArrow)];
+
+ setAndRegisterTFTColorRole(TFTColorRole::NavigationBar, navBarRole.onColor, navBarRole.offColor, rectX, rectY, rectWidth,
+ rectHeight);
+ setTFTColorRole(TFTColorRole::NavigationArrow, navArrowRole.onColor, navArrowRole.offColor);
+ display->fillRect(rectX, rectY, rectWidth, rectHeight);
+#else
+ // Keep legacy OLED behavior untouched.
+ display->fillRect(rectX + 1, rectY, rectWidth - 2, rectHeight - 2);
+ display->setColor(WHITE);
+ display->drawRect(rectX, rectY, rectWidth, rectHeight);
+#endif
+
+ // Icons are 1-bit glyphs and must be drawn with WHITE to set pixels.
display->setColor(WHITE);
- display->drawRect(rectX, y - 2, rectWidth, rectHeight);
// Icon drawing loop for the current page
for (size_t i = pageStart; i < pageEnd; ++i) {
const uint8_t *icon = screen->indicatorIcons[i];
const int x = xStart + (i - pageStart) * (iconSize + spacing);
- const bool isActive = (i == static_cast(currentFrame));
+ const bool isActive = (i == static_cast(frameToHighlight));
if (isActive) {
+#if GRAPHICS_TFT_COLORING_ENABLED
+ // Active icon inverts on TFT: white chip with black glyph.
+ // Keep the buffer visibly different too, so dirty-rect updates include this region.
+ registerTFTColorRegion(TFTColorRole::NavigationBar, x - 1, y - 1, iconSize + 2, iconSize + 2);
+ display->setColor(WHITE);
+ display->fillRect(x - 1, y - 1, iconSize + 2, iconSize + 2);
+ display->setColor(BLACK);
+#else
display->setColor(WHITE);
display->fillRect(x - 2, y - 2, iconSize + 4, iconSize + 4);
display->setColor(BLACK);
+#endif
}
if (currentResolution == ScreenResolution::High) {
@@ -1576,22 +1917,17 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
}
}
- // Compact arrow drawer
+ display->setColor(WHITE);
+
+ const int offset = (currentResolution == ScreenResolution::High) ? 3 : 1;
+ const int halfH = rectHeight / 2;
+ const int top = rectY + (rectHeight - halfH) / 2;
+ const int bottom = top + halfH - 1;
+ const int midY = top + (halfH / 2);
+ const int maxW = 4;
+
auto drawArrow = [&](bool rightSide) {
- display->setColor(WHITE);
-
- const int offset = (currentResolution == ScreenResolution::High) ? 3 : 1;
- const int halfH = rectHeight / 2;
-
- const int top = (y - 2) + (rectHeight - halfH) / 2;
- const int bottom = top + halfH - 1;
- const int midY = top + (halfH / 2);
-
- const int maxW = 4;
-
- // Determine left X coordinate
- int baseX = rightSide ? (rectX + rectWidth + offset) : // right arrow
- (rectX - offset - 1); // left arrow
+ int baseX = rightSide ? (rectX + rectWidth + offset) : (rectX - offset - 1);
for (int yy = top; yy <= bottom; yy++) {
int dist = abs(yy - midY);
@@ -1606,21 +1942,43 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
}
}
};
+
// Right arrow
- if (pageEnd < totalIcons) {
+ if (navBarVisible && pageEnd < totalIcons) {
+ int baseX = rectX + rectWidth + offset;
+ int regionX = baseX;
+
+#if GRAPHICS_TFT_COLORING_ENABLED
+ registerTFTColorRegion(TFTColorRole::NavigationArrow, regionX, top, maxW, halfH);
+#endif
+
drawArrow(true);
}
// Left arrow
- if (pageStart > 0) {
+ if (navBarVisible && pageStart > 0) {
+ int baseX = rectX - offset - 1;
+ int regionX = baseX - maxW + 1;
+
+#if GRAPHICS_TFT_COLORING_ENABLED
+ registerTFTColorRegion(TFTColorRole::NavigationArrow, regionX, top, maxW, halfH);
+#endif
+
drawArrow(false);
}
// Knock the corners off the square
+#if GRAPHICS_TFT_COLORING_ENABLED
+ // TFT corner mask
+ registerTFTColorRegion(TFTColorRole::NavigationArrow, rectX, rectY, 1, 1);
+ registerTFTColorRegion(TFTColorRole::NavigationArrow, rectX + rectWidth - 1, rectY, 1, 1);
+#else
+ // monochrome styling only
display->setColor(BLACK);
- display->drawRect(rectX, y - 2, 1, 1);
- display->drawRect(rectX + rectWidth - 1, y - 2, 1, 1);
+ display->drawRect(rectX, rectY, 1, 1);
+ display->drawRect(rectX + rectWidth - 1, rectY, 1, 1);
display->setColor(WHITE);
+#endif
}
void UIRenderer::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message)
diff --git a/src/graphics/draw/UIRenderer.h b/src/graphics/draw/UIRenderer.h
index a705d944d..0aeace42e 100644
--- a/src/graphics/draw/UIRenderer.h
+++ b/src/graphics/draw/UIRenderer.h
@@ -54,6 +54,8 @@ class UIRenderer
static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+ static void drawBootIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+
// Icon and screen drawing functions
static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
diff --git a/src/graphics/niche/Drivers/EInk/ED047TC1.cpp b/src/graphics/niche/Drivers/EInk/ED047TC1.cpp
new file mode 100644
index 000000000..2e283737c
--- /dev/null
+++ b/src/graphics/niche/Drivers/EInk/ED047TC1.cpp
@@ -0,0 +1,255 @@
+/*
+
+ NicheGraphics parallel E-Ink driver for the LilyGo T5-S3-ePaper-Pro (ED047TC1).
+
+ InkHUD buffer format : 1bpp, horizontal bytes, MSB = leftmost pixel, 1 = white
+ FastEPD buffer format: 1bpp, horizontal bytes, MSB = leftmost pixel, 1 = white
+
+ Both formats share the same pixel layout and polarity (1 = white, 0 = black).
+ The InkHUD safe-area buffer (944×523) is copied into the centre of the physical
+ 960×540 FastEPD buffer so content clears the panel's inactive edge border.
+ See ED047TC1.h for the H_OFFSET_BYTES / V_OFFSET_TOP / V_OFFSET_BOTTOM constants.
+
+*/
+
+// Ruler diagnostic — uncomment to draw calibration lines at each physical edge.
+// #define EINK_EDGE_LINES
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+#ifdef T5_S3_EPAPER_PRO
+
+#include "./ED047TC1.h"
+
+#include "FastEPD.h"
+#include "configuration.h"
+
+using namespace NicheGraphics::Drivers;
+
+#if defined(T5_S3_EPAPER_PRO_V2)
+// FastEPD helper symbols are defined in FastEPD.inl with C++ linkage.
+extern void bbepPCA9535DigitalWrite(uint8_t pin, uint8_t value);
+extern uint8_t bbepPCA9535DigitalRead(uint8_t pin);
+extern int bbepI2CWrite(unsigned char iAddr, unsigned char *pData, int iLen);
+extern int bbepI2CReadRegister(unsigned char iAddr, unsigned char u8Register, unsigned char *pData, int iLen);
+#endif
+
+namespace
+{
+#if defined(T5_S3_EPAPER_PRO_V2)
+// FastEPD default V2 power callback blocks forever waiting for PWRGOOD.
+// Replace it with a timeout-safe version so boot never deadlocks.
+int safeEPDiyV7EinkPower(void *pBBEP, int bOn)
+{
+ static bool warnedPgood = false;
+ static bool warnedTpsPg = false;
+ static bool warnedTpsWrite = false;
+
+ FASTEPDSTATE *pState = static_cast(pBBEP);
+ if (!pState) {
+ return BBEP_ERROR_BAD_PARAMETER;
+ }
+
+ if (bOn == pState->pwr_on) {
+ return BBEP_SUCCESS;
+ }
+
+ if (bOn) {
+ bbepPCA9535DigitalWrite(8, 1); // OE on
+ bbepPCA9535DigitalWrite(9, 1); // GMOD on
+ bbepPCA9535DigitalWrite(13, 1); // WAKEUP on
+ bbepPCA9535DigitalWrite(11, 1); // PWRUP on
+ bbepPCA9535DigitalWrite(12, 1); // VCOM CTRL on
+ delay(1);
+
+ const uint32_t pgoodStart = millis();
+ bool pgoodSeen = false;
+ while (!bbepPCA9535DigitalRead(14)) { // CFG_PIN_PWRGOOD
+ if ((millis() - pgoodStart) > 1200) {
+ if (!warnedPgood) {
+ LOG_WARN("ED047TC1: PWRGOOD timeout, continuing with fallback power-on path");
+ warnedPgood = true;
+ }
+ break;
+ }
+ delay(1);
+ }
+ if (bbepPCA9535DigitalRead(14)) {
+ pgoodSeen = true;
+ }
+
+ uint8_t ucTemp[4] = {0};
+ ucTemp[0] = 0x01; // TPS_REG_ENABLE
+ ucTemp[1] = 0x3f; // enable rails
+ const int tpsEnableRc = bbepI2CWrite(0x68, ucTemp, 2);
+
+ const int vcom = pState->iVCOM / -10;
+ ucTemp[0] = 3; // VCOM registers 3+4 (L + H)
+ ucTemp[1] = static_cast(vcom);
+ ucTemp[2] = static_cast(vcom >> 8);
+ const int tpsVcomRc = bbepI2CWrite(0x68, ucTemp, 3);
+ if ((tpsEnableRc == 0 || tpsVcomRc == 0) && !warnedTpsWrite) {
+ LOG_WARN("ED047TC1: TPS write did not ACK, continuing with fallback");
+ warnedTpsWrite = true;
+ }
+
+ int iTimeout = 0;
+ uint8_t u8Value = 0;
+ while (iTimeout < 220 && ((u8Value & 0xfa) != 0xfa)) {
+ bbepI2CReadRegister(0x68, 0x0F, &u8Value, 1); // TPS_REG_PG
+ iTimeout++;
+ delay(1);
+ }
+ if (iTimeout >= 220 && !warnedTpsPg) {
+ if (pgoodSeen) {
+ LOG_WARN("ED047TC1: TPS power-good register timeout, panel may still work");
+ } else {
+ LOG_WARN("ED047TC1: TPS power-good register timeout after PWRGOOD fallback");
+ }
+ warnedTpsPg = true;
+ }
+
+ pState->pwr_on = 1;
+ } else {
+ bbepPCA9535DigitalWrite(8, 0); // OE off
+ bbepPCA9535DigitalWrite(9, 0); // GMOD off
+ bbepPCA9535DigitalWrite(11, 0); // PWRUP off
+ bbepPCA9535DigitalWrite(12, 0); // VCOM CTRL off
+ delay(1);
+ bbepPCA9535DigitalWrite(13, 0); // WAKEUP off
+ pState->pwr_on = 0;
+ }
+
+ return BBEP_SUCCESS;
+}
+#endif
+
+class SafeFastEPD : public FASTEPD
+{
+ public:
+ void installSafePowerHandler()
+ {
+#if defined(T5_S3_EPAPER_PRO_V2)
+ _state.pfnEinkPower = safeEPDiyV7EinkPower;
+#endif
+ }
+};
+} // namespace
+
+void ED047TC1::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst)
+{
+ // Parallel display — SPI parameters are not used
+ (void)spi;
+ (void)pin_dc;
+ (void)pin_cs;
+ (void)pin_busy;
+ (void)pin_rst;
+
+ SafeFastEPD *safeEpaper = new SafeFastEPD;
+ epaper = safeEpaper;
+
+ int initRc = BBEP_ERROR_BAD_PARAMETER;
+#if defined(T5_S3_EPAPER_PRO_V1)
+ initRc = epaper->initPanel(BB_PANEL_LILYGO_T5PRO, 28000000);
+#elif defined(T5_S3_EPAPER_PRO_V2)
+ initRc = epaper->initPanel(BB_PANEL_LILYGO_T5PRO_V2, 28000000);
+ // Initialize all PCA9535 port-0 pins as outputs / HIGH
+ for (int i = 0; i < 8; i++) {
+ epaper->ioPinMode(i, OUTPUT);
+ epaper->ioWrite(i, HIGH);
+ }
+ // On this board, the physical side key is labeled IO48; electrically it maps to PCA9535 IO12 (bit 2 on port-1).
+ // FastEPD's generic V7 init drives 8..13 as outputs; force IO12 back to input
+ // so variant touch-control polling can read the key reliably.
+ epaper->ioPinMode(10, INPUT);
+#else
+#error "ED047TC1 driver: unsupported variant — define T5_S3_EPAPER_PRO_V1 or T5_S3_EPAPER_PRO_V2"
+#endif
+
+ if (initRc != BBEP_SUCCESS) {
+ LOG_ERROR("ED047TC1 initPanel failed rc=%d", initRc);
+ return;
+ }
+
+ safeEpaper->installSafePowerHandler();
+
+ const int modeRc = epaper->setMode(BB_MODE_1BPP);
+ if (modeRc != BBEP_SUCCESS) {
+ LOG_WARN("ED047TC1 setMode failed rc=%d", modeRc);
+ }
+
+ const int clearRc = epaper->clearWhite();
+ if (clearRc != BBEP_SUCCESS) {
+ LOG_WARN("ED047TC1 clearWhite failed rc=%d", clearRc);
+ }
+
+ const int fullRc = epaper->fullUpdate(true); // Blocking initial clear
+ if (fullRc != BBEP_SUCCESS) {
+ LOG_WARN("ED047TC1 initial fullUpdate failed rc=%d", fullRc);
+ }
+}
+
+void ED047TC1::update(uint8_t *imageData, UpdateTypes type)
+{
+ if (!epaper)
+ return;
+
+ // InkHUD renders into a DISPLAY_WIDTH × DISPLAY_HEIGHT safe-area buffer.
+ // We need to place that into the centre of the physical 960×540 FastEPD buffer,
+ // leaving blank margins at every edge to avoid the panel's inactive border.
+ const uint32_t srcRowBytes = (DISPLAY_WIDTH + 7) / 8; // bytes per row in InkHUD buffer (118)
+ const uint32_t dstRowBytes = (960 + 7) / 8; // bytes per row in physical buffer (120)
+ const uint32_t dstTotalRows = 540;
+
+ uint8_t *cur = epaper->currentBuffer();
+
+ // Fill physical buffer with white (0xFF = white in FastEPD 1bpp)
+ memset(cur, 0xFF, dstRowBytes * dstTotalRows);
+
+ // Copy each InkHUD row into the physical buffer with horizontal + vertical offsets
+ for (uint32_t row = 0; row < DISPLAY_HEIGHT; row++) {
+ const uint8_t *srcRow = imageData + row * srcRowBytes;
+ uint8_t *dstRow = cur + (row + V_OFFSET_TOP) * dstRowBytes + H_OFFSET_BYTES;
+ memcpy(dstRow, srcRow, srcRowBytes);
+ }
+
+#ifdef EINK_EDGE_LINES
+ // Draw a 1px black box at the exact boundary of the safe area within the
+ // physical buffer. If the margins are correct, all 4 lines should be
+ // fully visible and right at the edge of the usable display area.
+
+ auto setPixelBlack = [&](uint32_t col, uint32_t row) { cur[row * dstRowBytes + col / 8] &= ~(0x80 >> (col % 8)); };
+
+ const uint32_t safeX = H_OFFSET_BYTES * 8;
+ const uint32_t safeY = V_OFFSET_TOP;
+ const uint32_t safeW = DISPLAY_WIDTH;
+ const uint32_t safeH = DISPLAY_HEIGHT;
+
+ // Top edge: horizontal line at safeY
+ for (uint32_t col = safeX; col < safeX + safeW; col++)
+ setPixelBlack(col, safeY);
+
+ // Bottom edge: horizontal line at safeY + safeH - 1
+ for (uint32_t col = safeX; col < safeX + safeW; col++)
+ setPixelBlack(col, safeY + safeH - 1);
+
+ // Left edge: vertical line at safeX
+ for (uint32_t row = safeY; row < safeY + safeH; row++)
+ setPixelBlack(safeX, row);
+
+ // Right edge: vertical line at safeX + safeW - 1
+ for (uint32_t row = safeY; row < safeY + safeH; row++)
+ setPixelBlack(safeX + safeW - 1, row);
+#endif
+
+ if (type == FULL) {
+ epaper->fullUpdate(CLEAR_SLOW, false);
+ epaper->backupPlane(); // Sync pPrevious so next partialUpdate has a correct baseline
+ } else {
+ // FAST: true partial update - compares pCurrent vs pPrevious and only applies
+ // update waveform to rows that changed. partialUpdate() updates pPrevious.
+ epaper->partialUpdate(false, 0, dstTotalRows - 1);
+ }
+}
+
+#endif // T5_S3_EPAPER_PRO
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/niche/Drivers/EInk/ED047TC1.h b/src/graphics/niche/Drivers/EInk/ED047TC1.h
new file mode 100644
index 000000000..3540481e7
--- /dev/null
+++ b/src/graphics/niche/Drivers/EInk/ED047TC1.h
@@ -0,0 +1,90 @@
+/*
+
+ E-Ink display driver adapter
+ - ED047TC1 (via FastEPD library)
+ - Manufacturer: E Ink / used in LilyGo T5-E-Paper-S3-Pro
+ - Size: 4.7 inch
+ - Physical resolution: 960px x 540px
+ - Interface: 8-bit parallel (NOT SPI)
+
+ Unlike the other NicheGraphics EInk drivers, this one drives a parallel e-paper
+ panel via the FastEPD library. SPI parameters passed to begin() are ignored.
+
+ The ED047TC1 panel has an inactive pixel border on all four edges (~4–8 physical
+ pixels). DISPLAY_WIDTH / DISPLAY_HEIGHT expose a reduced "safe area" to InkHUD so
+ that content is never drawn into this dead zone. The update() method copies the
+ InkHUD frame buffer into the centre of the larger physical 960×540 buffer, using
+ H_OFFSET_BYTES (horizontal, whole bytes = 8 pixels per byte),
+ V_OFFSET_TOP and V_OFFSET_BOTTOM (vertical, pixel rows) to position it.
+
+ Changing these constants shifts content inward from each physical edge:
+ H_OFFSET_BYTES = 1 → 8px left margin, 8px right margin (960 – 8 – 8 = 944)
+ V_OFFSET_TOP = 9 → 9px top margin (asymmetric: top ≠ bottom)
+ V_OFFSET_BOTTOM = 8 → 8px bottom margin (540 – 9 – 8 = 523)
+
+*/
+
+#pragma once
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+#include "configuration.h"
+
+#include "./EInk.h"
+
+// Forward declare to avoid pulling FastEPD into all translation units
+class FASTEPD;
+
+namespace NicheGraphics::Drivers
+{
+
+class ED047TC1 : public EInk
+{
+ // Safe-area dimensions exposed to InkHUD (physical panel is 960×540).
+ //
+ // The ED047TC1 has an inactive pixel border on all physical edges.
+ // The physical buffer coordinates do NOT directly match the visual orientation
+ // due to FastEPD's portrait scan direction and InkHUD's rotation=3 (270° CW):
+ //
+ // Physical buffer Visual on device (rotation=3)
+ // ───────────────── ──────────────────────────────
+ // Physical LEFT cols → Visual TOP edge
+ // Physical RIGHT cols → Visual BOTTOM edge
+ // Physical TOP rows → Visual RIGHT edge
+ // Physical BOTTOM rows → Visual LEFT edge
+ //
+ // Offset constants shift the InkHUD safe-area away from each physical dead zone:
+ // H_OFFSET_BYTES : whole bytes from physical left (8px per byte, affects visual TOP)
+ // Physical right margin = 960 − H_OFFSET_BYTES×8 − DISPLAY_WIDTH (affects visual BOTTOM)
+ // V_OFFSET_TOP : pixel rows from physical top (affects visual RIGHT)
+ // V_OFFSET_BOTTOM: pixel rows from physical bottom (affects visual LEFT)
+ //
+ // Calibrated by flashing a 1px border box and adjusting until all 4 sides are visible.
+
+ static constexpr uint16_t DISPLAY_WIDTH = 944; // 960 − H_OFFSET_BYTES×8 − right_margin (8+8 = 16px)
+ static constexpr uint16_t DISPLAY_HEIGHT = 523; // 540 − V_OFFSET_TOP − V_OFFSET_BOTTOM (9+8 = 17px)
+
+ static constexpr uint8_t H_OFFSET_BYTES = 1; // visual TOP : 8px physical left margin
+ // visual BOTTOM: 960−8−944=8px physical right margin
+ static constexpr uint8_t V_OFFSET_TOP = 9; // visual RIGHT : CONFIRMED OK
+ static constexpr uint8_t V_OFFSET_BOTTOM = 8; // visual LEFT : 8px physical bottom margin
+
+ static constexpr UpdateTypes supported = static_cast(FULL | FAST);
+
+ public:
+ ED047TC1() : EInk(DISPLAY_WIDTH, DISPLAY_HEIGHT, supported) {}
+
+ // EInk interface — SPI params are not used for this parallel display
+ void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst = 0xFF) override;
+ void update(uint8_t *imageData, UpdateTypes type) override;
+
+ protected:
+ bool isUpdateDone() override { return true; } // FastEPD updates are blocking
+
+ private:
+ FASTEPD *epaper = nullptr;
+};
+
+} // namespace NicheGraphics::Drivers
+
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
diff --git a/src/graphics/niche/Fonts/FreeSans18pt_Win1253.h b/src/graphics/niche/Fonts/FreeSans18pt_Win1253.h
new file mode 100644
index 000000000..9b29f32b5
--- /dev/null
+++ b/src/graphics/niche/Fonts/FreeSans18pt_Win1253.h
@@ -0,0 +1,1467 @@
+// trunk-ignore-all(clang-format)
+#pragma once
+/* PROPERTIES
+
+FONT_NAME FreeSans18pt_Win1253
+*/
+const uint8_t FreeSans18pt_Win1253Bitmaps[] PROGMEM = {
+ 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x23, 0x00,
+ 0x00, 0x00, 0x10, 0x80, 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x04, 0x30,
+ 0x00, 0x00, 0x02, 0x18, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x01, 0x84,
+ 0x00, 0x00, 0x01, 0x82, 0x00, 0x00, 0x01, 0x83, 0x00, 0x00, 0x01, 0x81,
+ 0x80, 0x00, 0x03, 0x80, 0xBF, 0xC0, 0x03, 0x00, 0x7C, 0x30, 0x03, 0x00,
+ 0x60, 0x18, 0x01, 0x00, 0x20, 0x04, 0x01, 0x80, 0x10, 0x06, 0x7F, 0xC0,
+ 0x0C, 0x06, 0x30, 0x00, 0x07, 0xFF, 0xD8, 0x00, 0x03, 0xE0, 0x2C, 0x00,
+ 0x01, 0x80, 0x1E, 0x00, 0x01, 0xC0, 0x0F, 0x00, 0x01, 0xA0, 0x05, 0x80,
+ 0x03, 0x9C, 0x06, 0xC0, 0x03, 0x07, 0xFE, 0x60, 0x00, 0x06, 0x01, 0xB0,
+ 0x00, 0x03, 0x00, 0x58, 0x00, 0x01, 0x80, 0x2C, 0x00, 0x00, 0xC0, 0x16,
+ 0x00, 0x00, 0x3E, 0x1B, 0xF8, 0x00, 0x3F, 0xF9, 0xFF, 0x80, 0x10, 0x10,
+ 0x00, 0x60, 0x08, 0x08, 0x00, 0x1E, 0x06, 0x04, 0x00, 0x03, 0xFF, 0xFE,
+ 0x00, 0x00, 0x06, 0x1E, 0x00, 0x00, 0x00, 0x79, 0xF0, 0x00, 0x07, 0xFF,
+ 0xEC, 0x00, 0x0E, 0x03, 0x02, 0x00, 0x0C, 0x01, 0x01, 0x0F, 0xFC, 0x00,
+ 0x80, 0x87, 0xE0, 0x00, 0x7F, 0xF3, 0x00, 0x00, 0x1E, 0x0D, 0x80, 0x00,
+ 0x18, 0x02, 0xC0, 0x00, 0x0C, 0x01, 0x60, 0x00, 0x06, 0x00, 0xB0, 0x00,
+ 0x03, 0x80, 0xD8, 0x00, 0x60, 0xFF, 0xCC, 0x00, 0x1C, 0xC0, 0x36, 0x00,
+ 0x03, 0x40, 0x0B, 0x00, 0x00, 0xE0, 0x07, 0x80, 0x00, 0x38, 0x03, 0xC0,
+ 0x00, 0x1F, 0x81, 0x60, 0x00, 0x07, 0xFF, 0xBF, 0xE0, 0x06, 0x01, 0x00,
+ 0x30, 0x02, 0x00, 0xC0, 0x08, 0x01, 0x00, 0x20, 0x06, 0x00, 0xC0, 0x30,
+ 0x01, 0x80, 0x3F, 0x18, 0x00, 0x70, 0x13, 0xF8, 0x00, 0x0C, 0x0C, 0x00,
+ 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x30, 0x80,
+ 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x02, 0x18,
+ 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x46,
+ 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x07,
+ 0xFC, 0x00, 0x00, 0x07, 0x80, 0xE0, 0x00, 0x01, 0x80, 0x03, 0x00, 0x00,
+ 0xC0, 0x00, 0x18, 0x00, 0x20, 0x00, 0x01, 0x80, 0x08, 0x00, 0x00, 0x08,
+ 0x02, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x18, 0x10, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x00, 0x10, 0x80, 0x00, 0x00, 0x02, 0x20, 0x00,
+ 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x80, 0x70, 0x07, 0x00, 0xA0,
+ 0x31, 0x01, 0x18, 0x0C, 0x04, 0x30, 0x61, 0x01, 0x80, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8, 0x00,
+ 0x54, 0x18, 0x2A, 0x00, 0x12, 0x83, 0x05, 0x40, 0x02, 0xA0, 0x50, 0x00,
+ 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x02, 0x40, 0x10, 0x01, 0x00, 0x44,
+ 0x03, 0x80, 0xE0, 0x10, 0x80, 0x0F, 0xE0, 0x02, 0x08, 0x00, 0x00, 0x00,
+ 0x81, 0x00, 0x00, 0x00, 0x30, 0x10, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x10, 0x00, 0x00, 0x40, 0x01, 0x80, 0x00, 0x30, 0x00, 0x0C,
+ 0x00, 0x18, 0x00, 0x00, 0xF0, 0x1E, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00,
+ 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00, 0x00, 0x70, 0x1C, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0xC0, 0x00, 0x00, 0x60, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00,
+ 0x01, 0x80, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x08, 0x00, 0x00, 0x02,
+ 0x00, 0x06, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x0C, 0x00, 0x06, 0x10, 0x00,
+ 0x86, 0x00, 0x00, 0xC2, 0x00, 0x22, 0x00, 0x00, 0x08, 0x80, 0x11, 0x00,
+ 0x00, 0x01, 0x10, 0x04, 0x40, 0x00, 0x00, 0x44, 0x01, 0x03, 0xE0, 0x0F,
+ 0x81, 0x00, 0x81, 0x8C, 0x06, 0x30, 0x20, 0x20, 0x41, 0x01, 0x04, 0x08,
+ 0x08, 0x00, 0x00, 0x00, 0x02, 0x03, 0xE0, 0x00, 0x00, 0x0F, 0x83, 0x98,
+ 0x00, 0x00, 0x02, 0x31, 0x86, 0x00, 0x00, 0x00, 0xC2, 0xC1, 0x00, 0x00,
+ 0x00, 0x10, 0xE0, 0x4F, 0x80, 0x03, 0xE4, 0x18, 0x32, 0x1F, 0xFF, 0x09,
+ 0x06, 0x08, 0xE0, 0x00, 0x0E, 0x61, 0x4E, 0x1F, 0xFF, 0xFF, 0x0C, 0x8F,
+ 0x87, 0xFF, 0xFF, 0xC3, 0xC0, 0x20, 0xFF, 0xFF, 0xE0, 0x80, 0x0C, 0x0F,
+ 0x83, 0xE0, 0x60, 0x01, 0x81, 0xC0, 0x70, 0x30, 0x00, 0x20, 0x0F, 0xE0,
+ 0x18, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x00,
+ 0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x03, 0x80, 0x03, 0x00, 0x00, 0x00,
+ 0x3C, 0x07, 0x80, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0C,
+ 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x08, 0x40, 0x00, 0x00, 0x71, 0xC0, 0x00, 0x03, 0x9F, 0x0C, 0x00, 0x00,
+ 0xFA, 0x30, 0x07, 0x00, 0x11, 0xC3, 0x01, 0xB0, 0x02, 0x18, 0x30, 0x62,
+ 0x00, 0x61, 0x83, 0x08, 0xC0, 0x06, 0x18, 0x31, 0x18, 0x01, 0xE1, 0x83,
+ 0x23, 0x00, 0x66, 0x18, 0x34, 0x20, 0x08, 0x61, 0x83, 0x84, 0x01, 0x86,
+ 0x18, 0x38, 0xC0, 0x18, 0x61, 0x83, 0x08, 0x03, 0x86, 0x10, 0x41, 0x00,
+ 0xF8, 0x60, 0x18, 0x30, 0x31, 0x86, 0x02, 0x02, 0x06, 0x18, 0x40, 0x40,
+ 0x60, 0xC1, 0x80, 0x08, 0x04, 0x0C, 0x18, 0x01, 0x00, 0x80, 0xC1, 0x00,
+ 0x20, 0x18, 0x0C, 0x00, 0x06, 0x01, 0x10, 0xC0, 0x00, 0xC0, 0x23, 0x8C,
+ 0x00, 0x0C, 0x06, 0x10, 0xC0, 0x00, 0x01, 0xE0, 0x0C, 0x00, 0x00, 0x37,
+ 0x00, 0xC0, 0x00, 0x04, 0x60, 0x0C, 0x00, 0x01, 0x80, 0x00, 0xC0, 0x00,
+ 0x20, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x07,
+ 0x00, 0xC0, 0x00, 0x00, 0x7F, 0xF0, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00,
+ 0x00, 0x0C, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0xD0, 0x00,
+ 0x00, 0xE0, 0x12, 0x00, 0x00, 0x16, 0x04, 0x60, 0x00, 0x02, 0x71, 0x8C,
+ 0x00, 0x00, 0x63, 0x20, 0x80, 0x00, 0x0C, 0x1F, 0xD0, 0x78, 0x00, 0x8E,
+ 0x0F, 0xFD, 0x00, 0x12, 0x00, 0x30, 0x60, 0x02, 0x80, 0x02, 0x08, 0x00,
+ 0x60, 0x00, 0x22, 0x00, 0x7C, 0x00, 0x06, 0xC0, 0xFD, 0x00, 0x00, 0x50,
+ 0x30, 0x20, 0x00, 0x0C, 0x06, 0x08, 0x00, 0x00, 0x80, 0x71, 0x00, 0x00,
+ 0x18, 0x03, 0x20, 0x00, 0x02, 0xC0, 0x3C, 0x00, 0x00, 0x4C, 0x01, 0x80,
+ 0x00, 0x08, 0x60, 0x10, 0x00, 0x01, 0x06, 0x07, 0x00, 0x00, 0x40, 0xC0,
+ 0xA0, 0x00, 0x0B, 0xF0, 0x36, 0x00, 0x03, 0xE0, 0x04, 0x40, 0x00, 0x60,
+ 0x01, 0x04, 0x00, 0x14, 0x00, 0x60, 0xC0, 0x04, 0x80, 0x0B, 0xFF, 0x07,
+ 0x10, 0x01, 0xF0, 0xBF, 0xC3, 0x00, 0x00, 0x10, 0x4C, 0x60, 0x00, 0x03,
+ 0x18, 0xE4, 0x00, 0x00, 0x62, 0x06, 0x80, 0x00, 0x0C, 0xC0, 0x70, 0x00,
+ 0x00, 0xB0, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x1F, 0xC0, 0x00, 0x00, 0x03, 0x83, 0x80, 0x00, 0x00,
+ 0x30, 0x06, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x60,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x07, 0xC0,
+ 0x00, 0x60, 0x00, 0xC3, 0x80, 0x01, 0x00, 0x0C, 0x04, 0x00, 0x08, 0x00,
+ 0xC0, 0x30, 0x00, 0x40, 0x04, 0x00, 0x80, 0x02, 0x00, 0x20, 0x04, 0x00,
+ 0x18, 0x0F, 0x00, 0x00, 0x01, 0xF1, 0xC0, 0x00, 0x00, 0x00, 0xC8, 0x00,
+ 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00,
+ 0x00, 0x03, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x30, 0x03, 0x00, 0x38, 0x03, 0x80,
+ 0x38, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x1E, 0x01, 0xE0, 0x1E, 0x00, 0xE0,
+ 0x0E, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x80, 0x30, 0x00, 0x00, 0x1C, 0x03, 0x80, 0x00, 0x01, 0xE0,
+ 0x3C, 0x00, 0x00, 0x0F, 0x01, 0xC0, 0x00, 0x00, 0x70, 0x0E, 0x00, 0x00,
+ 0x00, 0x00, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x1C, 0x0C, 0x00, 0x00, 0x00,
+ 0x70, 0x02, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x01, 0x80, 0x00,
+ 0x80, 0x00, 0x01, 0x80, 0x00, 0x40, 0x00, 0x03, 0x00, 0x00, 0x40, 0x00,
+ 0x3F, 0x00, 0x00, 0x20, 0x00, 0xE1, 0xC0, 0x00, 0x20, 0x01, 0x80, 0x60,
+ 0x00, 0x20, 0x01, 0x00, 0x20, 0x00, 0x20, 0x02, 0x00, 0x10, 0x00, 0x20,
+ 0x02, 0x00, 0x10, 0x00, 0x20, 0x06, 0x00, 0x00, 0x00, 0x38, 0x1E, 0x00,
+ 0x00, 0x00, 0x0C, 0x70, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00,
+ 0x03, 0x80, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x01, 0x80,
+ 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00,
+ 0x00, 0x02, 0x60, 0x00, 0x00, 0x00, 0x02, 0x18, 0x20, 0x00, 0x00, 0x0C,
+ 0x0F, 0xF0, 0x00, 0x00, 0x70, 0x00, 0x0F, 0xFC, 0x00, 0x80, 0x00, 0x03,
+ 0xE7, 0x03, 0x00, 0x00, 0x00, 0x01, 0xFC, 0x00, 0x01, 0xF0, 0x00, 0x1F,
+ 0x00, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x39, 0xDC, 0x07, 0x3B, 0x80, 0xF2,
+ 0xDC, 0x1E, 0x5B, 0x83, 0xB4, 0xEC, 0x6E, 0x9D, 0x8D, 0x38, 0xCD, 0x93,
+ 0x19, 0x9E, 0x30, 0xCB, 0xA3, 0x19, 0x64, 0x31, 0x69, 0xC7, 0x3B, 0x8C,
+ 0x52, 0x71, 0x8B, 0x4F, 0x96, 0x94, 0x61, 0x93, 0x8F, 0xA7, 0x18, 0x63,
+ 0xA3, 0x0D, 0xC6, 0x18, 0xE4, 0xC3, 0x19, 0x86, 0x39, 0x68, 0x87, 0x31,
+ 0x8E, 0x5A, 0x71, 0xCB, 0x53, 0x96, 0x9C, 0x62, 0xD1, 0x25, 0xA7, 0x18,
+ 0x64, 0xE2, 0x69, 0xC6, 0x18, 0xE8, 0xC4, 0x70, 0x86, 0x39, 0x70, 0xD0,
+ 0xE1, 0x8E, 0x5A, 0x21, 0xE0, 0xE2, 0xD2, 0x9C, 0x62, 0x81, 0xA4, 0xE3,
+ 0x08, 0xB7, 0x01, 0x38, 0xC3, 0x19, 0x34, 0x01, 0x30, 0xC7, 0x2E, 0x30,
+ 0x01, 0x31, 0xCB, 0x4C, 0x40, 0x01, 0x72, 0xD3, 0x8D, 0x00, 0x03, 0xB4,
+ 0xE3, 0x1C, 0x00, 0x03, 0x38, 0xC3, 0x38, 0x00, 0x03, 0x30, 0xC7, 0x60,
+ 0x00, 0x01, 0x31, 0xCB, 0x00, 0x00, 0x01, 0x72, 0x54, 0x00, 0x00, 0x01,
+ 0xB4, 0x70, 0x00, 0x00, 0x01, 0x18, 0x40, 0x00, 0x00, 0x01, 0x93, 0x00,
+ 0x00, 0x00, 0x01, 0xBC, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x38, 0x00,
+ 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x01, 0xFE,
+ 0x00, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x01,
+ 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFC, 0x00,
+ 0x03, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFE,
+ 0x00, 0x07, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF,
+ 0xFF, 0xE0, 0x07, 0xE1, 0xF8, 0xFC, 0x00, 0xF8, 0x1E, 0x0F, 0x80, 0x3E,
+ 0x71, 0x98, 0xF8, 0x1F, 0xCF, 0x37, 0x9F, 0xC7, 0xF9, 0xC6, 0x63, 0xFC,
+ 0xFF, 0x01, 0xE0, 0x7F, 0xBF, 0xF0, 0x7C, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80,
+ 0x03, 0xFF, 0xBF, 0xF0, 0x00, 0x7F, 0xE7, 0xFE, 0x00, 0x1F, 0xFC, 0x7F,
+ 0xE0, 0x03, 0xFF, 0x0F, 0xFE, 0x00, 0xFF, 0xC0, 0xFF, 0xF0, 0x7F, 0xF0,
+ 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF,
+ 0x00, 0x00, 0x07, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x0C, 0x20, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00,
+ 0x00, 0x20, 0x80, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x00, 0x7C, 0x00,
+ 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x1C, 0x07, 0x00, 0x00, 0x06, 0x00,
+ 0x30, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x43, 0x00, 0x30, 0x00, 0x18,
+ 0xC0, 0x03, 0x00, 0x02, 0x30, 0x00, 0x20, 0x00, 0x44, 0x00, 0x04, 0x00,
+ 0x11, 0x80, 0x00, 0xC0, 0x02, 0x20, 0x00, 0x08, 0x00, 0x40, 0x00, 0x01,
+ 0x00, 0x08, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x04, 0x00, 0x20, 0x00,
+ 0x00, 0x80, 0x0C, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x03, 0x00, 0x20,
+ 0x00, 0x00, 0x20, 0x0D, 0xFF, 0xFE, 0x04, 0x01, 0xE0, 0x00, 0x00, 0x40,
+ 0x40, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x44, 0x07, 0xFF, 0xFC,
+ 0x04, 0xFF, 0xF8, 0x0F, 0xFE, 0xBF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xE0,
+ 0x3F, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xC7, 0xFF, 0xC1, 0xFF, 0xF0, 0x3F,
+ 0xFC, 0x7F, 0xF8, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x07, 0xFC, 0x00,
+ 0x00, 0x07, 0x00, 0xF0, 0x00, 0x03, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x00,
+ 0x18, 0x00, 0x20, 0x00, 0x00, 0x80, 0x08, 0x00, 0x00, 0x08, 0x02, 0x00,
+ 0x00, 0x00, 0x80, 0x82, 0x00, 0x02, 0x08, 0x11, 0xC0, 0x00, 0x71, 0x04,
+ 0xE0, 0x00, 0x03, 0x11, 0x90, 0x00, 0x00, 0x33, 0x26, 0x00, 0x00, 0x03,
+ 0x24, 0x0E, 0x00, 0x0F, 0x05, 0x87, 0xF0, 0x07, 0xF0, 0x61, 0xCF, 0x01,
+ 0xE7, 0x0C, 0x09, 0x80, 0x0C, 0x81, 0x81, 0x30, 0x01, 0x90, 0x30, 0x26,
+ 0x00, 0x32, 0x06, 0x04, 0xC0, 0x06, 0x40, 0xC0, 0x98, 0xF8, 0xC8, 0x18,
+ 0x13, 0xFF, 0xF9, 0x03, 0x02, 0x70, 0x07, 0x20, 0xD0, 0x4C, 0x00, 0x64,
+ 0x12, 0x09, 0x80, 0x0C, 0x82, 0x61, 0x3F, 0xFF, 0x90, 0xC4, 0x27, 0xFF,
+ 0xF2, 0x10, 0xC4, 0xFF, 0xFE, 0x46, 0x08, 0x9F, 0xDF, 0xC8, 0x80, 0x93,
+ 0x00, 0x19, 0x30, 0x1A, 0x60, 0x03, 0x2C, 0x01, 0xCC, 0x00, 0x67, 0x80,
+ 0xC1, 0x80, 0x1C, 0x18, 0x10, 0x18, 0x03, 0x01, 0x03, 0x03, 0xC1, 0xE0,
+ 0x60, 0x20, 0x18, 0x30, 0x08, 0x06, 0x03, 0xFF, 0x02, 0x00, 0x3F, 0x80,
+ 0x3F, 0x80, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00,
+ 0x24, 0x80, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x06, 0xC8, 0x00, 0x00,
+ 0x01, 0x93, 0x00, 0x00, 0x00, 0x55, 0xC0, 0x00, 0x00, 0x1F, 0xF0, 0x00,
+ 0x00, 0x04, 0x44, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0xC4, 0x40,
+ 0x00, 0x00, 0x31, 0x10, 0x00, 0x00, 0x0C, 0x46, 0x00, 0x00, 0x02, 0x11,
+ 0x80, 0x00, 0x00, 0x84, 0x20, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0x18,
+ 0x43, 0x00, 0x00, 0x04, 0x10, 0xC0, 0x00, 0x01, 0x04, 0x10, 0x00, 0x00,
+ 0x41, 0x04, 0x00, 0x00, 0x30, 0x41, 0x00, 0x00, 0x3C, 0x10, 0x78, 0x00,
+ 0x7E, 0x04, 0x0F, 0x80, 0x7A, 0x01, 0x01, 0xBC, 0x70, 0xC0, 0x40, 0x43,
+ 0xD0, 0x10, 0x10, 0x30, 0x10, 0x06, 0x0C, 0x0C, 0x00, 0x00, 0xC7, 0xC6,
+ 0x00, 0x00, 0x13, 0x1B, 0x00, 0x00, 0x07, 0x83, 0x80, 0x00, 0x00, 0xF1,
+ 0xE0, 0x00, 0x00, 0x1C, 0x70, 0x00, 0x00, 0x03, 0x18, 0x00, 0x00, 0x03,
+ 0x83, 0x80, 0x00, 0x01, 0x80, 0x30, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00,
+ 0x07, 0xFC, 0x00, 0x00, 0x07, 0x80, 0xF0, 0x00, 0x01, 0x80, 0x03, 0x80,
+ 0x00, 0xC0, 0x00, 0x18, 0x00, 0x20, 0x00, 0x00, 0x80, 0x08, 0x00, 0x00,
+ 0x08, 0x02, 0x00, 0x00, 0x00, 0x80, 0x83, 0x00, 0x00, 0x08, 0x10, 0xE0,
+ 0x00, 0x01, 0x84, 0x30, 0x00, 0x00, 0x10, 0x8C, 0x00, 0x00, 0x03, 0x21,
+ 0x00, 0x00, 0x10, 0x24, 0x03, 0x00, 0x0E, 0x04, 0x80, 0xF0, 0x03, 0x00,
+ 0xE0, 0x1E, 0x01, 0xC0, 0x0C, 0x03, 0xC0, 0x3E, 0x01, 0x80, 0x30, 0x00,
+ 0xF0, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00,
+ 0x00, 0x1E, 0x18, 0x00, 0x07, 0x07, 0xE1, 0x00, 0x00, 0x30, 0xFF, 0x90,
+ 0x00, 0x02, 0x1F, 0xFA, 0x00, 0x00, 0x43, 0xFF, 0x40, 0x00, 0x30, 0x7F,
+ 0xE4, 0x00, 0x01, 0x0F, 0xFC, 0x80, 0x00, 0x20, 0xFF, 0x88, 0x00, 0x0C,
+ 0x3F, 0xE1, 0x80, 0x06, 0x07, 0xF0, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0xE0, 0x01, 0x80, 0x00, 0x30, 0x00,
+ 0x0C, 0x00, 0x1C, 0x00, 0x00, 0x70, 0x1E, 0x00, 0x00, 0x01, 0xFE, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0xC0, 0x30, 0x00, 0xC0, 0xC0,
+ 0x00, 0x30, 0x30, 0x40, 0x30, 0x04, 0x04, 0x30, 0x04, 0x01, 0x00, 0x08,
+ 0x00, 0x00, 0x24, 0x02, 0x3C, 0x00, 0x09, 0x80, 0x99, 0x80, 0x02, 0x00,
+ 0x2C, 0x20, 0x00, 0x80, 0x06, 0x08, 0xF0, 0x20, 0x01, 0x86, 0x47, 0x18,
+ 0x1C, 0x3F, 0x10, 0x7C, 0x01, 0x0C, 0x04, 0x00, 0x00, 0x0F, 0x80, 0x80,
+ 0x00, 0x0C, 0x78, 0x21, 0x80, 0x02, 0x13, 0x08, 0x20, 0x01, 0x04, 0x7E,
+ 0x00, 0x00, 0x41, 0x1E, 0x00, 0x00, 0x30, 0x8D, 0x00, 0x0C, 0x0C, 0x66,
+ 0x23, 0x04, 0x07, 0x11, 0x08, 0x42, 0x01, 0x40, 0x41, 0x01, 0x80, 0x48,
+ 0x00, 0x40, 0x60, 0x32, 0x00, 0x7C, 0x10, 0x0C, 0x43, 0xE7, 0x8C, 0x07,
+ 0x88, 0x01, 0x3F, 0x01, 0x21, 0x00, 0x47, 0x80, 0xCC, 0x30, 0x20, 0x00,
+ 0x31, 0x83, 0xF0, 0x00, 0xCC, 0x38, 0xF0, 0x00, 0x05, 0x83, 0xF0, 0x04,
+ 0x01, 0x30, 0xE0, 0x01, 0x80, 0xC7, 0xE0, 0x00, 0x60, 0xA1, 0xE0, 0x00,
+ 0x00, 0x69, 0xC0, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07,
+ 0xFC, 0x00, 0x00, 0x07, 0x00, 0x70, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00,
+ 0xC0, 0x00, 0x18, 0x00, 0x20, 0x00, 0x00, 0x80, 0x08, 0x00, 0x00, 0x08,
+ 0x02, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x00, 0x11, 0x80, 0x00, 0x00, 0x02, 0x20, 0x00,
+ 0x00, 0x00, 0x24, 0x01, 0x80, 0x18, 0x05, 0x80, 0x78, 0x07, 0x80, 0xA0,
+ 0x0F, 0x00, 0xF0, 0x0C, 0x01, 0xE0, 0x1E, 0x01, 0x80, 0x18, 0x01, 0x80,
+ 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00,
+ 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x07, 0xC0, 0x01, 0xF0, 0x70, 0x87,
+ 0xFF, 0xC2, 0x12, 0x1C, 0x00, 0x01, 0xC2, 0x41, 0xFF, 0xFF, 0xF0, 0x4C,
+ 0x3F, 0xFF, 0xFE, 0x10, 0x83, 0xFF, 0xFF, 0x82, 0x08, 0x1F, 0x03, 0xC0,
+ 0x81, 0x01, 0xC0, 0x30, 0x10, 0x10, 0x07, 0xF8, 0x04, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x10, 0x00, 0x00, 0x40, 0x01, 0x80, 0x00, 0x30, 0x00, 0x1C,
+ 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x0E, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00,
+ 0x00, 0x07, 0xFC, 0x00, 0x00, 0x07, 0x80, 0xE0, 0x00, 0x01, 0x80, 0x03,
+ 0x00, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x20, 0x00, 0x01, 0x80, 0x08, 0x00,
+ 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x18, 0x10,
+ 0x10, 0x01, 0x81, 0x04, 0x1C, 0x00, 0x1E, 0x10, 0x8E, 0x00, 0x00, 0xF2,
+ 0x20, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x80, 0xF0, 0x07,
+ 0xC0, 0xA0, 0x67, 0x81, 0x9C, 0x0C, 0x08, 0x70, 0x61, 0xC1, 0x83, 0x0F,
+ 0x18, 0x3C, 0x30, 0x63, 0xE3, 0x0F, 0x86, 0x0C, 0xFC, 0x73, 0xF0, 0xC1,
+ 0xFB, 0x8F, 0xEE, 0x18, 0x1F, 0xE0, 0xFF, 0x83, 0x03, 0xFC, 0x0F, 0xE0,
+ 0x50, 0x1E, 0x00, 0xF8, 0x12, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
+ 0x00, 0x44, 0x00, 0x00, 0x00, 0x10, 0x80, 0x00, 0x00, 0x02, 0x08, 0x00,
+ 0x00, 0x00, 0x81, 0x00, 0x3F, 0x80, 0x30, 0x10, 0x04, 0x10, 0x04, 0x01,
+ 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x40, 0x01, 0x80, 0x00, 0x30,
+ 0x00, 0x0C, 0x00, 0x18, 0x00, 0x00, 0xF0, 0x1E, 0x00, 0x00, 0x03, 0xFE,
+ 0x00, 0x00, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x00, 0x70, 0x0E, 0x02, 0x00,
+ 0x06, 0x00, 0x0E, 0x0C, 0x00, 0x60, 0x00, 0x0E, 0x58, 0x02, 0x00, 0x00,
+ 0x0F, 0x20, 0x10, 0x00, 0x00, 0x18, 0x40, 0x80, 0x00, 0x00, 0x41, 0x86,
+ 0x00, 0x00, 0x01, 0x02, 0x10, 0x00, 0x00, 0x08, 0x04, 0x80, 0x00, 0x00,
+ 0x20, 0x12, 0x00, 0x00, 0x00, 0x80, 0x50, 0x00, 0x00, 0x02, 0x01, 0x40,
+ 0x00, 0x00, 0x0C, 0x0D, 0x01, 0xF0, 0x1F, 0x18, 0x68, 0x0C, 0x60, 0xC6,
+ 0x3E, 0x20, 0x00, 0x82, 0x08, 0x08, 0x80, 0x00, 0x00, 0x00, 0x22, 0x00,
+ 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00,
+ 0x08, 0x80, 0x00, 0x00, 0x00, 0x22, 0x0F, 0xC0, 0x03, 0xE0, 0x84, 0x21,
+ 0xFF, 0xF0, 0x84, 0x10, 0xE0, 0x00, 0x0E, 0x10, 0x41, 0xFF, 0xFF, 0xF8,
+ 0x40, 0x87, 0xFF, 0xFF, 0xC2, 0x02, 0x0F, 0xFF, 0xFE, 0x08, 0x04, 0x0F,
+ 0x83, 0xF0, 0x40, 0x10, 0x1C, 0x07, 0x03, 0x00, 0x20, 0x0F, 0xE0, 0x08,
+ 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x06, 0x00, 0x01, 0x80,
+ 0x00, 0x30, 0x00, 0x01, 0x80, 0x03, 0x00, 0x00, 0x03, 0xC0, 0x70, 0x00,
+ 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x01, 0xF8, 0x00,
+ 0x00, 0x0F, 0xC0, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00,
+ 0xFF, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x0F, 0xF0,
+ 0x00, 0x01, 0xFF, 0x80, 0x00, 0x3F, 0xF8, 0x00, 0x87, 0xFF, 0x80, 0x18,
+ 0x7F, 0x78, 0x03, 0x8F, 0xCF, 0x00, 0x38, 0xF8, 0xF0, 0x07, 0x9F, 0x0F,
+ 0x00, 0x79, 0xE0, 0xF0, 0x0F, 0xDE, 0x0F, 0x04, 0xFF, 0xC0, 0xF9, 0xCF,
+ 0xFC, 0x0F, 0xFE, 0xFF, 0xC0, 0x7F, 0xEF, 0xFC, 0x07, 0xFF, 0xFF, 0xC0,
+ 0x3F, 0xFF, 0xF6, 0x01, 0xFF, 0xFF, 0x60, 0x0F, 0xFF, 0xE0, 0x00, 0xFF,
+ 0x7E, 0x00, 0x07, 0xE7, 0xE0, 0x00, 0x7E, 0x7E, 0x00, 0x07, 0xE3, 0xE0,
+ 0x00, 0x7C, 0x1E, 0x00, 0x0F, 0xC1, 0xF0, 0x00, 0xF8, 0x0F, 0x80, 0x1F,
+ 0x00, 0x3E, 0x07, 0xC0, 0x01, 0xFF, 0xF8, 0x00, 0x03, 0xFC, 0x00, 0x00,
+ 0x0F, 0xFC, 0x00, 0x00, 0x01, 0xC0, 0x38, 0x00, 0x00, 0x38, 0x00, 0x60,
+ 0x00, 0x03, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x02, 0x00, 0x01, 0x00,
+ 0x00, 0x18, 0x00, 0x18, 0x1F, 0x80, 0x60, 0x01, 0x81, 0x86, 0x01, 0x00,
+ 0x08, 0x08, 0x10, 0x0C, 0x00, 0xC0, 0xC0, 0xF8, 0x30, 0x04, 0x16, 0x03,
+ 0x60, 0x80, 0x60, 0xF0, 0x13, 0x06, 0x02, 0x01, 0x80, 0x10, 0x18, 0x30,
+ 0x04, 0x00, 0x80, 0x61, 0x00, 0x30, 0x0C, 0x01, 0x18, 0x00, 0xC0, 0x20,
+ 0x0C, 0xC0, 0x03, 0x01, 0x00, 0x34, 0x01, 0xF8, 0x04, 0x00, 0xA0, 0x00,
+ 0x60, 0x30, 0x05, 0x00, 0x01, 0x00, 0xC0, 0x38, 0x00, 0x0C, 0x01, 0x80,
+ 0xC0, 0x00, 0x20, 0x04, 0x06, 0x00, 0x01, 0x80, 0x00, 0x38, 0x00, 0x06,
+ 0x00, 0x01, 0xC0, 0x00, 0x18, 0x00, 0x1B, 0x00, 0x00, 0x60, 0x00, 0xCC,
+ 0x00, 0x01, 0x80, 0x0C, 0x38, 0x00, 0x06, 0x00, 0x40, 0x7C, 0x00, 0x7E,
+ 0x0E, 0x00, 0xFF, 0xFF, 0x3F, 0xC0, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00,
+ 0x00, 0x70, 0x1C, 0x00, 0x00, 0x00, 0x30, 0x01, 0x80, 0x00, 0x00, 0x10,
+ 0x00, 0x30, 0x00, 0x00, 0x08, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00,
+ 0xC0, 0x00, 0x03, 0x00, 0x00, 0x18, 0x01, 0xC0, 0x80, 0x00, 0x02, 0x0F,
+ 0xC8, 0x40, 0x00, 0x00, 0xC6, 0x71, 0x30, 0x70, 0x18, 0x19, 0x1C, 0x78,
+ 0x1C, 0x0E, 0x03, 0x85, 0x03, 0x03, 0x01, 0x81, 0x83, 0x60, 0x40, 0x00,
+ 0x00, 0xC0, 0x8C, 0x18, 0x00, 0x00, 0x20, 0x61, 0x83, 0x00, 0xE0, 0x18,
+ 0x30, 0x20, 0x40, 0x38, 0x04, 0x18, 0x0C, 0x18, 0x00, 0x03, 0x04, 0x03,
+ 0x82, 0x00, 0x00, 0x83, 0x80, 0xA0, 0x80, 0x00, 0x60, 0xA0, 0x6C, 0x30,
+ 0x00, 0x18, 0x68, 0x13, 0x0C, 0x00, 0x04, 0x13, 0x04, 0x41, 0x00, 0x01,
+ 0x04, 0x41, 0x10, 0x40, 0x00, 0x41, 0x10, 0x40, 0x10, 0x00, 0x10, 0x04,
+ 0x10, 0x04, 0x00, 0x04, 0x01, 0x04, 0x01, 0x00, 0x01, 0x00, 0x41, 0x80,
+ 0xC0, 0x00, 0x40, 0x30, 0x20, 0x3F, 0xFF, 0xF8, 0x08, 0x0E, 0x18, 0xFF,
+ 0xC3, 0x0C, 0x00, 0xFC, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x07, 0xFC, 0x00,
+ 0x00, 0x07, 0x80, 0xE0, 0x00, 0x01, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x00,
+ 0x18, 0x00, 0x20, 0x00, 0x01, 0x80, 0x08, 0x00, 0x00, 0x08, 0x02, 0x00,
+ 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x18, 0x10, 0x00, 0x00, 0x01, 0x04,
+ 0x00, 0x00, 0x00, 0x10, 0x80, 0x00, 0x00, 0x02, 0x20, 0x7E, 0x01, 0xF8,
+ 0x24, 0x1F, 0xE0, 0x7F, 0x84, 0x82, 0xF4, 0x0B, 0xD0, 0xA0, 0x9E, 0x42,
+ 0x79, 0x0C, 0x10, 0x88, 0x42, 0x21, 0x82, 0x01, 0x08, 0x04, 0x30, 0x40,
+ 0x21, 0x00, 0x86, 0x04, 0x08, 0x10, 0x20, 0xC0, 0xC3, 0x03, 0x0C, 0x18,
+ 0x07, 0x80, 0x1E, 0x03, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00,
+ 0x00, 0x10, 0x80, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x81, 0x00,
+ 0x7F, 0xE0, 0x30, 0x10, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x01, 0x00,
+ 0x10, 0x00, 0x00, 0x40, 0x01, 0x80, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x18,
+ 0x00, 0x00, 0xF0, 0x1E, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x00, 0x00,
+ 0x7F, 0x80, 0x00, 0x00, 0x00, 0xE0, 0x1C, 0x00, 0x00, 0x00, 0xC0, 0x00,
+ 0xC0, 0x00, 0x00, 0xC0, 0x00, 0x1C, 0x00, 0x00, 0x60, 0x00, 0x01, 0x80,
+ 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x18, 0x00, 0x00, 0x06, 0x00, 0x0C,
+ 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xF0, 0x30, 0x01, 0x80, 0x00,
+ 0x66, 0x06, 0x00, 0x60, 0x78, 0x10, 0x81, 0x80, 0x30, 0x13, 0x04, 0x00,
+ 0x30, 0x0C, 0x08, 0x00, 0x00, 0x0C, 0x07, 0x02, 0x00, 0x00, 0x43, 0x01,
+ 0x80, 0x00, 0x00, 0x10, 0x60, 0x60, 0x00, 0x00, 0x08, 0x18, 0x18, 0x00,
+ 0x00, 0x04, 0x06, 0x06, 0x00, 0x00, 0x06, 0x01, 0x81, 0x81, 0xC0, 0x07,
+ 0x00, 0x60, 0x60, 0x3E, 0x0F, 0x00, 0x18, 0x18, 0x01, 0xFF, 0x00, 0x06,
+ 0x07, 0xE0, 0x00, 0x00, 0x0F, 0x87, 0xCD, 0xC0, 0x00, 0x66, 0x79, 0x31,
+ 0x50, 0x00, 0x27, 0x12, 0x46, 0x32, 0x00, 0x19, 0x88, 0x98, 0x8C, 0x80,
+ 0x04, 0x46, 0x7B, 0x11, 0x20, 0x01, 0x11, 0x36, 0x22, 0x08, 0x00, 0x40,
+ 0x99, 0xC4, 0x01, 0x00, 0x10, 0x0C, 0xF8, 0x80, 0x40, 0x08, 0x06, 0x79,
+ 0x80, 0x10, 0x02, 0x00, 0x26, 0x30, 0x04, 0x00, 0x80, 0x11, 0x40, 0x01,
+ 0x00, 0x20, 0x00, 0xD8, 0x00, 0xC0, 0x0C, 0x00, 0x61, 0x80, 0x38, 0x07,
+ 0x00, 0x60, 0x38, 0x37, 0xFF, 0xB0, 0x70, 0x01, 0xF8, 0x00, 0x07, 0xE0,
+ 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x07, 0x80, 0xE0, 0x00, 0x01, 0x80,
+ 0x03, 0x00, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x20, 0x00, 0x01, 0x80, 0x08,
+ 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x80, 0x81, 0xC0, 0x00, 0x18,
+ 0x10, 0x70, 0x00, 0x01, 0x04, 0x18, 0x00, 0x00, 0x10, 0x86, 0x00, 0x00,
+ 0x02, 0x20, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x02, 0x04, 0x80, 0x30,
+ 0x01, 0xC0, 0xA0, 0x0F, 0x00, 0x60, 0x0C, 0x01, 0xE0, 0x18, 0x01, 0x80,
+ 0x3C, 0x07, 0xC0, 0x30, 0x03, 0x00, 0x1E, 0x06, 0x00, 0x00, 0x00, 0x00,
+ 0xC0, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x50, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x04, 0x02, 0x40, 0x38,
+ 0x01, 0x80, 0x44, 0x01, 0xC0, 0xE0, 0x10, 0x80, 0x07, 0xE0, 0x02, 0x08,
+ 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x30, 0x10, 0x00, 0x00, 0x04,
+ 0x01, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x40, 0x01, 0x80, 0x00,
+ 0x30, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x00, 0xF0, 0x1E, 0x00, 0x00, 0x03,
+ 0xFE, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0x00, 0x03, 0xB8, 0x1C, 0x00,
+ 0x00, 0x60, 0xC0, 0x30, 0x00, 0x18, 0x00, 0x00, 0x80, 0x03, 0x00, 0x07,
+ 0xE4, 0x00, 0x61, 0xC0, 0x03, 0x20, 0x0C, 0x1E, 0x00, 0x01, 0x00, 0x81,
+ 0xE0, 0x00, 0x08, 0x10, 0x1E, 0x01, 0x80, 0xC3, 0x00, 0xC0, 0x3C, 0x04,
+ 0x20, 0x00, 0x03, 0xC0, 0x26, 0x00, 0x00, 0x3C, 0x02, 0x60, 0x00, 0x01,
+ 0x80, 0x24, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x14, 0x00,
+ 0x7F, 0x00, 0x01, 0x40, 0x1C, 0x1C, 0x00, 0x14, 0x00, 0x00, 0x60, 0x01,
+ 0x40, 0x00, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0xF8, 0x00, 0x00,
+ 0x00, 0x18, 0x80, 0xFC, 0x00, 0x03, 0x8C, 0xF0, 0x20, 0x00, 0x28, 0x78,
+ 0x06, 0x00, 0x02, 0x40, 0x07, 0x80, 0x00, 0x64, 0x01, 0xC0, 0x00, 0x04,
+ 0x40, 0x3C, 0x00, 0x00, 0xC4, 0x00, 0x40, 0x00, 0x18, 0x40, 0x04, 0x00,
+ 0x01, 0x04, 0x01, 0xC0, 0x00, 0x20, 0x40, 0x04, 0x00, 0x0C, 0x04, 0x00,
+ 0xC0, 0x01, 0x80, 0x60, 0x04, 0x00, 0x70, 0x03, 0x00, 0x60, 0x3C, 0x00,
+ 0x18, 0x1F, 0xFF, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xFC,
+ 0x00, 0x00, 0x0C, 0xC4, 0x80, 0x00, 0x06, 0x19, 0x9C, 0x00, 0x07, 0x06,
+ 0x66, 0xE0, 0x01, 0x03, 0x33, 0x43, 0x00, 0xC0, 0x8C, 0xD8, 0x30, 0x10,
+ 0x06, 0x66, 0x01, 0x04, 0x00, 0x31, 0x00, 0x10, 0x80, 0x00, 0xC0, 0x01,
+ 0x10, 0x00, 0x68, 0x00, 0x32, 0x07, 0xF3, 0x00, 0x02, 0x41, 0x84, 0xC0,
+ 0x00, 0x64, 0x00, 0x70, 0x00, 0x04, 0xC0, 0x18, 0x03, 0x80, 0x8C, 0x1F,
+ 0x00, 0x78, 0x1B, 0xFF, 0xE0, 0x0F, 0x01, 0x60, 0x3C, 0x01, 0xE0, 0x2C,
+ 0x03, 0x00, 0x38, 0x05, 0x80, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x02, 0xC0, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00,
+ 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x20, 0x18, 0x00, 0x80, 0x46, 0x03,
+ 0xFF, 0xF0, 0x08, 0x40, 0x00, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x40,
+ 0x80, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x06, 0x01, 0x80, 0x00, 0x01,
+ 0x80, 0x18, 0x00, 0x00, 0x60, 0x00, 0xC0, 0x00, 0x10, 0x00, 0x0C, 0x00,
+ 0x0C, 0x00, 0x00, 0x70, 0x0E, 0x00, 0x00, 0x01, 0xFE, 0x00, 0x00, 0x01,
+ 0xC0, 0x00, 0x01, 0xF8, 0x00, 0x00, 0x86, 0x00, 0x00, 0x41, 0xC0, 0x00,
+ 0x30, 0x30, 0x00, 0x1C, 0x0C, 0x00, 0x1B, 0x83, 0x80, 0x08, 0x60, 0x60,
+ 0x04, 0x18, 0x18, 0x03, 0x07, 0x04, 0x00, 0xC1, 0x83, 0x00, 0x30, 0x60,
+ 0x80, 0x1F, 0xF0, 0x60, 0x3C, 0x3E, 0x10, 0x30, 0x03, 0x8C, 0x70, 0x00,
+ 0x62, 0x60, 0x10, 0x11, 0x20, 0x7E, 0x0C, 0xF0, 0x61, 0x82, 0x6C, 0xE0,
+ 0x61, 0xB3, 0xC0, 0x10, 0x0B, 0x80, 0x08, 0x07, 0x60, 0x04, 0x03, 0x18,
+ 0x02, 0x03, 0x86, 0x03, 0x01, 0xC1, 0x01, 0x80, 0xE0, 0x61, 0x80, 0x58,
+ 0x1F, 0x80, 0x24, 0x00, 0x00, 0x33, 0x00, 0x00, 0x10, 0xC0, 0x00, 0x18,
+ 0x30, 0x00, 0x18, 0x0C, 0x00, 0x18, 0x03, 0x80, 0x18, 0x00, 0xFF, 0xF8,
+ 0x00, 0x0F, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
+ 0x00, 0x3F, 0xFC, 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0xE1, 0xF8, 0x7E, 0x1F,
+ 0x87, 0xE1, 0xC0, 0x00, 0x38, 0x30, 0x00, 0x30, 0x70, 0x00, 0x70, 0x70,
+ 0x00, 0x70, 0x70, 0x00, 0x70, 0xE0, 0x00, 0x60, 0xE0, 0x00, 0xE0, 0xE0,
+ 0x3F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0x01, 0xC1, 0xC0,
+ 0x01, 0xC1, 0xC0, 0x01, 0xC1, 0x80, 0x03, 0x83, 0x80, 0x03, 0x83, 0x80,
+ 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFC, 0x07, 0x07, 0x00,
+ 0x07, 0x07, 0x00, 0x07, 0x06, 0x00, 0x0E, 0x0E, 0x00, 0x0E, 0x0E, 0x00,
+ 0x0E, 0x0E, 0x00, 0x0E, 0x0C, 0x00, 0x01, 0x80, 0x00, 0xC0, 0x00, 0x60,
+ 0x00, 0x30, 0x00, 0xFF, 0x81, 0xFF, 0xF1, 0xFF, 0xF8, 0xF3, 0x0C, 0xF1,
+ 0x80, 0x70, 0xC0, 0x38, 0x60, 0x1C, 0x30, 0x0F, 0x18, 0x03, 0xFC, 0x00,
+ 0xFF, 0xC0, 0x1F, 0xF8, 0x01, 0xFF, 0x00, 0xC7, 0x80, 0x61, 0xE0, 0x30,
+ 0x70, 0x18, 0x38, 0x0C, 0x1E, 0x06, 0x1F, 0xC3, 0x3E, 0xFF, 0xFE, 0x3F,
+ 0xFE, 0x03, 0xFC, 0x00, 0x30, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x06, 0x00,
+ 0x03, 0x00, 0x1F, 0x80, 0x06, 0x01, 0xFE, 0x00, 0x70, 0x1E, 0x78, 0x03,
+ 0x00, 0xE1, 0xC0, 0x30, 0x0E, 0x07, 0x03, 0x80, 0x70, 0x38, 0x18, 0x03,
+ 0x81, 0xC1, 0xC0, 0x1C, 0x0E, 0x0C, 0x00, 0xE0, 0x70, 0xC0, 0x07, 0x03,
+ 0x8E, 0x00, 0x1C, 0x38, 0x60, 0x00, 0xF3, 0xC7, 0x00, 0x03, 0xFC, 0x30,
+ 0xFC, 0x0F, 0xC3, 0x0F, 0xF0, 0x00, 0x38, 0xF3, 0xC0, 0x01, 0x87, 0x0E,
+ 0x00, 0x1C, 0x70, 0x38, 0x00, 0xC3, 0x81, 0xC0, 0x0C, 0x1C, 0x0E, 0x00,
+ 0xE0, 0xE0, 0x70, 0x06, 0x07, 0x03, 0x80, 0x70, 0x38, 0x1C, 0x03, 0x00,
+ 0xE1, 0xC0, 0x30, 0x07, 0x9E, 0x03, 0x80, 0x1F, 0xE0, 0x18, 0x00, 0x7E,
+ 0x00, 0x01, 0xFC, 0x00, 0x07, 0xFF, 0x00, 0x0F, 0xFF, 0x00, 0x1F, 0x03,
+ 0x00, 0x1E, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x1C, 0x00,
+ 0x00, 0x0E, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x1F, 0xC0,
+ 0x00, 0x3D, 0xE0, 0x0E, 0x78, 0xF0, 0x0E, 0x70, 0x78, 0x1E, 0xF0, 0x3C,
+ 0x1C, 0xE0, 0x1F, 0x1C, 0xE0, 0x0F, 0xB8, 0xE0, 0x07, 0xF8, 0xE0, 0x03,
+ 0xF0, 0xF0, 0x01, 0xF0, 0x78, 0x03, 0xF0, 0x3E, 0x0F, 0xF8, 0x3F, 0xFF,
+ 0x7C, 0x0F, 0xFE, 0x3E, 0x03, 0xF8, 0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x07,
+ 0x0E, 0x1E, 0x1C, 0x18, 0x38, 0x38, 0x70, 0x70, 0x70, 0x70, 0xE0, 0xE0,
+ 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0x60, 0x70, 0x70, 0x70, 0x38,
+ 0x38, 0x1C, 0x1C, 0x1C, 0x0E, 0x07, 0xE0, 0x70, 0x70, 0x38, 0x38, 0x1C,
+ 0x1C, 0x0E, 0x0E, 0x0E, 0x0E, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x0E, 0x0E, 0x0E, 0x0E, 0x1C, 0x1C, 0x38, 0x38, 0x78, 0x70,
+ 0xE0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x41, 0x82, 0xF1, 0x8F, 0x39,
+ 0x9C, 0x1F, 0xF8, 0x07, 0xE0, 0x07, 0xE0, 0x1F, 0xF0, 0x39, 0x9C, 0xF1,
+ 0x8F, 0x41, 0x82, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x38, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0xE0, 0x00, 0x01, 0xC0, 0x00, 0x03, 0x80, 0x00,
+ 0x07, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x38, 0x00, 0x00,
+ 0x70, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x07,
+ 0x00, 0x00, 0x0E, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x38, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0xE0, 0x00, 0x01, 0xC0, 0x00, 0x03, 0x80, 0x00, 0x07, 0x00,
+ 0x00, 0x0E, 0x00, 0x00, 0x77, 0x77, 0x6E, 0xEC, 0xFF, 0xFF, 0xFF, 0xE0,
+ 0xFF, 0xF0, 0x00, 0x70, 0x0F, 0x00, 0xE0, 0x0E, 0x01, 0xE0, 0x1C, 0x01,
+ 0xC0, 0x1C, 0x03, 0x80, 0x38, 0x03, 0x80, 0x78, 0x07, 0x00, 0x70, 0x0F,
+ 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x1C, 0x01, 0xC0, 0x1C, 0x03, 0x80, 0x38,
+ 0x03, 0x80, 0x78, 0x07, 0x00, 0x70, 0x0F, 0x00, 0xE0, 0x00, 0x03, 0xF0,
+ 0x03, 0xFF, 0x01, 0xFF, 0xE0, 0xF8, 0x7C, 0x38, 0x07, 0x1E, 0x01, 0xE7,
+ 0x00, 0x39, 0xC0, 0x0E, 0xE0, 0x01, 0xF8, 0x00, 0x7E, 0x00, 0x1F, 0x80,
+ 0x07, 0xE0, 0x01, 0xF8, 0x00, 0x7E, 0x00, 0x1F, 0x80, 0x07, 0xE0, 0x01,
+ 0xF8, 0x00, 0x77, 0x00, 0x39, 0xC0, 0x0E, 0x78, 0x07, 0x8E, 0x01, 0xC3,
+ 0xE1, 0xF0, 0x7F, 0xF8, 0x0F, 0xFC, 0x00, 0xFC, 0x00, 0x1F, 0x81, 0xFF,
+ 0x03, 0xFE, 0x07, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0,
+ 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00,
+ 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38,
+ 0x00, 0x70, 0x00, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xE0,
+ 0xFF, 0xF8, 0xFF, 0xFC, 0xF0, 0x3E, 0x80, 0x0E, 0x00, 0x0F, 0x00, 0x07,
+ 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x1C,
+ 0x00, 0x38, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80,
+ 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0xF8, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x1F, 0xE0, 0x3F, 0xFC, 0x1F, 0xFF, 0x0C, 0x07, 0xC0, 0x00,
+ 0xF0, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x07, 0x00,
+ 0x07, 0x81, 0xFF, 0x80, 0xFF, 0x00, 0x7F, 0xE0, 0x00, 0x7C, 0x00, 0x0E,
+ 0x00, 0x07, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x78, 0x00,
+ 0x3B, 0x00, 0x7D, 0xFF, 0xFC, 0xFF, 0xFC, 0x1F, 0xF0, 0x00, 0x00, 0x3E,
+ 0x00, 0x07, 0xC0, 0x01, 0xF8, 0x00, 0x77, 0x00, 0x0E, 0xE0, 0x03, 0x9C,
+ 0x00, 0xE3, 0x80, 0x1C, 0x70, 0x07, 0x0E, 0x00, 0xE1, 0xC0, 0x38, 0x38,
+ 0x0E, 0x07, 0x01, 0xC0, 0xE0, 0x70, 0x1C, 0x1C, 0x03, 0x83, 0x80, 0x70,
+ 0xE0, 0x0E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xE0,
+ 0x00, 0x1C, 0x00, 0x03, 0x80, 0x00, 0x70, 0x00, 0x0E, 0x00, 0x01, 0xC0,
+ 0x7F, 0xFE, 0x3F, 0xFF, 0x1F, 0xFF, 0x8E, 0x00, 0x07, 0x00, 0x03, 0x80,
+ 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x3F, 0xF0, 0x1F, 0xFE, 0x0F,
+ 0xFF, 0xC6, 0x03, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x00, 0x03,
+ 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x78, 0x00, 0x7B, 0x00,
+ 0xFD, 0xFF, 0xFC, 0xFF, 0xF8, 0x1F, 0xF0, 0x00, 0x01, 0xFC, 0x01, 0xFF,
+ 0xC0, 0xFF, 0xF0, 0x7C, 0x0C, 0x3C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x01,
+ 0xC0, 0x00, 0xF0, 0x00, 0x38, 0xFE, 0x0E, 0x7F, 0xE3, 0xBF, 0xFC, 0xFE,
+ 0x0F, 0xBE, 0x00, 0xEF, 0x80, 0x3F, 0xC0, 0x07, 0xF0, 0x01, 0xFC, 0x00,
+ 0x77, 0x00, 0x1D, 0xC0, 0x07, 0x78, 0x03, 0xCE, 0x00, 0xE3, 0xE0, 0xF8,
+ 0x7F, 0xFC, 0x0F, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x1C, 0x00, 0x1C, 0x00,
+ 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x70, 0x00, 0x70, 0x00, 0xF0, 0x00,
+ 0xE0, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0xC0, 0x01, 0xC0, 0x03, 0xC0, 0x03,
+ 0x80, 0x03, 0x80, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x03,
+ 0xF8, 0x03, 0xFF, 0x03, 0xFF, 0xF0, 0xF0, 0x3C, 0x78, 0x07, 0x9C, 0x00,
+ 0xE7, 0x00, 0x39, 0xC0, 0x0E, 0x70, 0x03, 0x8E, 0x01, 0xC3, 0xC0, 0xF0,
+ 0x7F, 0xF8, 0x07, 0xF8, 0x07, 0xFF, 0x87, 0xC0, 0xF9, 0xC0, 0x0E, 0xE0,
+ 0x01, 0xF8, 0x00, 0x7E, 0x00, 0x1F, 0x80, 0x07, 0xE0, 0x01, 0xFC, 0x00,
+ 0xF7, 0xC0, 0xF8, 0xFF, 0xFC, 0x1F, 0xFE, 0x01, 0xFE, 0x00, 0x07, 0xF0,
+ 0x07, 0xFF, 0x03, 0xFF, 0xE1, 0xF0, 0x7C, 0x70, 0x07, 0x3C, 0x01, 0xEE,
+ 0x00, 0x3B, 0x80, 0x0E, 0xE0, 0x03, 0xF8, 0x00, 0xFE, 0x00, 0x3F, 0xC0,
+ 0x1F, 0x70, 0x07, 0xDF, 0x07, 0xF3, 0xFF, 0xDC, 0x7F, 0xE7, 0x07, 0xF1,
+ 0xC0, 0x00, 0xF0, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0xC3,
+ 0x03, 0xE0, 0xFF, 0xF0, 0x3F, 0xF8, 0x03, 0xF8, 0x00, 0xFF, 0xF0, 0x00,
+ 0x00, 0x00, 0x3F, 0xFC, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77,
+ 0x77, 0x6E, 0xEC, 0x00, 0x00, 0x04, 0x00, 0x00, 0xF0, 0x00, 0x1F, 0xC0,
+ 0x03, 0xFE, 0x00, 0x3F, 0xC0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x0F, 0xF0,
+ 0x00, 0xFF, 0x00, 0x03, 0xE0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00,
+ 0x0F, 0xF8, 0x00, 0x07, 0xFC, 0x00, 0x03, 0xFC, 0x00, 0x03, 0xFE, 0x00,
+ 0x01, 0xFC, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x40, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
+ 0x80, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0xE0, 0x00, 0x1F, 0xF0, 0x00, 0x0F,
+ 0xF0, 0x00, 0x0F, 0xF8, 0x00, 0x07, 0xFC, 0x00, 0x03, 0xFC, 0x00, 0x03,
+ 0xFC, 0x00, 0x01, 0xF0, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x7F, 0xC0,
+ 0x0F, 0xF8, 0x00, 0xFF, 0x00, 0x1F, 0xF0, 0x00, 0xFE, 0x00, 0x03, 0xC0,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0x1F, 0xC1, 0xFF, 0xCF, 0xFF, 0xBC, 0x1E,
+ 0x80, 0x3C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x78, 0x03, 0xE0, 0x1F,
+ 0x00, 0xF8, 0x07, 0xC0, 0x3C, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38,
+ 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x80, 0x0E,
+ 0x00, 0x38, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x07,
+ 0xFF, 0xFE, 0x00, 0x1F, 0x80, 0x7E, 0x00, 0x7C, 0x00, 0x3E, 0x01, 0xE0,
+ 0x00, 0x1E, 0x07, 0x80, 0x00, 0x1E, 0x1E, 0x00, 0x00, 0x1E, 0x38, 0x0F,
+ 0x8E, 0x1C, 0xF0, 0x7F, 0xDC, 0x39, 0xC1, 0xFF, 0xF8, 0x3F, 0x83, 0xC1,
+ 0xF0, 0x7E, 0x0F, 0x01, 0xE0, 0xFC, 0x1C, 0x01, 0xC1, 0xF8, 0x38, 0x03,
+ 0x83, 0xF0, 0x70, 0x07, 0x07, 0xE0, 0xE0, 0x0E, 0x1F, 0xC1, 0xC0, 0x1C,
+ 0x3B, 0x83, 0xC0, 0x78, 0xF7, 0x83, 0xC1, 0xF3, 0xC7, 0x07, 0xFF, 0xFF,
+ 0x0E, 0x07, 0xFD, 0xF8, 0x0E, 0x03, 0xE3, 0xC0, 0x1E, 0x00, 0x00, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x02, 0x00, 0x3F, 0x00, 0x0E, 0x00,
+ 0x1F, 0x80, 0xFC, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x80, 0x00,
+ 0x07, 0xF8, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0xF8, 0x00, 0x01, 0xF0,
+ 0x00, 0x07, 0x70, 0x00, 0x0E, 0xE0, 0x00, 0x1D, 0xC0, 0x00, 0x71, 0xC0,
+ 0x00, 0xE3, 0x80, 0x03, 0xC7, 0x80, 0x07, 0x07, 0x00, 0x0E, 0x0E, 0x00,
+ 0x3C, 0x1E, 0x00, 0x70, 0x1C, 0x00, 0xE0, 0x38, 0x03, 0x80, 0x38, 0x07,
+ 0x00, 0x70, 0x1E, 0x00, 0xF0, 0x3F, 0xFF, 0xE0, 0x7F, 0xFF, 0xC1, 0xFF,
+ 0xFF, 0xC3, 0x80, 0x03, 0x87, 0x00, 0x07, 0x1C, 0x00, 0x07, 0x38, 0x00,
+ 0x0E, 0x70, 0x00, 0x1D, 0xC0, 0x00, 0x1C, 0xFF, 0xF8, 0x3F, 0xFF, 0x8F,
+ 0xFF, 0xF3, 0x80, 0x3C, 0xE0, 0x07, 0xB8, 0x00, 0xEE, 0x00, 0x3B, 0x80,
+ 0x0E, 0xE0, 0x03, 0xB8, 0x01, 0xEE, 0x00, 0xF3, 0xFF, 0xF8, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xCE, 0x00, 0xFB, 0x80, 0x0E, 0xE0, 0x01, 0xF8, 0x00, 0x7E,
+ 0x00, 0x1F, 0x80, 0x07, 0xE0, 0x01, 0xF8, 0x00, 0xFE, 0x00, 0xFB, 0xFF,
+ 0xFC, 0xFF, 0xFE, 0x3F, 0xFE, 0x00, 0x00, 0x7F, 0x80, 0x1F, 0xFF, 0x83,
+ 0xFF, 0xFE, 0x3F, 0x01, 0xF3, 0xE0, 0x01, 0x9E, 0x00, 0x05, 0xE0, 0x00,
+ 0x0E, 0x00, 0x00, 0x70, 0x00, 0x07, 0x00, 0x00, 0x38, 0x00, 0x01, 0xC0,
+ 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x1C, 0x00, 0x00,
+ 0xE0, 0x00, 0x03, 0x80, 0x00, 0x1C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0,
+ 0x00, 0x9F, 0x00, 0x0C, 0x7E, 0x03, 0xE1, 0xFF, 0xFF, 0x03, 0xFF, 0xF0,
+ 0x07, 0xFC, 0x00, 0xFF, 0xF0, 0x07, 0xFF, 0xF0, 0x3F, 0xFF, 0xE1, 0xC0,
+ 0x1F, 0x8E, 0x00, 0x3E, 0x70, 0x00, 0x73, 0x80, 0x03, 0xDC, 0x00, 0x0E,
+ 0xE0, 0x00, 0x7F, 0x00, 0x01, 0xF8, 0x00, 0x0F, 0xC0, 0x00, 0x7E, 0x00,
+ 0x03, 0xF0, 0x00, 0x1F, 0x80, 0x00, 0xFC, 0x00, 0x07, 0xE0, 0x00, 0x3F,
+ 0x00, 0x03, 0xF8, 0x00, 0x1D, 0xC0, 0x01, 0xEE, 0x00, 0x0E, 0x70, 0x01,
+ 0xF3, 0x80, 0x3F, 0x1F, 0xFF, 0xF0, 0xFF, 0xFE, 0x07, 0xFF, 0x80, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
+ 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xFF, 0xFE,
+ 0xFF, 0xFE, 0xFF, 0xFE, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
+ 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0E,
+ 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80,
+ 0x07, 0xFF, 0xEF, 0xFF, 0xDF, 0xFF, 0xB8, 0x00, 0x70, 0x00, 0xE0, 0x01,
+ 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70,
+ 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0xFF, 0x80, 0x0F, 0xFF, 0xC0, 0xFF,
+ 0xFF, 0x87, 0xE0, 0x3E, 0x3E, 0x00, 0x18, 0xE0, 0x00, 0x27, 0x80, 0x00,
+ 0x1C, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x0E, 0x00, 0x00, 0x38,
+ 0x00, 0x00, 0xE0, 0x07, 0xFF, 0x80, 0x1F, 0xFE, 0x00, 0x7F, 0xF8, 0x00,
+ 0x07, 0xE0, 0x00, 0x1D, 0xC0, 0x00, 0x77, 0x00, 0x01, 0xDE, 0x00, 0x07,
+ 0x38, 0x00, 0x1C, 0xF8, 0x00, 0x71, 0xF8, 0x07, 0xC3, 0xFF, 0xFE, 0x03,
+ 0xFF, 0xE0, 0x03, 0xFE, 0x00, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03,
+ 0xF0, 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F, 0x00, 0x07,
+ 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x00, 0x3F, 0x00, 0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1F,
+ 0x80, 0x03, 0xF0, 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F,
+ 0x00, 0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x0E, 0x1E, 0xFE,
+ 0xFC, 0xF0, 0xE0, 0x03, 0xCE, 0x00, 0x78, 0xE0, 0x0F, 0x0E, 0x01, 0xE0,
+ 0xE0, 0x3C, 0x0E, 0x07, 0x80, 0xE0, 0xF0, 0x0E, 0x1E, 0x00, 0xE3, 0xC0,
+ 0x0E, 0x78, 0x00, 0xEF, 0x00, 0x0F, 0xE0, 0x00, 0xFE, 0x00, 0x0F, 0xF0,
+ 0x00, 0xEF, 0x80, 0x0E, 0x7C, 0x00, 0xE3, 0xE0, 0x0E, 0x1F, 0x00, 0xE0,
+ 0xF8, 0x0E, 0x07, 0xC0, 0xE0, 0x3E, 0x0E, 0x01, 0xF0, 0xE0, 0x0F, 0x8E,
+ 0x00, 0x7C, 0xE0, 0x03, 0xEE, 0x00, 0x1F, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8,
+ 0x00, 0x3F, 0xF8, 0x00, 0xFF, 0xF0, 0x01, 0xFF, 0xE0, 0x03, 0xFF, 0xE0,
+ 0x0F, 0xFD, 0xC0, 0x1D, 0xFB, 0x80, 0x3B, 0xF3, 0x80, 0xE7, 0xE7, 0x01,
+ 0xCF, 0xCF, 0x07, 0x9F, 0x8E, 0x0E, 0x3F, 0x1C, 0x1C, 0x7E, 0x1C, 0x70,
+ 0xFC, 0x38, 0xE1, 0xF8, 0x71, 0xC3, 0xF0, 0x77, 0x07, 0xE0, 0xEE, 0x0F,
+ 0xC1, 0xFC, 0x1F, 0x81, 0xF0, 0x3F, 0x03, 0xE0, 0x7E, 0x03, 0x80, 0xFC,
+ 0x00, 0x01, 0xF8, 0x00, 0x03, 0xF0, 0x00, 0x07, 0xE0, 0x00, 0x0F, 0xC0,
+ 0x00, 0x1C, 0xF8, 0x00, 0xFF, 0x00, 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F,
+ 0xE0, 0x0F, 0xDC, 0x01, 0xFB, 0xC0, 0x3F, 0x38, 0x07, 0xE7, 0x80, 0xFC,
+ 0x70, 0x1F, 0x8F, 0x03, 0xF0, 0xE0, 0x7E, 0x0E, 0x0F, 0xC1, 0xC1, 0xF8,
+ 0x1C, 0x3F, 0x03, 0xC7, 0xE0, 0x38, 0xFC, 0x07, 0x9F, 0x80, 0x73, 0xF0,
+ 0x0F, 0x7E, 0x00, 0xEF, 0xC0, 0x1F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0,
+ 0x03, 0xFC, 0x00, 0x7C, 0x00, 0xFF, 0x00, 0x03, 0xFF, 0xC0, 0x0F, 0xFF,
+ 0xF0, 0x1F, 0x81, 0xF8, 0x3E, 0x00, 0x7C, 0x3C, 0x00, 0x3C, 0x78, 0x00,
+ 0x1E, 0x70, 0x00, 0x0E, 0x70, 0x00, 0x0E, 0xE0, 0x00, 0x07, 0xE0, 0x00,
+ 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00,
+ 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0x70, 0x00, 0x0E, 0x70, 0x00,
+ 0x0E, 0x78, 0x00, 0x1E, 0x3C, 0x00, 0x3C, 0x3E, 0x00, 0x7C, 0x1F, 0x81,
+ 0xF8, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xC0, 0x00, 0xFF, 0x00, 0xFF, 0xE0,
+ 0xFF, 0xF8, 0xFF, 0xFC, 0xE0, 0x3E, 0xE0, 0x0F, 0xE0, 0x07, 0xE0, 0x07,
+ 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xE0, 0x3E, 0xFF, 0xFC,
+ 0xFF, 0xF8, 0xFF, 0xE0, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
+ 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
+ 0xE0, 0x00, 0x00, 0xFF, 0x00, 0x03, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x1F,
+ 0x81, 0xF8, 0x3E, 0x00, 0x7C, 0x3C, 0x00, 0x3C, 0x78, 0x00, 0x1E, 0x70,
+ 0x00, 0x0E, 0x70, 0x00, 0x0E, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0,
+ 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0,
+ 0x00, 0x07, 0xE0, 0x00, 0x07, 0x70, 0x00, 0x0E, 0x70, 0x00, 0x0E, 0x78,
+ 0x00, 0x1E, 0x3C, 0x00, 0x3C, 0x3E, 0x00, 0x7C, 0x1F, 0x81, 0xF8, 0x0F,
+ 0xFF, 0xF0, 0x03, 0xFF, 0xC0, 0x00, 0xFF, 0xC0, 0x00, 0x01, 0xE0, 0x00,
+ 0x01, 0xE0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x3C, 0xFF,
+ 0xE0, 0x1F, 0xFF, 0x03, 0xFF, 0xF8, 0x70, 0x1F, 0x0E, 0x00, 0xF1, 0xC0,
+ 0x0E, 0x38, 0x01, 0xC7, 0x00, 0x38, 0xE0, 0x07, 0x1C, 0x00, 0xE3, 0x80,
+ 0x3C, 0x70, 0x0F, 0x0F, 0xFF, 0xC1, 0xFF, 0xF0, 0x3F, 0xFE, 0x07, 0x01,
+ 0xE0, 0xE0, 0x1E, 0x1C, 0x01, 0xC3, 0x80, 0x3C, 0x70, 0x03, 0x8E, 0x00,
+ 0x79, 0xC0, 0x07, 0x38, 0x00, 0xE7, 0x00, 0x0E, 0xE0, 0x01, 0xDC, 0x00,
+ 0x1C, 0x07, 0xFC, 0x07, 0xFF, 0xC3, 0xFF, 0xF1, 0xF0, 0x0C, 0xF0, 0x00,
+ 0x38, 0x00, 0x0E, 0x00, 0x03, 0x80, 0x00, 0xE0, 0x00, 0x3C, 0x00, 0x07,
+ 0xC0, 0x00, 0xFF, 0x80, 0x1F, 0xFC, 0x00, 0xFF, 0xC0, 0x01, 0xF8, 0x00,
+ 0x1E, 0x00, 0x03, 0xC0, 0x00, 0x70, 0x00, 0x1C, 0x00, 0x07, 0x00, 0x01,
+ 0xE0, 0x00, 0xEF, 0x00, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0x0F, 0xFE, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xE0, 0x00, 0x07,
+ 0x00, 0x00, 0x38, 0x00, 0x01, 0xC0, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00,
+ 0x03, 0x80, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x00, 0x07, 0x00, 0x00, 0x38,
+ 0x00, 0x01, 0xC0, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00,
+ 0x1C, 0x00, 0x00, 0xE0, 0x00, 0x07, 0x00, 0x00, 0x38, 0x00, 0x01, 0xC0,
+ 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0xE0, 0x00, 0xFC,
+ 0x00, 0x1F, 0x80, 0x03, 0xF0, 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8,
+ 0x00, 0x3F, 0x00, 0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0,
+ 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F, 0x00, 0x07, 0xE0,
+ 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF8, 0x00, 0xF7, 0x00, 0x1C, 0xF0,
+ 0x07, 0x8F, 0x01, 0xE1, 0xFF, 0xFC, 0x0F, 0xFE, 0x00, 0x7F, 0x00, 0xE0,
+ 0x00, 0x0E, 0xE0, 0x00, 0x39, 0xC0, 0x00, 0x73, 0x80, 0x01, 0xE3, 0x80,
+ 0x03, 0x87, 0x00, 0x07, 0x0F, 0x00, 0x1E, 0x0E, 0x00, 0x38, 0x1C, 0x00,
+ 0x70, 0x3C, 0x01, 0xE0, 0x38, 0x03, 0x80, 0x70, 0x07, 0x00, 0x70, 0x1C,
+ 0x00, 0xE0, 0x38, 0x01, 0xE0, 0xF0, 0x01, 0xC1, 0xC0, 0x03, 0x83, 0x80,
+ 0x07, 0x8F, 0x00, 0x07, 0x1C, 0x00, 0x0E, 0x38, 0x00, 0x0E, 0xE0, 0x00,
+ 0x1D, 0xC0, 0x00, 0x3F, 0x80, 0x00, 0x3E, 0x00, 0x00, 0x7C, 0x00, 0x00,
+ 0xF8, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0x70, 0x07,
+ 0xE0, 0x0E, 0x70, 0x07, 0xE0, 0x0E, 0x70, 0x07, 0xE0, 0x0E, 0x70, 0x0F,
+ 0xF0, 0x0E, 0x38, 0x0E, 0x70, 0x1C, 0x38, 0x0E, 0x70, 0x1C, 0x38, 0x0E,
+ 0x70, 0x1C, 0x38, 0x1E, 0x78, 0x1C, 0x1C, 0x1C, 0x38, 0x38, 0x1C, 0x1C,
+ 0x38, 0x38, 0x1C, 0x1C, 0x38, 0x38, 0x1C, 0x1C, 0x38, 0x38, 0x0E, 0x38,
+ 0x1C, 0x70, 0x0E, 0x38, 0x1C, 0x70, 0x0E, 0x38, 0x1C, 0x70, 0x0E, 0x38,
+ 0x1C, 0x70, 0x0F, 0x70, 0x0E, 0xE0, 0x07, 0x70, 0x0E, 0xE0, 0x07, 0x70,
+ 0x0E, 0xE0, 0x07, 0x70, 0x0E, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x03, 0xE0,
+ 0x07, 0xC0, 0x03, 0xE0, 0x07, 0xC0, 0x03, 0xE0, 0x07, 0xC0, 0x78, 0x00,
+ 0x78, 0xF0, 0x03, 0xC1, 0xC0, 0x0E, 0x07, 0x80, 0x78, 0x0F, 0x03, 0xC0,
+ 0x1C, 0x0E, 0x00, 0x78, 0x78, 0x00, 0xF3, 0xC0, 0x01, 0xCE, 0x00, 0x07,
+ 0xF8, 0x00, 0x0F, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0x78, 0x00, 0x03, 0xF0,
+ 0x00, 0x0F, 0xC0, 0x00, 0x7F, 0x80, 0x03, 0xCF, 0x00, 0x0E, 0x1C, 0x00,
+ 0x78, 0x78, 0x03, 0xC0, 0xF0, 0x0E, 0x01, 0xC0, 0x78, 0x07, 0x83, 0xC0,
+ 0x0F, 0x0E, 0x00, 0x1C, 0x78, 0x00, 0x7B, 0xC0, 0x00, 0xF0, 0xF0, 0x00,
+ 0x7B, 0xC0, 0x07, 0x8E, 0x00, 0x38, 0x78, 0x03, 0xC1, 0xE0, 0x3C, 0x07,
+ 0x01, 0xC0, 0x3C, 0x1E, 0x00, 0xF1, 0xE0, 0x03, 0x8E, 0x00, 0x1E, 0xF0,
+ 0x00, 0x7F, 0x00, 0x01, 0xF0, 0x00, 0x0F, 0x80, 0x00, 0x38, 0x00, 0x01,
+ 0xC0, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x1C, 0x00,
+ 0x00, 0xE0, 0x00, 0x07, 0x00, 0x00, 0x38, 0x00, 0x01, 0xC0, 0x00, 0x0E,
+ 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF,
+ 0xBF, 0xFF, 0xFC, 0x00, 0x01, 0xC0, 0x00, 0x1E, 0x00, 0x01, 0xE0, 0x00,
+ 0x1E, 0x00, 0x01, 0xE0, 0x00, 0x0E, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00,
+ 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78,
+ 0x00, 0x07, 0x80, 0x00, 0x38, 0x00, 0x03, 0xC0, 0x00, 0x3C, 0x00, 0x03,
+ 0xC0, 0x00, 0x3C, 0x00, 0x01, 0xC0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0x0E, 0x1C, 0x38, 0x70, 0xE1,
+ 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38,
+ 0x70, 0xE1, 0xC3, 0x87, 0x0F, 0xFF, 0xFF, 0x80, 0xE0, 0x0F, 0x00, 0x70,
+ 0x07, 0x00, 0x78, 0x03, 0x80, 0x38, 0x03, 0x80, 0x1C, 0x01, 0xC0, 0x1C,
+ 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0F, 0x00, 0x70, 0x07, 0x00, 0x70, 0x03,
+ 0x80, 0x38, 0x03, 0x80, 0x1C, 0x01, 0xC0, 0x1C, 0x01, 0xE0, 0x0E, 0x00,
+ 0xE0, 0x0F, 0x00, 0x70, 0xFF, 0xFF, 0xF8, 0x70, 0xE1, 0xC3, 0x87, 0x0E,
+ 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3,
+ 0x87, 0x0E, 0x1C, 0x38, 0x7F, 0xFF, 0xFF, 0x80, 0x00, 0x78, 0x00, 0x03,
+ 0xF0, 0x00, 0x1F, 0xE0, 0x00, 0xF3, 0xC0, 0x07, 0x87, 0x80, 0x3C, 0x0F,
+ 0x01, 0xE0, 0x1E, 0x0F, 0x00, 0x3C, 0x78, 0x00, 0x7B, 0xC0, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xF0, 0x70, 0x38, 0x1C, 0x0E,
+ 0x07, 0x0F, 0xE0, 0x3F, 0xF8, 0x7F, 0xFC, 0x70, 0x1E, 0x40, 0x0E, 0x00,
+ 0x07, 0x00, 0x07, 0x0F, 0xFF, 0x3F, 0xFF, 0x7F, 0xFF, 0x78, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xE0, 0x1F, 0xF8, 0x3F, 0x7F, 0xFF, 0x3F,
+ 0xF7, 0x0F, 0xC7, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E,
+ 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE3, 0xF0, 0x77, 0xFE,
+ 0x3F, 0xFF, 0x9F, 0x83, 0xCF, 0x80, 0xF7, 0x80, 0x3B, 0x80, 0x0F, 0xC0,
+ 0x07, 0xE0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0x3F,
+ 0x80, 0x3B, 0xE0, 0x3D, 0xF8, 0x3C, 0xFF, 0xFE, 0x77, 0xFE, 0x38, 0xFC,
+ 0x00, 0x03, 0xF8, 0x1F, 0xFC, 0x7F, 0xF9, 0xF0, 0x37, 0x80, 0x0E, 0x00,
+ 0x3C, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0F,
+ 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x1F, 0x03, 0x1F, 0xFE, 0x1F, 0xFC, 0x0F,
+ 0xE0, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38,
+ 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x07, 0xE3, 0x8F, 0xFD, 0xCF, 0xFF,
+ 0xE7, 0x83, 0xF7, 0x80, 0xFB, 0x80, 0x3F, 0x80, 0x0F, 0xC0, 0x07, 0xE0,
+ 0x03, 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0x3B, 0x80, 0x3D,
+ 0xE0, 0x3E, 0x78, 0x3F, 0x3F, 0xFF, 0x8F, 0xFD, 0xC1, 0xF8, 0xE0, 0x03,
+ 0xF8, 0x03, 0xFF, 0x81, 0xFF, 0xF0, 0xF8, 0x3E, 0x78, 0x03, 0x9C, 0x00,
+ 0xEE, 0x00, 0x1F, 0x80, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x80, 0x00, 0xE0, 0x00, 0x1C, 0x00, 0x07, 0x80, 0x08, 0xF8, 0x0E, 0x1F,
+ 0xFF, 0x83, 0xFF, 0xC0, 0x3F, 0xC0, 0x03, 0xF0, 0x7F, 0x0F, 0xF1, 0xE0,
+ 0x1C, 0x01, 0xC0, 0x1C, 0x01, 0xC0, 0xFF, 0xEF, 0xFE, 0xFF, 0xE1, 0xC0,
+ 0x1C, 0x01, 0xC0, 0x1C, 0x01, 0xC0, 0x1C, 0x01, 0xC0, 0x1C, 0x01, 0xC0,
+ 0x1C, 0x01, 0xC0, 0x1C, 0x01, 0xC0, 0x1C, 0x01, 0xC0, 0x1C, 0x00, 0x07,
+ 0xE3, 0x8F, 0xFD, 0xCF, 0xFF, 0xEF, 0x83, 0xF7, 0x80, 0xFB, 0x80, 0x3F,
+ 0x80, 0x0F, 0xC0, 0x07, 0xE0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00,
+ 0x7E, 0x00, 0x3B, 0x80, 0x3D, 0xE0, 0x3E, 0xF8, 0x3F, 0x3F, 0xFF, 0x8F,
+ 0xFD, 0xC1, 0xF8, 0xE0, 0x00, 0x70, 0x00, 0x70, 0x00, 0x78, 0xC0, 0x7C,
+ 0x7F, 0xFC, 0x3F, 0xFC, 0x07, 0xF8, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE3,
+ 0xF0, 0xEF, 0xFC, 0xFF, 0xFE, 0xFC, 0x1E, 0xF0, 0x0F, 0xF0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80,
+ 0x0E, 0x1C, 0x38, 0x70, 0x00, 0x00, 0x00, 0x0E, 0x1C, 0x38, 0x70, 0xE1,
+ 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38,
+ 0x70, 0xE1, 0xC7, 0xFE, 0xF9, 0xE0, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00,
+ 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0,
+ 0x0F, 0x70, 0x0F, 0x38, 0x1F, 0x1C, 0x1F, 0x0E, 0x1F, 0x07, 0x1E, 0x03,
+ 0x9E, 0x01, 0xFE, 0x00, 0xFE, 0x00, 0x7F, 0x00, 0x3B, 0xC0, 0x1C, 0xF0,
+ 0x0E, 0x3C, 0x07, 0x0F, 0x03, 0x83, 0xC1, 0xC0, 0xF0, 0xE0, 0x3C, 0x70,
+ 0x0F, 0x38, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x80, 0xE3, 0xF0, 0x1F, 0x87, 0x7F, 0xE3, 0xFF, 0x3F, 0xFF,
+ 0xBF, 0xFD, 0xF8, 0x3D, 0xC1, 0xEF, 0x00, 0xF8, 0x07, 0xF8, 0x03, 0xC0,
+ 0x1F, 0x80, 0x1C, 0x00, 0xFC, 0x00, 0xE0, 0x07, 0xE0, 0x07, 0x00, 0x3F,
+ 0x00, 0x38, 0x01, 0xF8, 0x01, 0xC0, 0x0F, 0xC0, 0x0E, 0x00, 0x7E, 0x00,
+ 0x70, 0x03, 0xF0, 0x03, 0x80, 0x1F, 0x80, 0x1C, 0x00, 0xFC, 0x00, 0xE0,
+ 0x07, 0xE0, 0x07, 0x00, 0x3F, 0x00, 0x38, 0x01, 0xF8, 0x01, 0xC0, 0x0E,
+ 0xE3, 0xF0, 0xEF, 0xFC, 0xFF, 0xFE, 0xFC, 0x1E, 0xF0, 0x0F, 0xF0, 0x07,
+ 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07,
+ 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07,
+ 0xE0, 0x07, 0x03, 0xF0, 0x03, 0xFF, 0x03, 0xFF, 0xF0, 0xF0, 0x3C, 0x78,
+ 0x07, 0x9C, 0x00, 0xEE, 0x00, 0x3F, 0x80, 0x07, 0xE0, 0x01, 0xF8, 0x00,
+ 0x7E, 0x00, 0x1F, 0x80, 0x07, 0xF0, 0x03, 0xDC, 0x00, 0xE7, 0x80, 0x78,
+ 0xF0, 0x3C, 0x3F, 0xFF, 0x03, 0xFF, 0x00, 0x3F, 0x00, 0xE3, 0xF0, 0x77,
+ 0xFE, 0x3F, 0xFF, 0x9F, 0x83, 0xCF, 0x80, 0xF7, 0x80, 0x3B, 0x80, 0x0F,
+ 0xC0, 0x07, 0xE0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0x7E, 0x00,
+ 0x3F, 0x80, 0x3B, 0xE0, 0x3D, 0xF8, 0x3C, 0xFF, 0xFE, 0x77, 0xFE, 0x38,
+ 0xFC, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00,
+ 0xE0, 0x00, 0x70, 0x00, 0x00, 0x07, 0xE3, 0x8F, 0xFD, 0xCF, 0xFF, 0xE7,
+ 0x83, 0xF7, 0x80, 0xFB, 0x80, 0x3F, 0x80, 0x0F, 0xC0, 0x07, 0xE0, 0x03,
+ 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0x3B, 0x80, 0x3D, 0xE0,
+ 0x3E, 0x78, 0x3F, 0x3F, 0xFF, 0x8F, 0xFD, 0xC1, 0xF8, 0xE0, 0x00, 0x70,
+ 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x01,
+ 0xC0, 0xE3, 0xFD, 0xFF, 0xFF, 0xFE, 0x0F, 0x01, 0xE0, 0x38, 0x07, 0x00,
+ 0xE0, 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x01, 0xC0, 0x38, 0x07, 0x00, 0xE0,
+ 0x1C, 0x03, 0x80, 0x00, 0x0F, 0xF0, 0x7F, 0xF9, 0xFF, 0xF7, 0xC0, 0x6E,
+ 0x00, 0x1C, 0x00, 0x3C, 0x00, 0x3F, 0xC0, 0x3F, 0xF0, 0x1F, 0xF0, 0x03,
+ 0xF0, 0x00, 0xF0, 0x00, 0xE0, 0x01, 0xE0, 0x03, 0xF8, 0x1F, 0xFF, 0xFC,
+ 0xFF, 0xF0, 0x3F, 0x80, 0x38, 0x07, 0x00, 0xE0, 0x1C, 0x03, 0x81, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x38, 0x07, 0x00, 0xE0, 0x1C, 0x03, 0x80, 0x70, 0x0E,
+ 0x01, 0xC0, 0x38, 0x07, 0x00, 0xE0, 0x1C, 0x03, 0xC0, 0x3F, 0xC7, 0xF8,
+ 0x3F, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xF0, 0x0F, 0x78, 0x3F, 0x7F, 0xFF, 0x3F,
+ 0xF7, 0x0F, 0xC7, 0xE0, 0x00, 0xEE, 0x00, 0x39, 0xC0, 0x07, 0x3C, 0x01,
+ 0xE3, 0x80, 0x38, 0x70, 0x07, 0x0F, 0x01, 0xE0, 0xE0, 0x38, 0x1C, 0x0F,
+ 0x01, 0xC1, 0xC0, 0x38, 0x38, 0x07, 0x8F, 0x00, 0x71, 0xC0, 0x0E, 0x38,
+ 0x00, 0xEE, 0x00, 0x1D, 0xC0, 0x03, 0xF8, 0x00, 0x3E, 0x00, 0x07, 0xC0,
+ 0x00, 0xE0, 0x1E, 0x01, 0xFC, 0x0F, 0xC0, 0xF7, 0x03, 0xF0, 0x39, 0xC0,
+ 0xFC, 0x0E, 0x70, 0x3F, 0x03, 0x9E, 0x1C, 0xE1, 0xE3, 0x87, 0x38, 0x70,
+ 0xE1, 0xCE, 0x1C, 0x38, 0x73, 0x87, 0x07, 0x38, 0x73, 0x81, 0xCE, 0x1C,
+ 0xE0, 0x73, 0x87, 0x38, 0x1D, 0xE1, 0xEE, 0x03, 0xF0, 0x3F, 0x00, 0xFC,
+ 0x0F, 0xC0, 0x3F, 0x03, 0xF0, 0x0F, 0xC0, 0xFC, 0x01, 0xE0, 0x1E, 0x00,
+ 0x78, 0x07, 0x80, 0x78, 0x01, 0xE7, 0x80, 0x78, 0x78, 0x1E, 0x07, 0x03,
+ 0x80, 0xF0, 0xF0, 0x0F, 0x3C, 0x00, 0xFF, 0x00, 0x0F, 0xC0, 0x00, 0xF0,
+ 0x00, 0x1E, 0x00, 0x07, 0xE0, 0x01, 0xFE, 0x00, 0x79, 0xC0, 0x1E, 0x3C,
+ 0x03, 0x83, 0xC0, 0xF0, 0x38, 0x3C, 0x07, 0x8F, 0x00, 0x7B, 0xC0, 0x07,
+ 0x80, 0xE0, 0x00, 0xEE, 0x00, 0x39, 0xC0, 0x07, 0x1C, 0x01, 0xE3, 0x80,
+ 0x38, 0x78, 0x0F, 0x07, 0x01, 0xC0, 0xF0, 0x38, 0x0E, 0x0E, 0x01, 0xC1,
+ 0xC0, 0x1C, 0x78, 0x03, 0x8E, 0x00, 0x79, 0xC0, 0x07, 0x70, 0x00, 0xFE,
+ 0x00, 0x0F, 0xC0, 0x01, 0xF0, 0x00, 0x1E, 0x00, 0x03, 0x80, 0x00, 0x70,
+ 0x00, 0x1C, 0x00, 0x03, 0x80, 0x00, 0xF0, 0x01, 0xFC, 0x00, 0x3F, 0x00,
+ 0x07, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0xE0, 0x03,
+ 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x07, 0x80, 0x1E, 0x00,
+ 0x78, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x3C, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x00, 0xF8, 0x1F, 0xC0, 0xFE, 0x0F, 0x00, 0x70, 0x03,
+ 0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70,
+ 0x07, 0x03, 0xF8, 0x1F, 0x00, 0xFE, 0x00, 0xF0, 0x03, 0xC0, 0x0E, 0x00,
+ 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E,
+ 0x00, 0x78, 0x01, 0xFC, 0x0F, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xF8, 0x07, 0xF0,
+ 0x3F, 0x80, 0x1E, 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00,
+ 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70, 0x03, 0xC0, 0x0F, 0xE0, 0x1F, 0x03,
+ 0xF8, 0x1E, 0x01, 0xE0, 0x0E, 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0,
+ 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0xF0, 0x7F, 0x03, 0xF8, 0x1F,
+ 0x00, 0x0F, 0xC0, 0x05, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xFC, 0x1F, 0xFE,
+ 0xC0, 0x0F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x0F, 0xFE, 0x01,
+ 0xFF, 0xF0, 0x3E, 0x0F, 0x07, 0x80, 0x30, 0xF0, 0x01, 0x0E, 0x00, 0x00,
+ 0xE0, 0x00, 0x1C, 0x00, 0x07, 0xFF, 0xF0, 0xFF, 0xFE, 0x01, 0xC0, 0x00,
+ 0x1C, 0x00, 0x01, 0xC0, 0x00, 0x1C, 0x00, 0x07, 0xFF, 0xC0, 0xFF, 0xF8,
+ 0x01, 0xC0, 0x00, 0x0E, 0x00, 0x00, 0xE0, 0x00, 0x0F, 0x00, 0x10, 0x78,
+ 0x03, 0x03, 0xE0, 0xF0, 0x1F, 0xFF, 0x00, 0xFF, 0xE0, 0x03, 0xF8, 0x77,
+ 0x77, 0x6E, 0xEC, 0x00, 0x7E, 0x01, 0xFC, 0x07, 0xF8, 0x1E, 0x00, 0x38,
+ 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x1F, 0xFC, 0x3F, 0xF8, 0x7F, 0xF0,
+ 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07,
+ 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0,
+ 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x01,
+ 0xE0, 0x7F, 0x80, 0xFE, 0x01, 0xF8, 0x00, 0x71, 0xDC, 0x77, 0x1D, 0xC7,
+ 0x61, 0xB8, 0xEE, 0x3B, 0x0C, 0xE0, 0x0E, 0x00, 0xFC, 0x01, 0xC0, 0x1F,
+ 0x80, 0x38, 0x03, 0xF0, 0x07, 0x00, 0x70, 0x03, 0x80, 0x07, 0x00, 0x0E,
+ 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFC, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01,
+ 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70,
+ 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00,
+ 0x38, 0x00, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00,
+ 0x70, 0x00, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0E, 0x00, 0x1C,
+ 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00,
+ 0x0E, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xE0, 0x01, 0xC0, 0x03,
+ 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x1F, 0x80, 0x06,
+ 0x00, 0x00, 0x07, 0xF8, 0x01, 0xC0, 0x00, 0x01, 0xE7, 0x80, 0x30, 0x00,
+ 0x00, 0x38, 0x70, 0x0C, 0x00, 0x00, 0x0E, 0x07, 0x03, 0x80, 0x00, 0x01,
+ 0xC0, 0xE0, 0x60, 0x00, 0x00, 0x38, 0x1C, 0x1C, 0x00, 0x00, 0x07, 0x03,
+ 0x83, 0x00, 0x00, 0x00, 0xE0, 0x70, 0xC0, 0x00, 0x00, 0x1C, 0x0E, 0x38,
+ 0x00, 0x00, 0x01, 0xC3, 0x86, 0x00, 0x00, 0x00, 0x3C, 0xF1, 0xC0, 0x00,
+ 0x00, 0x03, 0xFC, 0x30, 0xFC, 0x03, 0xF0, 0x3F, 0x0C, 0x3F, 0xC0, 0xFF,
+ 0x00, 0x03, 0x8F, 0x3C, 0x3C, 0xF0, 0x00, 0x61, 0xC3, 0x87, 0x0E, 0x00,
+ 0x1C, 0x70, 0x39, 0xC0, 0xE0, 0x03, 0x0E, 0x07, 0x38, 0x1C, 0x00, 0xC1,
+ 0xC0, 0xE7, 0x03, 0x80, 0x38, 0x38, 0x1C, 0xE0, 0x70, 0x06, 0x07, 0x03,
+ 0x9C, 0x0E, 0x01, 0xC0, 0xE0, 0x73, 0x81, 0xC0, 0x30, 0x0E, 0x1C, 0x38,
+ 0x70, 0x0C, 0x01, 0xE7, 0x87, 0x9E, 0x03, 0x80, 0x1F, 0xE0, 0x7F, 0x80,
+ 0x60, 0x01, 0xF8, 0x07, 0xE0, 0x01, 0x03, 0x07, 0x0E, 0x1C, 0x38, 0x70,
+ 0xE0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x01, 0x37, 0x76, 0xEE,
+ 0xEE, 0x77, 0x77, 0x6E, 0xEC, 0x30, 0xDC, 0x77, 0x1D, 0x86, 0xE3, 0xB8,
+ 0xEE, 0x3B, 0x8E, 0x71, 0xDC, 0x77, 0x1D, 0xC7, 0x61, 0xB8, 0xEE, 0x3B,
+ 0x0C, 0x1E, 0x1F, 0xE7, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFE, 0x7F,
+ 0x87, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x38, 0x1F, 0xFC,
+ 0xF0, 0xF1, 0x83, 0xE7, 0xC6, 0x0D, 0x9B, 0x18, 0x33, 0xCC, 0x60, 0xCF,
+ 0x31, 0x83, 0x18, 0xC6, 0x0C, 0x03, 0x18, 0x30, 0x0C, 0x60, 0xC0, 0x30,
+ 0x80, 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x07, 0x0E, 0x1C, 0x38,
+ 0x70, 0xE0, 0xC0, 0x80, 0x00, 0x01, 0xE0, 0x78, 0x0E, 0x03, 0x80, 0xE0,
+ 0x38, 0x00, 0x07, 0x8F, 0xF1, 0xFE, 0x3F, 0xC7, 0x80, 0x03, 0xC0, 0x00,
+ 0x0F, 0x00, 0x00, 0x1D, 0xF0, 0x00, 0x73, 0xE0, 0x01, 0xC7, 0xC0, 0x07,
+ 0x1D, 0xC0, 0x00, 0x3B, 0x80, 0x00, 0x77, 0x00, 0x01, 0xC7, 0x00, 0x03,
+ 0x8E, 0x00, 0x0F, 0x1E, 0x00, 0x1C, 0x1C, 0x00, 0x38, 0x38, 0x00, 0xF0,
+ 0x78, 0x01, 0xC0, 0x70, 0x03, 0x80, 0xE0, 0x0E, 0x00, 0xE0, 0x1C, 0x01,
+ 0xC0, 0x78, 0x03, 0xC0, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x07, 0xFF, 0xFF,
+ 0x0E, 0x00, 0x0E, 0x1C, 0x00, 0x1C, 0x70, 0x00, 0x1C, 0xE0, 0x00, 0x39,
+ 0xC0, 0x00, 0x77, 0x00, 0x00, 0x70, 0x00, 0xFE, 0x00, 0xFF, 0xC0, 0xFF,
+ 0xE0, 0xF0, 0x30, 0x70, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E,
+ 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x03, 0xFF, 0xE1,
+ 0xFF, 0xF0, 0xFF, 0xF8, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0,
+ 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xC0, 0x40, 0x00, 0x5C, 0x00, 0x1D, 0xE7, 0xCF, 0x1F, 0xFF, 0xC1,
+ 0xFF, 0xF0, 0x3C, 0x1E, 0x07, 0x01, 0xC1, 0xC0, 0x1C, 0x38, 0x03, 0x87,
+ 0x00, 0x70, 0xE0, 0x0E, 0x1C, 0x01, 0xC1, 0xC0, 0x70, 0x3C, 0x1E, 0x0F,
+ 0xFF, 0xC1, 0xFF, 0xFC, 0x79, 0xF1, 0xDC, 0x00, 0x1D, 0x00, 0x01, 0x00,
+ 0xF0, 0x01, 0xEE, 0x00, 0x39, 0xE0, 0x0F, 0x1C, 0x01, 0xC3, 0xC0, 0x78,
+ 0x38, 0x0E, 0x07, 0x83, 0xC0, 0x70, 0x70, 0x0F, 0x1E, 0x00, 0xE3, 0x80,
+ 0x1E, 0xF0, 0x3F, 0xDF, 0xE7, 0xFF, 0xFC, 0x03, 0xE0, 0x00, 0x7C, 0x00,
+ 0x07, 0x00, 0x00, 0xE0, 0x0F, 0xFF, 0xF9, 0xFF, 0xFF, 0x00, 0x70, 0x00,
+ 0x0E, 0x00, 0x01, 0xC0, 0x00, 0x38, 0x00, 0x07, 0x00, 0x00, 0xE0, 0x00,
+ 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xF8, 0x0F, 0xE0, 0xFF, 0xC3, 0x87, 0x1C, 0x04, 0x70, 0x01, 0xC0,
+ 0x07, 0x80, 0x0F, 0x00, 0x1F, 0x00, 0xFE, 0x07, 0x3E, 0x38, 0x7C, 0xE0,
+ 0x7B, 0x80, 0xFE, 0x01, 0xFC, 0x07, 0x7C, 0x1C, 0xF8, 0xE0, 0xFB, 0x81,
+ 0xF8, 0x01, 0xF0, 0x03, 0xC0, 0x07, 0x80, 0x0E, 0x00, 0x39, 0x00, 0xE7,
+ 0x07, 0x1F, 0xF8, 0x1F, 0xC0, 0xF1, 0xFE, 0x3F, 0xC7, 0xF8, 0xF0, 0x00,
+ 0x7F, 0x00, 0x00, 0xFF, 0xE0, 0x01, 0xE0, 0x3C, 0x01, 0xC0, 0x07, 0x01,
+ 0xC0, 0x01, 0xC1, 0xC3, 0xF8, 0x71, 0xC3, 0xFE, 0x18, 0xC3, 0xC1, 0x06,
+ 0x63, 0x80, 0x03, 0x63, 0xC0, 0x00, 0xF1, 0xC0, 0x00, 0x78, 0xE0, 0x00,
+ 0x3C, 0x70, 0x00, 0x1E, 0x38, 0x00, 0x0F, 0x1C, 0x00, 0x07, 0x8F, 0x00,
+ 0x03, 0x63, 0x80, 0x03, 0x30, 0xF0, 0x41, 0x9C, 0x3F, 0xE1, 0xC7, 0x0F,
+ 0xE1, 0xC1, 0xC0, 0x01, 0xC0, 0x70, 0x01, 0xC0, 0x1E, 0x03, 0xC0, 0x03,
+ 0xFF, 0x80, 0x00, 0x7F, 0x00, 0x00, 0x01, 0x02, 0x06, 0x0C, 0x1C, 0x38,
+ 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xE1, 0xC0, 0xE1,
+ 0xC0, 0xE1, 0xC0, 0xE1, 0xC0, 0xE1, 0xC0, 0xE1, 0xC0, 0xC1, 0x80, 0x81,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0x00,
+ 0x00, 0x1C, 0x00, 0x00, 0x70, 0x00, 0x01, 0xC0, 0x00, 0x07, 0x00, 0x00,
+ 0x1C, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, 0x00, 0x00,
+ 0xFF, 0xE0, 0x01, 0xE0, 0x3C, 0x01, 0xC0, 0x07, 0x01, 0xC0, 0x01, 0xC1,
+ 0xC7, 0xF8, 0x71, 0xC3, 0xFE, 0x18, 0xC1, 0xC7, 0x86, 0x60, 0xE1, 0xC3,
+ 0x60, 0x70, 0xE0, 0xF0, 0x38, 0xF0, 0x78, 0x1F, 0xF0, 0x3C, 0x0F, 0xE0,
+ 0x1E, 0x07, 0x38, 0x0F, 0x03, 0x8C, 0x07, 0x81, 0xC7, 0x03, 0x60, 0xE3,
+ 0x83, 0x30, 0x70, 0xE1, 0x9C, 0x38, 0x71, 0xC7, 0x1C, 0x1D, 0xC1, 0xC0,
+ 0x01, 0xC0, 0x70, 0x01, 0xC0, 0x1E, 0x03, 0xC0, 0x03, 0xFF, 0x80, 0x00,
+ 0x7F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07,
+ 0x80, 0xF0, 0x1F, 0x07, 0x71, 0xC7, 0xF0, 0x7C, 0x00, 0x00, 0x38, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0xE0, 0x00, 0x01, 0xC0, 0x00, 0x03, 0x80, 0x00,
+ 0x07, 0x00, 0x00, 0x0E, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFC, 0x00, 0xE0, 0x00, 0x01, 0xC0, 0x00, 0x03, 0x80, 0x00, 0x07,
+ 0x00, 0x00, 0x0E, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xC0, 0x7E, 0x3F, 0xEC, 0x1C, 0x03, 0x00, 0xC0, 0x70, 0x18, 0x0C, 0x0E,
+ 0x07, 0x03, 0x81, 0xC0, 0xFF, 0xFF, 0xF0, 0x1F, 0x8F, 0xF9, 0x83, 0x80,
+ 0x30, 0x0E, 0x3F, 0x87, 0xF0, 0x07, 0x00, 0x60, 0x0C, 0x01, 0xC0, 0xFF,
+ 0xFC, 0xFE, 0x00, 0x0F, 0x1E, 0x1C, 0x38, 0x70, 0xE0, 0x01, 0xE0, 0x78,
+ 0x0E, 0x03, 0x80, 0xE0, 0x38, 0x00, 0x07, 0x8F, 0xF1, 0xFE, 0x3F, 0xC7,
+ 0x80, 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x1D, 0xF0, 0x00, 0x73, 0xE0,
+ 0x01, 0xC7, 0xC0, 0x07, 0x1D, 0xC0, 0x00, 0x3B, 0x80, 0x00, 0x77, 0x00,
+ 0x01, 0xC7, 0x00, 0x03, 0x8E, 0x00, 0x0F, 0x1E, 0x00, 0x1C, 0x1C, 0x00,
+ 0x38, 0x38, 0x00, 0xF0, 0x78, 0x01, 0xC0, 0x70, 0x03, 0x80, 0xE0, 0x0E,
+ 0x00, 0xE0, 0x1C, 0x01, 0xC0, 0x78, 0x03, 0xC0, 0xFF, 0xFF, 0x81, 0xFF,
+ 0xFF, 0x07, 0xFF, 0xFF, 0x0E, 0x00, 0x0E, 0x1C, 0x00, 0x1C, 0x70, 0x00,
+ 0x1C, 0xE0, 0x00, 0x39, 0xC0, 0x00, 0x77, 0x00, 0x00, 0x70, 0xFF, 0xF0,
+ 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x77, 0xFF, 0xF9, 0xCF, 0xFF, 0xF7,
+ 0x1F, 0xFF, 0xEC, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 0xE0, 0x00, 0x01,
+ 0xC0, 0x00, 0x03, 0x80, 0x00, 0x07, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x1C,
+ 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x7F, 0xFF, 0x00, 0xFF, 0xFE, 0x01, 0xC0,
+ 0x00, 0x03, 0x80, 0x00, 0x07, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x1C, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 0xE0, 0x00, 0x01, 0xC0, 0x00,
+ 0x03, 0xFF, 0xFC, 0x07, 0xFF, 0xF8, 0x0F, 0xFF, 0xF0, 0x07, 0x00, 0x00,
+ 0x03, 0x80, 0x00, 0x01, 0xDC, 0x00, 0x1C, 0xE7, 0x00, 0x07, 0x31, 0xC0,
+ 0x01, 0xD8, 0x70, 0x00, 0x70, 0x1C, 0x00, 0x1C, 0x07, 0x00, 0x07, 0x01,
+ 0xC0, 0x01, 0xC0, 0x70, 0x00, 0x70, 0x1C, 0x00, 0x1C, 0x07, 0x00, 0x07,
+ 0x01, 0xC0, 0x01, 0xC0, 0x7F, 0xFF, 0xF0, 0x1F, 0xFF, 0xFC, 0x07, 0xFF,
+ 0xFF, 0x01, 0xC0, 0x01, 0xC0, 0x70, 0x00, 0x70, 0x1C, 0x00, 0x1C, 0x07,
+ 0x00, 0x07, 0x01, 0xC0, 0x01, 0xC0, 0x70, 0x00, 0x70, 0x1C, 0x00, 0x1C,
+ 0x07, 0x00, 0x07, 0x01, 0xC0, 0x01, 0xC0, 0x70, 0x00, 0x70, 0x1C, 0x00,
+ 0x1C, 0x07, 0x00, 0x07, 0x07, 0x03, 0x81, 0xDC, 0xE7, 0x71, 0xD8, 0x70,
+ 0x1C, 0x07, 0x01, 0xC0, 0x70, 0x1C, 0x07, 0x01, 0xC0, 0x70, 0x1C, 0x07,
+ 0x01, 0xC0, 0x70, 0x1C, 0x07, 0x01, 0xC0, 0x70, 0x1C, 0x07, 0x01, 0xC0,
+ 0x70, 0x1C, 0x07, 0x81, 0x01, 0x83, 0x03, 0x87, 0x03, 0x87, 0x03, 0x87,
+ 0x03, 0x87, 0x03, 0x87, 0x03, 0x87, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE1,
+ 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x30, 0x60, 0x40, 0x80, 0x07, 0x00, 0x00,
+ 0x01, 0xC0, 0x00, 0x00, 0x70, 0x7F, 0x80, 0x1C, 0x3F, 0xFC, 0x03, 0x1F,
+ 0xFF, 0xC0, 0xC7, 0xE0, 0x7C, 0x01, 0xE0, 0x03, 0xC0, 0x78, 0x00, 0x3C,
+ 0x0E, 0x00, 0x03, 0x81, 0xC0, 0x00, 0x78, 0x70, 0x00, 0x07, 0x0E, 0x00,
+ 0x00, 0xE1, 0xC0, 0x00, 0x1E, 0x38, 0x00, 0x01, 0xC7, 0x00, 0x00, 0x38,
+ 0xE0, 0x00, 0x07, 0x1C, 0x00, 0x00, 0xE3, 0x80, 0x00, 0x3C, 0x70, 0x00,
+ 0x07, 0x0E, 0x00, 0x00, 0xE0, 0xE0, 0x00, 0x3C, 0x1C, 0x00, 0x07, 0x03,
+ 0xC0, 0x01, 0xE0, 0x3C, 0x00, 0x78, 0x03, 0xF0, 0x3E, 0x00, 0x3F, 0xFF,
+ 0x80, 0x03, 0xFF, 0xE0, 0x00, 0x0F, 0xE0, 0x00, 0x3C, 0x00, 0x07, 0x07,
+ 0xE0, 0x00, 0x30, 0x33, 0x00, 0x03, 0x00, 0x18, 0x00, 0x38, 0x00, 0xC0,
+ 0x01, 0x80, 0x06, 0x00, 0x1C, 0x00, 0x30, 0x01, 0xC0, 0x01, 0x80, 0x0C,
+ 0x00, 0x0C, 0x00, 0xE0, 0x00, 0x60, 0x06, 0x00, 0x03, 0x00, 0x60, 0x00,
+ 0x18, 0x07, 0x1F, 0x8F, 0xFC, 0x31, 0xFF, 0x7F, 0xE3, 0x08, 0x1C, 0x00,
+ 0x38, 0x00, 0x60, 0x01, 0x80, 0x03, 0x00, 0x18, 0x00, 0x30, 0x01, 0xC0,
+ 0x03, 0x80, 0x0C, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x06, 0x00, 0x38,
+ 0x00, 0x60, 0x03, 0x80, 0x07, 0x00, 0x38, 0x00, 0x30, 0x03, 0xFF, 0x03,
+ 0x00, 0x1F, 0xF8, 0x38, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xE0,
+ 0x00, 0x00, 0x1C, 0xE0, 0x00, 0xF3, 0x8F, 0x00, 0x0E, 0x70, 0x78, 0x01,
+ 0xE6, 0x03, 0x80, 0x3C, 0x00, 0x3C, 0x03, 0x80, 0x01, 0xE0, 0x78, 0x00,
+ 0x0E, 0x0F, 0x00, 0x00, 0xF0, 0xE0, 0x00, 0x07, 0x1E, 0x00, 0x00, 0x3B,
+ 0xC0, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0x0E, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
+ 0xE0, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x0E, 0x00,
+ 0x00, 0x00, 0xE0, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00,
+ 0x0E, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x00,
+ 0x01, 0xC0, 0x00, 0x00, 0x70, 0x7F, 0x80, 0x1C, 0x3F, 0xFC, 0x07, 0x1F,
+ 0xFF, 0xE1, 0xC7, 0xE0, 0x7E, 0x01, 0xF0, 0x03, 0xE0, 0x38, 0x00, 0x3C,
+ 0x0F, 0x00, 0x03, 0xC1, 0xC0, 0x00, 0x38, 0x78, 0x00, 0x07, 0x0E, 0x00,
+ 0x00, 0x71, 0xC0, 0x00, 0x0E, 0x38, 0x00, 0x01, 0xC7, 0x00, 0x00, 0x38,
+ 0xE0, 0x00, 0x07, 0x1C, 0x00, 0x00, 0xE3, 0x80, 0x00, 0x3C, 0x38, 0x00,
+ 0x07, 0x07, 0x00, 0x00, 0xE0, 0xF0, 0x00, 0x38, 0x0E, 0x00, 0x07, 0x00,
+ 0xE0, 0x01, 0xC0, 0x0E, 0x00, 0x70, 0x00, 0xE0, 0x1C, 0x03, 0xFE, 0x07,
+ 0xFC, 0x7F, 0xC0, 0xFF, 0x8F, 0xF8, 0x1F, 0xF0, 0x00, 0xE0, 0x38, 0x0E,
+ 0x03, 0x80, 0x60, 0x18, 0x00, 0x07, 0x8F, 0xF1, 0xFE, 0x3F, 0xC7, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x01,
+ 0xC0, 0x38, 0x07, 0x00, 0xE0, 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x01, 0xC0,
+ 0x3C, 0x03, 0x80, 0x7F, 0x07, 0xE0, 0x7C, 0x00, 0x7C, 0x00, 0x00, 0xF8,
+ 0x00, 0x01, 0xF0, 0x00, 0x07, 0x70, 0x00, 0x0E, 0xE0, 0x00, 0x1D, 0xC0,
+ 0x00, 0x71, 0xC0, 0x00, 0xE3, 0x80, 0x03, 0xC7, 0x80, 0x07, 0x07, 0x00,
+ 0x0E, 0x0E, 0x00, 0x3C, 0x1E, 0x00, 0x70, 0x1C, 0x00, 0xE0, 0x38, 0x03,
+ 0x80, 0x38, 0x07, 0x00, 0x70, 0x1E, 0x00, 0xF0, 0x3F, 0xFF, 0xE0, 0x7F,
+ 0xFF, 0xC1, 0xFF, 0xFF, 0xC3, 0x80, 0x03, 0x87, 0x00, 0x07, 0x1C, 0x00,
+ 0x07, 0x38, 0x00, 0x0E, 0x70, 0x00, 0x1D, 0xC0, 0x00, 0x1C, 0xFF, 0xF8,
+ 0x3F, 0xFF, 0x8F, 0xFF, 0xF3, 0x80, 0x3C, 0xE0, 0x07, 0xB8, 0x00, 0xEE,
+ 0x00, 0x3B, 0x80, 0x0E, 0xE0, 0x03, 0xB8, 0x01, 0xEE, 0x00, 0xF3, 0xFF,
+ 0xF8, 0xFF, 0xFC, 0x3F, 0xFF, 0xCE, 0x00, 0xFB, 0x80, 0x0E, 0xE0, 0x01,
+ 0xF8, 0x00, 0x7E, 0x00, 0x1F, 0x80, 0x07, 0xE0, 0x01, 0xF8, 0x00, 0xFE,
+ 0x00, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0x3F, 0xFE, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00,
+ 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38,
+ 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00,
+ 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x7C,
+ 0x00, 0x00, 0xF8, 0x00, 0x01, 0xF0, 0x00, 0x07, 0x70, 0x00, 0x0E, 0xE0,
+ 0x00, 0x1D, 0xC0, 0x00, 0x71, 0xC0, 0x00, 0xE3, 0x80, 0x03, 0xC7, 0x80,
+ 0x07, 0x07, 0x00, 0x0E, 0x0E, 0x00, 0x3C, 0x1E, 0x00, 0x70, 0x1C, 0x00,
+ 0xE0, 0x38, 0x03, 0x80, 0x38, 0x07, 0x00, 0x70, 0x1E, 0x00, 0xF0, 0x38,
+ 0x00, 0xE0, 0x70, 0x01, 0xC1, 0xE0, 0x03, 0xC3, 0x80, 0x03, 0x87, 0x00,
+ 0x07, 0x1C, 0x00, 0x07, 0x3F, 0xFF, 0xFE, 0x7F, 0xFF, 0xFD, 0xFF, 0xFF,
+ 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xFF,
+ 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xBF, 0xFF,
+ 0xFC, 0x00, 0x01, 0xC0, 0x00, 0x1E, 0x00, 0x01, 0xE0, 0x00, 0x1E, 0x00,
+ 0x01, 0xE0, 0x00, 0x0E, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0,
+ 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x07,
+ 0x80, 0x00, 0x38, 0x00, 0x03, 0xC0, 0x00, 0x3C, 0x00, 0x03, 0xC0, 0x00,
+ 0x3C, 0x00, 0x01, 0xC0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xC0, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0, 0x00, 0x7E,
+ 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F, 0x00, 0x07, 0xE0, 0x00, 0xFC,
+ 0x00, 0x1F, 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8,
+ 0x00, 0x3F, 0x00, 0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0,
+ 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F, 0x00, 0x07, 0xE0,
+ 0x00, 0xFC, 0x00, 0x1C, 0x00, 0xFF, 0x00, 0x03, 0xFF, 0xC0, 0x0F, 0xFF,
+ 0xF0, 0x1F, 0x81, 0xF8, 0x3E, 0x00, 0x7C, 0x3C, 0x00, 0x3C, 0x78, 0x00,
+ 0x1E, 0x70, 0x00, 0x0E, 0x70, 0x00, 0x0E, 0xE0, 0x00, 0x07, 0xE0, 0x00,
+ 0x07, 0xE3, 0xFF, 0xC7, 0xE3, 0xFF, 0xC7, 0xE3, 0xFF, 0xC7, 0xE0, 0x00,
+ 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0x70, 0x00, 0x0E, 0x70, 0x00,
+ 0x0E, 0x78, 0x00, 0x1E, 0x3C, 0x00, 0x3C, 0x3E, 0x00, 0x7C, 0x1F, 0x81,
+ 0xF8, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xC0, 0x00, 0xFF, 0x00, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xE0, 0x03, 0xCE, 0x00,
+ 0x78, 0xE0, 0x0F, 0x0E, 0x01, 0xE0, 0xE0, 0x3C, 0x0E, 0x07, 0x80, 0xE0,
+ 0xF0, 0x0E, 0x1E, 0x00, 0xE3, 0xC0, 0x0E, 0x78, 0x00, 0xEF, 0x00, 0x0F,
+ 0xE0, 0x00, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0xEF, 0x80, 0x0E, 0x7C, 0x00,
+ 0xE3, 0xE0, 0x0E, 0x1F, 0x00, 0xE0, 0xF8, 0x0E, 0x07, 0xC0, 0xE0, 0x3E,
+ 0x0E, 0x01, 0xF0, 0xE0, 0x0F, 0x8E, 0x00, 0x7C, 0xE0, 0x03, 0xEE, 0x00,
+ 0x1F, 0x00, 0x7C, 0x00, 0x00, 0xF8, 0x00, 0x01, 0xF0, 0x00, 0x07, 0x70,
+ 0x00, 0x0E, 0xE0, 0x00, 0x1D, 0xC0, 0x00, 0x71, 0xC0, 0x00, 0xE3, 0x80,
+ 0x03, 0xC7, 0x80, 0x07, 0x07, 0x00, 0x0E, 0x0E, 0x00, 0x3C, 0x1E, 0x00,
+ 0x70, 0x1C, 0x00, 0xE0, 0x38, 0x03, 0x80, 0x38, 0x07, 0x00, 0x70, 0x1E,
+ 0x00, 0xF0, 0x38, 0x00, 0xE0, 0x70, 0x01, 0xC1, 0xE0, 0x03, 0xC3, 0x80,
+ 0x03, 0x87, 0x00, 0x07, 0x1C, 0x00, 0x07, 0x38, 0x00, 0x0E, 0x70, 0x00,
+ 0x1D, 0xC0, 0x00, 0x1C, 0xF8, 0x00, 0x3F, 0xF8, 0x00, 0xFF, 0xF0, 0x01,
+ 0xFF, 0xE0, 0x03, 0xFF, 0xE0, 0x0F, 0xFD, 0xC0, 0x1D, 0xFB, 0x80, 0x3B,
+ 0xF3, 0x80, 0xE7, 0xE7, 0x01, 0xCF, 0xCF, 0x07, 0x9F, 0x8E, 0x0E, 0x3F,
+ 0x1C, 0x1C, 0x7E, 0x1C, 0x70, 0xFC, 0x38, 0xE1, 0xF8, 0x71, 0xC3, 0xF0,
+ 0x77, 0x07, 0xE0, 0xEE, 0x0F, 0xC1, 0xFC, 0x1F, 0x81, 0xF0, 0x3F, 0x03,
+ 0xE0, 0x7E, 0x03, 0x80, 0xFC, 0x00, 0x01, 0xF8, 0x00, 0x03, 0xF0, 0x00,
+ 0x07, 0xE0, 0x00, 0x0F, 0xC0, 0x00, 0x1C, 0xF8, 0x00, 0xFF, 0x00, 0x1F,
+ 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xE0, 0x0F, 0xDC, 0x01, 0xFB, 0xC0, 0x3F,
+ 0x38, 0x07, 0xE7, 0x80, 0xFC, 0x70, 0x1F, 0x8F, 0x03, 0xF0, 0xE0, 0x7E,
+ 0x0E, 0x0F, 0xC1, 0xC1, 0xF8, 0x1C, 0x3F, 0x03, 0xC7, 0xE0, 0x38, 0xFC,
+ 0x07, 0x9F, 0x80, 0x73, 0xF0, 0x0F, 0x7E, 0x00, 0xEF, 0xC0, 0x1F, 0xF8,
+ 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x03, 0xFC, 0x00, 0x7C, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x3F,
+ 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x1F, 0x81,
+ 0xF8, 0x3E, 0x00, 0x7C, 0x3C, 0x00, 0x3C, 0x78, 0x00, 0x1E, 0x70, 0x00,
+ 0x0E, 0x70, 0x00, 0x0E, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00,
+ 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00,
+ 0x07, 0xE0, 0x00, 0x07, 0x70, 0x00, 0x0E, 0x70, 0x00, 0x0E, 0x78, 0x00,
+ 0x1E, 0x3C, 0x00, 0x3C, 0x3E, 0x00, 0x7C, 0x1F, 0x81, 0xF8, 0x0F, 0xFF,
+ 0xF0, 0x03, 0xFF, 0xC0, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF0, 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F,
+ 0x00, 0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0, 0x00, 0x7E,
+ 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F, 0x00, 0x07, 0xE0, 0x00, 0xFC,
+ 0x00, 0x1F, 0x80, 0x03, 0xF0, 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8,
+ 0x00, 0x3F, 0x00, 0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1C, 0xFF, 0xE0, 0xFF,
+ 0xF8, 0xFF, 0xFC, 0xE0, 0x3E, 0xE0, 0x0F, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xE0, 0x3E, 0xFF, 0xFC, 0xFF,
+ 0xF8, 0xFF, 0xE0, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x78, 0x00, 0x3C,
+ 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00,
+ 0xF0, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x03, 0x80, 0x07, 0x80, 0x0F,
+ 0x00, 0x1E, 0x00, 0x1C, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x00, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFE, 0x00, 0xE0, 0x00, 0x07, 0x00, 0x00, 0x38, 0x00, 0x01, 0xC0, 0x00,
+ 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x1C, 0x00, 0x00, 0xE0,
+ 0x00, 0x07, 0x00, 0x00, 0x38, 0x00, 0x01, 0xC0, 0x00, 0x0E, 0x00, 0x00,
+ 0x70, 0x00, 0x03, 0x80, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x00, 0x07, 0x00,
+ 0x00, 0x38, 0x00, 0x01, 0xC0, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03,
+ 0x80, 0x00, 0xF0, 0x00, 0x7B, 0xC0, 0x07, 0x8E, 0x00, 0x38, 0x78, 0x03,
+ 0xC1, 0xE0, 0x3C, 0x07, 0x01, 0xC0, 0x3C, 0x1E, 0x00, 0xF1, 0xE0, 0x03,
+ 0x8E, 0x00, 0x1E, 0xF0, 0x00, 0x7F, 0x00, 0x01, 0xF0, 0x00, 0x0F, 0x80,
+ 0x00, 0x38, 0x00, 0x01, 0xC0, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03,
+ 0x80, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x00, 0x07, 0x00, 0x00, 0x38, 0x00,
+ 0x01, 0xC0, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x00,
+ 0x1C, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x07, 0x00, 0x00, 0x1F, 0xF0, 0x00,
+ 0x7F, 0xFF, 0x00, 0x7F, 0xFF, 0xC0, 0x7E, 0x73, 0xF0, 0x78, 0x38, 0x3C,
+ 0x78, 0x1C, 0x0F, 0x38, 0x0E, 0x03, 0xB8, 0x07, 0x00, 0xFC, 0x03, 0x80,
+ 0x7E, 0x01, 0xC0, 0x3F, 0x00, 0xE0, 0x1F, 0x80, 0x70, 0x0F, 0xC0, 0x38,
+ 0x07, 0x70, 0x1C, 0x07, 0x3C, 0x0E, 0x07, 0x8F, 0x07, 0x07, 0x83, 0xF3,
+ 0x9F, 0x80, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x78, 0x00, 0x78,
+ 0xF0, 0x03, 0xC1, 0xC0, 0x0E, 0x07, 0x80, 0x78, 0x0F, 0x03, 0xC0, 0x1C,
+ 0x0E, 0x00, 0x78, 0x78, 0x00, 0xF3, 0xC0, 0x01, 0xCE, 0x00, 0x07, 0xF8,
+ 0x00, 0x0F, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0x78, 0x00, 0x03, 0xF0, 0x00,
+ 0x0F, 0xC0, 0x00, 0x7F, 0x80, 0x03, 0xCF, 0x00, 0x0E, 0x1C, 0x00, 0x78,
+ 0x78, 0x03, 0xC0, 0xF0, 0x0E, 0x01, 0xC0, 0x78, 0x07, 0x83, 0xC0, 0x0F,
+ 0x0E, 0x00, 0x1C, 0x78, 0x00, 0x7B, 0xC0, 0x00, 0xF0, 0xE0, 0x1C, 0x03,
+ 0xF0, 0x0E, 0x01, 0xF8, 0x07, 0x00, 0xFC, 0x03, 0x80, 0x7E, 0x01, 0xC0,
+ 0x3F, 0x00, 0xE0, 0x1F, 0x80, 0x70, 0x0F, 0xC0, 0x38, 0x07, 0xE0, 0x1C,
+ 0x03, 0xF0, 0x0E, 0x01, 0xF8, 0x07, 0x00, 0xFE, 0x03, 0x80, 0xE7, 0x01,
+ 0xC0, 0x73, 0x80, 0xE0, 0x38, 0xE0, 0x70, 0x38, 0x78, 0x38, 0x3C, 0x1E,
+ 0x1C, 0x3C, 0x07, 0xCE, 0x7C, 0x01, 0xFF, 0xFC, 0x00, 0x7F, 0xFC, 0x00,
+ 0x0F, 0xF8, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x70, 0x00, 0x00, 0x38, 0x00,
+ 0x00, 0x1C, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x03, 0xFF,
+ 0xC0, 0x0F, 0xFF, 0xF0, 0x1F, 0x81, 0xF8, 0x3E, 0x00, 0x7C, 0x3C, 0x00,
+ 0x1C, 0x78, 0x00, 0x1E, 0x70, 0x00, 0x0E, 0xF0, 0x00, 0x0F, 0xE0, 0x00,
+ 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00,
+ 0x07, 0xE0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0x70, 0x00, 0x0E, 0x70, 0x00,
+ 0x0E, 0x78, 0x00, 0x1E, 0x38, 0x00, 0x1C, 0x1C, 0x00, 0x38, 0x0E, 0x00,
+ 0x70, 0x07, 0x00, 0xE0, 0xFF, 0x81, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x81,
+ 0xFF, 0xF1, 0xFE, 0x3F, 0xC7, 0xF8, 0xF0, 0x00, 0x00, 0x03, 0x80, 0x70,
+ 0x0E, 0x01, 0xC0, 0x38, 0x07, 0x00, 0xE0, 0x1C, 0x03, 0x80, 0x70, 0x0E,
+ 0x01, 0xC0, 0x38, 0x07, 0x00, 0xE0, 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x01,
+ 0xC0, 0x38, 0x07, 0x00, 0xE0, 0x1C, 0x03, 0x80, 0x70, 0x07, 0x8F, 0x00,
+ 0x3C, 0x78, 0x01, 0xE3, 0xC0, 0x0F, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0xC0, 0x01, 0xEF, 0x00, 0x1E, 0x38, 0x00, 0xE1, 0xE0, 0x0F, 0x07,
+ 0x80, 0xF0, 0x1C, 0x07, 0x00, 0xF0, 0x78, 0x03, 0xC7, 0x80, 0x0E, 0x38,
+ 0x00, 0x7B, 0xC0, 0x01, 0xFC, 0x00, 0x07, 0xC0, 0x00, 0x3E, 0x00, 0x00,
+ 0xE0, 0x00, 0x07, 0x00, 0x00, 0x38, 0x00, 0x01, 0xC0, 0x00, 0x0E, 0x00,
+ 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x00, 0x07,
+ 0x00, 0x00, 0x38, 0x00, 0x01, 0xC0, 0x00, 0x0E, 0x00, 0x00, 0x1C, 0x00,
+ 0x07, 0x80, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x03, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x38, 0x7F, 0xCF, 0x1F,
+ 0xFD, 0xC7, 0x87, 0xB8, 0xE0, 0x7F, 0x3C, 0x07, 0xC7, 0x00, 0xF8, 0xE0,
+ 0x1F, 0x1C, 0x03, 0xC3, 0x80, 0x38, 0x70, 0x07, 0x0E, 0x01, 0xE1, 0xC0,
+ 0x3C, 0x3C, 0x0F, 0xC3, 0x81, 0xF8, 0x78, 0x7F, 0x87, 0xFF, 0xFC, 0x7F,
+ 0xCF, 0x83, 0xF0, 0xF0, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00,
+ 0x0C, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xE0, 0x7F,
+ 0xF1, 0xFF, 0xE7, 0x80, 0xCE, 0x00, 0x1C, 0x00, 0x3C, 0x00, 0x3F, 0xE0,
+ 0x1F, 0xC0, 0x7F, 0x83, 0xC0, 0x0F, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x78,
+ 0x00, 0xF8, 0x06, 0xFF, 0xFC, 0xFF, 0xF8, 0x3F, 0xC0, 0x00, 0x0E, 0x00,
+ 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0x60, 0x00, 0xC0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xE3, 0xF0, 0xEF, 0xFC, 0xFF, 0xFE, 0xFC, 0x1E, 0xF8,
+ 0x0F, 0xF0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
+ 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x07, 0x07, 0x03, 0x83, 0x83,
+ 0x83, 0x80, 0x00, 0x00, 0x00, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81,
+ 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xC0, 0xF0, 0x3F,
+ 0x8F, 0xC3, 0xE0, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x03,
+ 0x00, 0x06, 0x00, 0x00, 0x00, 0x78, 0xF0, 0x78, 0xF0, 0x78, 0xF0, 0x78,
+ 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0,
+ 0x1C, 0xE0, 0x1C, 0xE0, 0x0E, 0xE0, 0x0E, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xE0, 0x0F, 0xE0,
+ 0x0E, 0xE0, 0x1E, 0xF0, 0x3C, 0x78, 0x7C, 0x7F, 0xF8, 0x3F, 0xF0, 0x0F,
+ 0xC0, 0x07, 0xE1, 0xC3, 0xFE, 0x78, 0xFF, 0xEE, 0x3C, 0x3D, 0xC7, 0x03,
+ 0xF9, 0xE0, 0x3E, 0x38, 0x07, 0xC7, 0x00, 0xF8, 0xE0, 0x1E, 0x1C, 0x01,
+ 0xC3, 0x80, 0x38, 0x70, 0x0F, 0x0E, 0x01, 0xE1, 0xE0, 0x7E, 0x1C, 0x0F,
+ 0xC3, 0xC3, 0xFC, 0x3F, 0xFF, 0xE3, 0xFE, 0x7C, 0x1F, 0x87, 0x80, 0x0F,
+ 0xE0, 0x1F, 0xFC, 0x1F, 0xFF, 0x0F, 0x07, 0x8F, 0x01, 0xE7, 0x00, 0x73,
+ 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x70, 0x0F, 0x38, 0x0F, 0x1C, 0x0F,
+ 0x8E, 0x7F, 0x87, 0x3F, 0xC3, 0x9F, 0xF9, 0xC0, 0x3E, 0xE0, 0x07, 0x70,
+ 0x01, 0xF8, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0x3F, 0x00, 0x1F, 0xC0, 0x1D,
+ 0xF8, 0x3E, 0xFF, 0xFE, 0x7F, 0xFE, 0x39, 0xFC, 0x1C, 0x00, 0x0E, 0x00,
+ 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x00,
+ 0xE0, 0x00, 0xFF, 0x00, 0x3B, 0xE0, 0x07, 0x1E, 0x01, 0xE1, 0xC0, 0x38,
+ 0x3C, 0x0F, 0x03, 0x81, 0xC0, 0x70, 0x38, 0x0F, 0x0E, 0x00, 0xE1, 0xC0,
+ 0x1C, 0x78, 0x03, 0xCE, 0x00, 0x3B, 0xC0, 0x07, 0x70, 0x00, 0xFE, 0x00,
+ 0x0F, 0x80, 0x01, 0xF0, 0x00, 0x3E, 0x00, 0x03, 0x80, 0x00, 0x70, 0x00,
+ 0x0E, 0x00, 0x01, 0xC0, 0x00, 0x38, 0x00, 0x07, 0x00, 0x00, 0xE0, 0x00,
+ 0x1C, 0x00, 0x03, 0xFE, 0x03, 0xFF, 0xC1, 0xFF, 0xF0, 0xF0, 0x04, 0x38,
+ 0x00, 0x0E, 0x00, 0x03, 0xE0, 0x00, 0x7F, 0xC0, 0x0F, 0xFC, 0x07, 0xFF,
+ 0x83, 0x81, 0xF1, 0xC0, 0x1E, 0x70, 0x03, 0xB8, 0x00, 0xFE, 0x00, 0x1F,
+ 0x80, 0x07, 0xE0, 0x01, 0xF8, 0x00, 0x7E, 0x00, 0x1F, 0xC0, 0x0F, 0x70,
+ 0x03, 0x9E, 0x01, 0xE3, 0xC0, 0xF0, 0xFF, 0xFC, 0x0F, 0xFC, 0x00, 0xFC,
+ 0x00, 0x07, 0xF0, 0x3F, 0xF8, 0xFF, 0xF3, 0xC0, 0x67, 0x00, 0x0E, 0x00,
+ 0x1E, 0x00, 0x1F, 0xF0, 0x0F, 0xE0, 0x3F, 0xC1, 0xE0, 0x07, 0x80, 0x0E,
+ 0x00, 0x1C, 0x00, 0x3C, 0x00, 0x7C, 0x03, 0x7F, 0xFE, 0x7F, 0xFC, 0x1F,
+ 0xE0, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x00, 0x7E, 0x00, 0xF8, 0x03,
+ 0xE0, 0x07, 0xC0, 0x0F, 0x80, 0x1E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x78,
+ 0x00, 0x70, 0x00, 0x70, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0,
+ 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xF0, 0x00, 0x70, 0x00, 0x78, 0x00, 0x3E,
+ 0x00, 0x1F, 0xF8, 0x0F, 0xFE, 0x01, 0xFE, 0x00, 0x0F, 0x00, 0x07, 0x00,
+ 0x07, 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1C, 0xE3, 0xF0, 0xEF,
+ 0xFC, 0xFF, 0xFE, 0xFC, 0x1E, 0xF8, 0x0F, 0xF0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0x00,
+ 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
+ 0x07, 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xFF, 0xE0, 0xF8, 0x7C, 0x38, 0x07,
+ 0x1E, 0x01, 0xE7, 0x00, 0x39, 0xC0, 0x0E, 0xF0, 0x03, 0xF8, 0x00, 0x7E,
+ 0x00, 0x1F, 0x80, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80,
+ 0x07, 0xE0, 0x01, 0xF8, 0x00, 0x7E, 0x00, 0x1D, 0xC0, 0x0E, 0x70, 0x03,
+ 0x9E, 0x01, 0xE3, 0x80, 0x70, 0xF8, 0x7C, 0x1F, 0xFE, 0x03, 0xFF, 0x00,
+ 0x3F, 0x00, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
+ 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xF0, 0x7F, 0x3F, 0x1F, 0xE0, 0x3C, 0x70,
+ 0x3C, 0x38, 0x3C, 0x1C, 0x3C, 0x0E, 0x3C, 0x07, 0x3C, 0x03, 0xBC, 0x01,
+ 0xFE, 0x00, 0xFF, 0x80, 0x7F, 0xC0, 0x3C, 0xF0, 0x1C, 0x3C, 0x0E, 0x0F,
+ 0x07, 0x03, 0xC3, 0x80, 0xE1, 0xC0, 0x78, 0xE0, 0x1E, 0x70, 0x07, 0xB8,
+ 0x01, 0xE0, 0x1F, 0x00, 0x03, 0xF0, 0x00, 0x7F, 0x00, 0x01, 0xE0, 0x00,
+ 0x1E, 0x00, 0x01, 0xC0, 0x00, 0x38, 0x00, 0x07, 0x80, 0x00, 0x70, 0x00,
+ 0x1E, 0x00, 0x03, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0xC0, 0x07, 0xB8, 0x00,
+ 0xE7, 0x00, 0x3C, 0xF0, 0x07, 0x0E, 0x00, 0xE1, 0xC0, 0x38, 0x1C, 0x07,
+ 0x03, 0x81, 0xC0, 0x78, 0x38, 0x07, 0x0E, 0x00, 0xE1, 0xC0, 0x1E, 0x78,
+ 0x01, 0xCE, 0x00, 0x3B, 0xC0, 0x03, 0x80, 0xE0, 0x07, 0x38, 0x01, 0xCE,
+ 0x00, 0x73, 0x80, 0x1C, 0xE0, 0x07, 0x38, 0x01, 0xCE, 0x00, 0x73, 0x80,
+ 0x1C, 0xE0, 0x07, 0x38, 0x01, 0xCE, 0x00, 0x73, 0x80, 0x1C, 0xE0, 0x07,
+ 0x38, 0x01, 0xCF, 0x00, 0xF3, 0xE0, 0xFC, 0xFF, 0xFF, 0xFB, 0xFC, 0xFE,
+ 0x7E, 0x3B, 0x80, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x03, 0x80,
+ 0x00, 0xE0, 0x00, 0x38, 0x00, 0x00, 0xE0, 0x0E, 0x78, 0x07, 0x1C, 0x01,
+ 0xCE, 0x00, 0xE7, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x78, 0x07, 0x1C,
+ 0x03, 0x8E, 0x03, 0xC7, 0x01, 0xC1, 0xC1, 0xE0, 0xE0, 0xE0, 0x70, 0xE0,
+ 0x1C, 0xF0, 0x0E, 0xF0, 0x07, 0xF0, 0x01, 0xF0, 0x00, 0xF0, 0x00, 0x7F,
+ 0xFC, 0xFF, 0xF9, 0xFF, 0xF0, 0xFC, 0x03, 0xC0, 0x0F, 0x00, 0x1C, 0x00,
+ 0x38, 0x00, 0x70, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0xFF, 0x80, 0x3F, 0x03,
+ 0xFE, 0x0F, 0x80, 0x3C, 0x00, 0xF0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00,
+ 0x0E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x3F, 0x00, 0x3F, 0xF0, 0x3F, 0xF8,
+ 0x1F, 0xF0, 0x00, 0xF0, 0x00, 0xE0, 0x01, 0xC0, 0x07, 0x80, 0x1E, 0x00,
+ 0x3C, 0x00, 0x70, 0x03, 0xF0, 0x03, 0xFF, 0x03, 0xFF, 0xF0, 0xF0, 0x3C,
+ 0x78, 0x07, 0x9C, 0x00, 0xEE, 0x00, 0x3F, 0x80, 0x07, 0xE0, 0x01, 0xF8,
+ 0x00, 0x7E, 0x00, 0x1F, 0x80, 0x07, 0xF0, 0x03, 0xDC, 0x00, 0xE7, 0x80,
+ 0x78, 0xF0, 0x3C, 0x3F, 0xFF, 0x03, 0xFF, 0x00, 0x3F, 0x00, 0xFF, 0xFF,
+ 0xBF, 0xFF, 0xEF, 0xFF, 0xF8, 0xE0, 0x38, 0x38, 0x0E, 0x0E, 0x03, 0x83,
+ 0x80, 0xE0, 0xE0, 0x38, 0x38, 0x0E, 0x0E, 0x03, 0x83, 0x80, 0xE0, 0xE0,
+ 0x38, 0x38, 0x0E, 0x0E, 0x03, 0x83, 0x80, 0xE0, 0xE0, 0x38, 0x38, 0x0F,
+ 0xCE, 0x01, 0xF3, 0x80, 0x7C, 0x03, 0xF8, 0x03, 0xFF, 0x81, 0xFF, 0xF0,
+ 0xF0, 0x3C, 0x78, 0x07, 0x9C, 0x00, 0xEE, 0x00, 0x3F, 0x80, 0x07, 0xE0,
+ 0x01, 0xF8, 0x00, 0x7E, 0x00, 0x1F, 0x80, 0x07, 0xE0, 0x03, 0xFC, 0x00,
+ 0xEF, 0x80, 0x7B, 0xF0, 0x3C, 0xEF, 0xFF, 0x39, 0xFF, 0x0E, 0x3F, 0x03,
+ 0x80, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x03, 0x80, 0x00, 0xE0,
+ 0x00, 0x38, 0x00, 0x00, 0x03, 0xF8, 0x1F, 0xFC, 0x7F, 0xF9, 0xF0, 0x37,
+ 0x80, 0x0E, 0x00, 0x3C, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80,
+ 0x07, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x1F, 0xF0,
+ 0x1F, 0xF8, 0x0F, 0xF0, 0x00, 0xF0, 0x00, 0xE0, 0x01, 0xC0, 0x07, 0x80,
+ 0x1E, 0x00, 0x3C, 0x00, 0x70, 0x07, 0xFF, 0xF1, 0xFF, 0xFF, 0x3F, 0xFF,
+ 0xF7, 0xC0, 0xF0, 0x78, 0x07, 0x87, 0x00, 0x38, 0xE0, 0x03, 0xCE, 0x00,
+ 0x1C, 0xE0, 0x01, 0xCE, 0x00, 0x1C, 0xE0, 0x01, 0xCE, 0x00, 0x1C, 0xF0,
+ 0x03, 0xC7, 0x00, 0x38, 0x78, 0x07, 0x83, 0xC0, 0xF0, 0x3F, 0xFE, 0x00,
+ 0xFF, 0xC0, 0x03, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0,
+ 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0,
+ 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07,
+ 0x00, 0x03, 0xC0, 0x00, 0xFE, 0x00, 0x3F, 0x00, 0x0F, 0x80, 0xE0, 0x1C,
+ 0xE0, 0x1C, 0xE0, 0x0E, 0xE0, 0x0E, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07,
+ 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xE0, 0x0F, 0xE0, 0x0E,
+ 0xE0, 0x1E, 0xF0, 0x3C, 0x78, 0x7C, 0x7F, 0xF8, 0x3F, 0xF0, 0x0F, 0xC0,
+ 0x06, 0x3C, 0x03, 0xCF, 0xE0, 0xFB, 0xFE, 0x3E, 0x71, 0xE7, 0x8E, 0x1D,
+ 0xE1, 0xC3, 0xFC, 0x38, 0x3F, 0x07, 0x07, 0xE0, 0xE0, 0xFC, 0x1C, 0x1F,
+ 0x83, 0x83, 0xF0, 0x70, 0x7E, 0x0E, 0x0E, 0xE1, 0xC3, 0x9E, 0x38, 0xF1,
+ 0xE7, 0x3C, 0x3F, 0xFF, 0x83, 0xFF, 0xE0, 0x1F, 0xF0, 0x00, 0x70, 0x00,
+ 0x0E, 0x00, 0x01, 0xC0, 0x00, 0x38, 0x00, 0x07, 0x00, 0x00, 0xE0, 0x00,
+ 0x1C, 0x00, 0xF0, 0x03, 0xFF, 0x00, 0xEF, 0xC0, 0x78, 0x78, 0x1C, 0x0E,
+ 0x0E, 0x03, 0xC7, 0x80, 0x71, 0xC0, 0x1C, 0xF0, 0x03, 0xB8, 0x00, 0xFE,
+ 0x00, 0x3F, 0x00, 0x07, 0xC0, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x3E, 0x00,
+ 0x0F, 0xC0, 0x07, 0xF0, 0x01, 0xDC, 0x00, 0xF3, 0x80, 0x38, 0xE0, 0x1E,
+ 0x3C, 0x07, 0x07, 0x03, 0x81, 0xE1, 0xE0, 0x3F, 0x70, 0x0F, 0xFC, 0x00,
+ 0xF0, 0xE0, 0xE0, 0xFC, 0x1C, 0x1F, 0x83, 0x83, 0xF0, 0x70, 0x7E, 0x0E,
+ 0x0F, 0xC1, 0xC1, 0xF8, 0x38, 0x3F, 0x07, 0x07, 0xE0, 0xE0, 0xFC, 0x1C,
+ 0x1F, 0x83, 0x83, 0xF0, 0x70, 0x7E, 0x0E, 0x0F, 0xE1, 0xC3, 0xDC, 0x38,
+ 0x73, 0xE7, 0x3E, 0x3F, 0xFF, 0x83, 0xFF, 0xE0, 0x0F, 0xE0, 0x00, 0x70,
+ 0x00, 0x0E, 0x00, 0x01, 0xC0, 0x00, 0x38, 0x00, 0x07, 0x00, 0x00, 0xE0,
+ 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x1C, 0x00, 0x07, 0x0E, 0x00, 0x03,
+ 0x8E, 0x00, 0x00, 0xE7, 0x00, 0x00, 0x73, 0x80, 0x00, 0x3B, 0x80, 0x00,
+ 0x0F, 0xC0, 0x00, 0x07, 0xE0, 0x1C, 0x03, 0xF0, 0x0E, 0x01, 0xF8, 0x07,
+ 0x00, 0xFC, 0x03, 0x80, 0x7E, 0x01, 0xC0, 0x3F, 0x81, 0xF0, 0x3D, 0xC0,
+ 0xD8, 0x1C, 0xF0, 0xEE, 0x1E, 0x3F, 0xF7, 0xFE, 0x0F, 0xF1, 0xFE, 0x03,
+ 0xF0, 0x7E, 0x00, 0xF1, 0xFE, 0x3F, 0xC7, 0xF8, 0xF0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x01, 0xC0, 0x38, 0x07, 0x00,
+ 0xE0, 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x01, 0xC0, 0x38, 0x07, 0x80, 0x70,
+ 0x0F, 0xE0, 0xFC, 0x0F, 0x80, 0x7C, 0xF8, 0x7C, 0xF8, 0x7C, 0xF8, 0x7C,
+ 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0,
+ 0x1C, 0xE0, 0x1C, 0xE0, 0x0E, 0xE0, 0x0E, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xE0, 0x0F, 0xE0,
+ 0x0E, 0xE0, 0x1E, 0xF0, 0x3C, 0x78, 0x7C, 0x7F, 0xF8, 0x3F, 0xF0, 0x0F,
+ 0xC0, 0x00, 0x1E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0,
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00,
+ 0xFF, 0xC0, 0xFF, 0xFC, 0x3C, 0x0F, 0x1E, 0x01, 0xE7, 0x00, 0x3B, 0x80,
+ 0x0F, 0xE0, 0x01, 0xF8, 0x00, 0x7E, 0x00, 0x1F, 0x80, 0x07, 0xE0, 0x01,
+ 0xFC, 0x00, 0xF7, 0x00, 0x39, 0xE0, 0x1E, 0x3C, 0x0F, 0x0F, 0xFF, 0xC0,
+ 0xFF, 0xC0, 0x0F, 0xC0, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x01, 0x80,
+ 0x03, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xE0, 0x1C, 0xE0, 0x1C, 0xE0, 0x0E, 0xE0, 0x0E, 0xE0, 0x07, 0xE0, 0x07,
+ 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xE0, 0x0F,
+ 0xE0, 0x0E, 0xE0, 0x1E, 0xF0, 0x3C, 0x78, 0x7C, 0x7F, 0xF8, 0x3F, 0xF0,
+ 0x0F, 0xC0, 0x00, 0x01, 0xC0, 0x00, 0x01, 0xE0, 0x00, 0x00, 0xE0, 0x00,
+ 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07,
+ 0x07, 0x00, 0x01, 0xC3, 0x80, 0x00, 0xE3, 0x80, 0x00, 0x39, 0xC0, 0x00,
+ 0x1C, 0xE0, 0x00, 0x0E, 0xE0, 0x00, 0x03, 0xF0, 0x00, 0x01, 0xF8, 0x07,
+ 0x00, 0xFC, 0x03, 0x80, 0x7E, 0x01, 0xC0, 0x3F, 0x00, 0xE0, 0x1F, 0x80,
+ 0x70, 0x0F, 0xE0, 0x7C, 0x0F, 0x70, 0x36, 0x07, 0x3C, 0x3B, 0x87, 0x8F,
+ 0xFD, 0xFF, 0x83, 0xFC, 0x7F, 0x80, 0xFC, 0x1F, 0x80,
+};
+
+const GFXglyph FreeSans18pt_Win1253Glyphs[] PROGMEM = {
+/* 0x01 */ { 0, 33, 36, 45, 6, -29 },
+/* 0x02 */ { 149, 33, 36, 45, 6, -29 },
+/* 0x03 */ { 298, 35, 36, 45, 5, -29 },
+/* 0x04 */ { 456, 42, 36, 45, 1, -29 },
+/* 0x05 */ { 645, 35, 36, 45, 5, -29 },
+/* 0x06 */ { 803, 35, 36, 45, 5, -29 },
+/* 0x07 */ { 961, 0, 0, 0, 0, 0 },
+/* 0x08 */ { 961, 37, 36, 45, 4, -29 },
+/* 0x09 */ { 1128, 40, 28, 45, 2, -25 },
+/* 0x0A */ { 1268, 0, 0, 0, 0, 0 },
+/* 0x0B */ { 1268, 39, 36, 45, 3, -29 },
+/* 0x0C */ { 1444, 35, 36, 45, 5, -29 },
+/* 0x0D */ { 1602, 0, 0, 0, 0, 0 },
+/* 0x0E */ { 1602, 35, 36, 45, 5, -29 },
+/* 0x0F */ { 1760, 35, 37, 45, 5, -29 },
+/* 0x10 */ { 1922, 34, 36, 45, 5, -29 },
+/* 0x11 */ { 2075, 35, 36, 45, 5, -29 },
+/* 0x12 */ { 2233, 34, 36, 45, 5, -29 },
+/* 0x13 */ { 2386, 35, 36, 45, 5, -29 },
+/* 0x14 */ { 2544, 35, 36, 45, 5, -29 },
+/* 0x15 */ { 2702, 38, 36, 45, 4, -29 },
+/* 0x16 */ { 2873, 28, 36, 45, 8, -29 },
+/* 0x17 */ { 2999, 37, 30, 45, 4, -26 },
+/* 0x18 */ { 3138, 42, 30, 45, 1, -26 },
+/* 0x19 */ { 3296, 35, 36, 45, 5, -29 },
+/* 0x1A */ { 3454, 0, 0, 0, 0, 0 },
+/* 0x1B */ { 3454, 42, 37, 45, 1, -29 },
+/* 0x1C */ { 3649, 35, 36, 45, 5, -29 },
+/* 0x1D */ { 3807, 36, 36, 45, 4, -28 },
+/* 0x1E */ { 3969, 35, 36, 45, 4, -29 },
+/* 0x1F */ { 4127, 25, 36, 45, 10, -29 },
+/* 0x20 */ { 4240, 1, 1, 11, 0, 0 },
+/* 0x21 */ { 4241, 3, 26, 14, 5, -25 },
+/* 0x22 */ { 4251, 10, 9, 16, 3, -25 },
+/* 0x23 */ { 4263, 24, 25, 29, 3, -24 },
+/* 0x24 */ { 4338, 17, 32, 22, 3, -26 },
+/* 0x25 */ { 4406, 29, 26, 33, 2, -25 },
+/* 0x26 */ { 4501, 24, 26, 27, 2, -25 },
+/* 0x27 */ { 4579, 3, 9, 10, 3, -25 },
+/* 0x28 */ { 4583, 8, 31, 14, 3, -26 },
+/* 0x29 */ { 4614, 8, 31, 14, 3, -26 },
+/* 0x2A */ { 4645, 16, 16, 18, 1, -25 },
+/* 0x2B */ { 4677, 23, 23, 29, 4, -22 },
+/* 0x2C */ { 4744, 4, 8, 11, 3, -3 },
+/* 0x2D */ { 4748, 9, 3, 13, 2, -10 },
+/* 0x2E */ { 4752, 3, 4, 11, 4, -3 },
+/* 0x2F */ { 4754, 12, 29, 12, 0, -25 },
+/* 0x30 */ { 4798, 18, 26, 22, 2, -25 },
+/* 0x31 */ { 4857, 15, 26, 22, 4, -25 },
+/* 0x32 */ { 4906, 16, 26, 22, 3, -25 },
+/* 0x33 */ { 4958, 17, 26, 22, 3, -25 },
+/* 0x34 */ { 5014, 19, 26, 22, 2, -25 },
+/* 0x35 */ { 5076, 17, 26, 22, 3, -25 },
+/* 0x36 */ { 5132, 18, 26, 22, 2, -25 },
+/* 0x37 */ { 5191, 16, 26, 22, 3, -25 },
+/* 0x38 */ { 5243, 18, 26, 22, 2, -25 },
+/* 0x39 */ { 5302, 18, 26, 22, 2, -25 },
+/* 0x3A */ { 5361, 3, 18, 12, 4, -17 },
+/* 0x3B */ { 5368, 4, 22, 12, 3, -17 },
+/* 0x3C */ { 5379, 22, 19, 29, 4, -19 },
+/* 0x3D */ { 5432, 22, 10, 29, 4, -15 },
+/* 0x3E */ { 5460, 22, 19, 29, 4, -19 },
+/* 0x3F */ { 5513, 14, 26, 19, 3, -25 },
+/* 0x40 */ { 5559, 31, 31, 35, 2, -24 },
+/* 0x41 */ { 5680, 23, 26, 24, 0, -25 },
+/* 0x42 */ { 5755, 18, 26, 24, 3, -25 },
+/* 0x43 */ { 5814, 21, 26, 24, 2, -25 },
+/* 0x44 */ { 5883, 21, 26, 27, 3, -25 },
+/* 0x45 */ { 5952, 16, 26, 22, 3, -25 },
+/* 0x46 */ { 6004, 15, 26, 20, 3, -25 },
+/* 0x47 */ { 6053, 22, 26, 27, 2, -25 },
+/* 0x48 */ { 6125, 19, 26, 26, 3, -25 },
+/* 0x49 */ { 6187, 3, 26, 10, 3, -25 },
+/* 0x4A */ { 6197, 8, 33, 10, -2, -25 },
+/* 0x4B */ { 6230, 20, 26, 23, 3, -25 },
+/* 0x4C */ { 6295, 16, 26, 20, 3, -25 },
+/* 0x4D */ { 6347, 23, 26, 30, 3, -25 },
+/* 0x4E */ { 6422, 19, 26, 26, 3, -25 },
+/* 0x4F */ { 6484, 24, 26, 28, 2, -25 },
+/* 0x50 */ { 6562, 16, 26, 21, 3, -25 },
+/* 0x51 */ { 6614, 24, 31, 28, 2, -25 },
+/* 0x52 */ { 6707, 19, 26, 24, 3, -25 },
+/* 0x53 */ { 6769, 18, 26, 22, 2, -25 },
+/* 0x54 */ { 6828, 21, 26, 21, 0, -25 },
+/* 0x55 */ { 6897, 19, 26, 26, 3, -25 },
+/* 0x56 */ { 6959, 23, 26, 24, 0, -25 },
+/* 0x57 */ { 7034, 32, 26, 35, 1, -25 },
+/* 0x58 */ { 7138, 22, 26, 24, 1, -25 },
+/* 0x59 */ { 7210, 21, 26, 21, 0, -25 },
+/* 0x5A */ { 7279, 21, 26, 24, 2, -25 },
+/* 0x5B */ { 7348, 7, 31, 14, 3, -26 },
+/* 0x5C */ { 7376, 12, 29, 12, 0, -25 },
+/* 0x5D */ { 7420, 7, 31, 14, 3, -26 },
+/* 0x5E */ { 7448, 22, 10, 29, 4, -25 },
+/* 0x5F */ { 7476, 18, 3, 18, 0, 6 },
+/* 0x60 */ { 7483, 8, 6, 18, 3, -27 },
+/* 0x61 */ { 7489, 16, 19, 21, 2, -18 },
+/* 0x62 */ { 7527, 17, 27, 22, 3, -26 },
+/* 0x63 */ { 7585, 15, 19, 19, 2, -18 },
+/* 0x64 */ { 7621, 17, 27, 22, 2, -26 },
+/* 0x65 */ { 7679, 18, 19, 22, 2, -18 },
+/* 0x66 */ { 7722, 12, 27, 12, 1, -26 },
+/* 0x67 */ { 7763, 17, 26, 22, 2, -18 },
+/* 0x68 */ { 7819, 16, 27, 22, 3, -26 },
+/* 0x69 */ { 7873, 3, 27, 10, 3, -26 },
+/* 0x6A */ { 7884, 7, 34, 10, -1, -26 },
+/* 0x6B */ { 7914, 17, 27, 20, 3, -26 },
+/* 0x6C */ { 7972, 3, 27, 10, 3, -26 },
+/* 0x6D */ { 7983, 29, 19, 34, 3, -18 },
+/* 0x6E */ { 8052, 16, 19, 22, 3, -18 },
+/* 0x6F */ { 8090, 18, 19, 21, 2, -18 },
+/* 0x70 */ { 8133, 17, 26, 22, 3, -18 },
+/* 0x71 */ { 8189, 17, 26, 22, 2, -18 },
+/* 0x72 */ { 8245, 11, 19, 14, 3, -18 },
+/* 0x73 */ { 8272, 15, 19, 18, 2, -18 },
+/* 0x74 */ { 8308, 11, 24, 14, 1, -23 },
+/* 0x75 */ { 8341, 16, 19, 22, 3, -18 },
+/* 0x76 */ { 8379, 19, 19, 21, 1, -18 },
+/* 0x77 */ { 8425, 26, 19, 29, 1, -18 },
+/* 0x78 */ { 8487, 19, 19, 21, 1, -18 },
+/* 0x79 */ { 8533, 19, 26, 21, 1, -18 },
+/* 0x7A */ { 8595, 15, 19, 18, 2, -18 },
+/* 0x7B */ { 8631, 13, 32, 22, 5, -26 },
+/* 0x7C */ { 8683, 3, 35, 12, 4, -26 },
+/* 0x7D */ { 8697, 13, 32, 22, 4, -26 },
+/* 0x7E */ { 8749, 22, 6, 29, 4, -13 },
+/* 0x7F */ { 8766, 0, 0, 0, 0, 0 },
+/* 0x80 */ { 8766, 20, 26, 22, 0, -25 },
+/* 0x81 */ { 8831, 0, 0, 0, 0, 0 },
+/* 0x82 */ { 8831, 4, 8, 11, 3, -3 },
+/* 0x83 */ { 8835, 15, 34, 12, -2, -26 },
+/* 0x84 */ { 8899, 10, 8, 18, 3, -3 },
+/* 0x85 */ { 8909, 27, 4, 35, 4, -3 },
+/* 0x86 */ { 8923, 15, 29, 18, 1, -25 },
+/* 0x87 */ { 8978, 15, 29, 18, 1, -25 },
+/* 0x88 */ { 9033, 0, 0, 0, 0, 0 },
+/* 0x89 */ { 9033, 43, 26, 47, 2, -25 },
+/* 0x8A */ { 9173, 0, 0, 0, 0, 0 },
+/* 0x8B */ { 9173, 8, 16, 14, 3, -17 },
+/* 0x8C */ { 9189, 0, 0, 0, 0, 0 },
+/* 0x8D */ { 9189, 0, 0, 0, 0, 0 },
+/* 0x8E */ { 9189, 0, 0, 0, 0, 0 },
+/* 0x8F */ { 9189, 0, 0, 0, 0, 0 },
+/* 0x90 */ { 9189, 0, 0, 0, 0, 0 },
+/* 0x91 */ { 9189, 4, 8, 11, 3, -25 },
+/* 0x92 */ { 9193, 4, 8, 11, 3, -25 },
+/* 0x93 */ { 9197, 10, 8, 18, 3, -25 },
+/* 0x94 */ { 9207, 10, 8, 18, 3, -25 },
+/* 0x95 */ { 9217, 10, 10, 21, 5, -17 },
+/* 0x96 */ { 9230, 14, 3, 18, 2, -10 },
+/* 0x97 */ { 9236, 32, 3, 35, 2, -10 },
+/* 0x98 */ { 9248, 0, 0, 0, 0, 0 },
+/* 0x99 */ { 9248, 22, 10, 35, 5, -25 },
+/* 0x9A */ { 9276, 0, 0, 0, 0, 0 },
+/* 0x9B */ { 9276, 8, 16, 14, 3, -17 },
+/* 0x9C */ { 9292, 0, 0, 0, 0, 0 },
+/* 0x9D */ { 9292, 0, 0, 0, 0, 0 },
+/* 0x9E */ { 9292, 0, 0, 0, 0, 0 },
+/* 0x9F */ { 9292, 0, 0, 0, 0, 0 },
+/* 0xA0 */ { 9292, 1, 1, 11, 0, 0 },
+/* 0xA1 */ { 9293, 11, 11, 18, 4, -33 },
+/* 0xA2 */ { 9309, 23, 28, 24, 0, -27 },
+/* 0xA3 */ { 9390, 17, 26, 22, 2, -25 },
+/* 0xA4 */ { 9446, 19, 19, 22, 2, -19 },
+/* 0xA5 */ { 9492, 19, 26, 22, 1, -25 },
+/* 0xA6 */ { 9554, 3, 31, 12, 4, -24 },
+/* 0xA7 */ { 9566, 14, 29, 18, 2, -25 },
+/* 0xA8 */ { 9617, 11, 4, 18, 4, -26 },
+/* 0xA9 */ { 9623, 25, 25, 35, 5, -24 },
+/* 0xAA */ { 9702, 0, 0, 0, 0, 0 },
+/* 0xAB */ { 9702, 15, 16, 21, 3, -17 },
+/* 0xAC */ { 9732, 22, 10, 29, 4, -14 },
+/* 0xAD */ { 9760, 9, 3, 13, 2, -10 },
+/* 0xAE */ { 9764, 25, 25, 35, 5, -24 },
+/* 0xAF */ { 9843, 35, 3, 35, 0, -10 },
+/* 0xB0 */ { 9857, 11, 11, 18, 3, -25 },
+/* 0xB1 */ { 9873, 23, 22, 29, 3, -21 },
+/* 0xB2 */ { 9937, 10, 14, 14, 2, -25 },
+/* 0xB3 */ { 9955, 11, 14, 14, 2, -25 },
+/* 0xB4 */ { 9975, 8, 6, 18, 7, -27 },
+/* 0xB5 */ { 9981, 11, 11, 18, 4, -33 },
+/* 0xB6 */ { 9997, 23, 28, 24, 0, -27 },
+/* 0xB7 */ { 10078, 3, 4, 11, 4, -13 },
+/* 0xB8 */ { 10080, 23, 28, 26, 0, -27 },
+/* 0xB9 */ { 10161, 26, 28, 30, 0, -27 },
+/* 0xBA */ { 10252, 10, 28, 14, 0, -27 },
+/* 0xBB */ { 10287, 15, 16, 21, 3, -17 },
+/* 0xBC */ { 10317, 27, 28, 28, 0, -27 },
+/* 0xBD */ { 10412, 29, 26, 34, 3, -25 },
+/* 0xBE */ { 10507, 28, 28, 29, 0, -27 },
+/* 0xBF */ { 10605, 27, 28, 29, 0, -27 },
+/* 0xC0 */ { 10700, 11, 34, 12, 0, -33 },
+/* 0xC1 */ { 10747, 23, 26, 24, 0, -25 },
+/* 0xC2 */ { 10822, 18, 26, 24, 3, -25 },
+/* 0xC3 */ { 10881, 15, 26, 19, 3, -25 },
+/* 0xC4 */ { 10930, 23, 26, 24, 0, -25 },
+/* 0xC5 */ { 11005, 16, 26, 22, 3, -25 },
+/* 0xC6 */ { 11057, 21, 26, 24, 2, -25 },
+/* 0xC7 */ { 11126, 19, 26, 26, 3, -25 },
+/* 0xC8 */ { 11188, 24, 26, 28, 2, -25 },
+/* 0xC9 */ { 11266, 3, 26, 10, 3, -25 },
+/* 0xCA */ { 11276, 20, 26, 23, 3, -25 },
+/* 0xCB */ { 11341, 23, 26, 24, 0, -25 },
+/* 0xCC */ { 11416, 23, 26, 30, 3, -25 },
+/* 0xCD */ { 11491, 19, 26, 26, 3, -25 },
+/* 0xCE */ { 11553, 16, 26, 22, 3, -25 },
+/* 0xCF */ { 11605, 24, 26, 28, 2, -25 },
+/* 0xD0 */ { 11683, 19, 26, 25, 3, -25 },
+/* 0xD1 */ { 11745, 16, 26, 21, 3, -25 },
+/* 0xD2 */ { 11797, 0, 0, 0, 0, 0 },
+/* 0xD3 */ { 11797, 16, 26, 22, 3, -25 },
+/* 0xD4 */ { 11849, 21, 26, 21, 0, -25 },
+/* 0xD5 */ { 11918, 21, 26, 21, 0, -25 },
+/* 0xD6 */ { 11987, 25, 26, 29, 2, -25 },
+/* 0xD7 */ { 12069, 22, 26, 24, 1, -25 },
+/* 0xD8 */ { 12141, 25, 26, 29, 2, -25 },
+/* 0xD9 */ { 12223, 24, 26, 28, 2, -25 },
+/* 0xDA */ { 12301, 11, 32, 10, -1, -31 },
+/* 0xDB */ { 12345, 21, 32, 21, 0, -31 },
+/* 0xDC */ { 12429, 19, 28, 23, 2, -27 },
+/* 0xDD */ { 12496, 15, 28, 19, 2, -27 },
+/* 0xDE */ { 12549, 16, 35, 22, 3, -27 },
+/* 0xDF */ { 12619, 9, 28, 12, 3, -27 },
+/* 0xE0 */ { 12651, 16, 35, 21, 3, -33 },
+/* 0xE1 */ { 12721, 19, 19, 23, 2, -18 },
+/* 0xE2 */ { 12767, 17, 34, 22, 3, -26 },
+/* 0xE3 */ { 12840, 19, 26, 21, 1, -18 },
+/* 0xE4 */ { 12902, 18, 26, 22, 2, -25 },
+/* 0xE5 */ { 12961, 15, 19, 19, 2, -18 },
+/* 0xE6 */ { 12997, 16, 34, 20, 2, -26 },
+/* 0xE7 */ { 13065, 16, 26, 22, 3, -18 },
+/* 0xE8 */ { 13117, 18, 27, 22, 2, -26 },
+/* 0xE9 */ { 13178, 8, 19, 12, 3, -18 },
+/* 0xEA */ { 13197, 17, 19, 21, 3, -18 },
+/* 0xEB */ { 13238, 19, 27, 21, 1, -26 },
+/* 0xEC */ { 13303, 18, 26, 22, 3, -18 },
+/* 0xED */ { 13362, 17, 19, 20, 1, -18 },
+/* 0xEE */ { 13403, 15, 34, 19, 2, -26 },
+/* 0xEF */ { 13467, 18, 19, 21, 2, -18 },
+/* 0xF0 */ { 13510, 18, 19, 21, 2, -18 },
+/* 0xF1 */ { 13553, 18, 26, 23, 3, -18 },
+/* 0xF2 */ { 13612, 15, 26, 20, 2, -18 },
+/* 0xF3 */ { 13661, 20, 19, 23, 2, -18 },
+/* 0xF4 */ { 13709, 17, 19, 21, 2, -18 },
+/* 0xF5 */ { 13750, 16, 19, 21, 3, -17 },
+/* 0xF6 */ { 13788, 19, 26, 23, 2, -18 },
+/* 0xF7 */ { 13850, 18, 26, 20, 1, -18 },
+/* 0xF8 */ { 13909, 19, 26, 23, 2, -18 },
+/* 0xF9 */ { 13971, 25, 19, 29, 2, -17 },
+/* 0xFA */ { 14031, 11, 27, 12, 0, -26 },
+/* 0xFB */ { 14069, 16, 28, 21, 3, -26 },
+/* 0xFC */ { 14125, 18, 28, 21, 2, -27 },
+/* 0xFD */ { 14188, 16, 29, 21, 3, -27 },
+/* 0xFE */ { 14246, 25, 29, 29, 2, -27 },
+/* 0xFF */ { 14337, 0, 0, 0, 0, 0 },
+};
+
+const GFXfont FreeSans18pt_Win1253 PROGMEM = {
+(uint8_t*)FreeSans18pt_Win1253Bitmaps,
+(GFXglyph*)FreeSans18pt_Win1253Glyphs,
+0x01, 0xFF, 41
+};
diff --git a/src/graphics/niche/Fonts/FreeSans24pt_Win1253.h b/src/graphics/niche/Fonts/FreeSans24pt_Win1253.h
new file mode 100644
index 000000000..7efefd443
--- /dev/null
+++ b/src/graphics/niche/Fonts/FreeSans24pt_Win1253.h
@@ -0,0 +1,2429 @@
+// trunk-ignore-all(clang-format)
+#pragma once
+/* PROPERTIES
+
+FONT_NAME FreeSans24pt_Win1253
+*/
+const uint8_t FreeSans24pt_Win1253Bitmaps[] PROGMEM = {
+ 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00,
+ 0x00, 0x0C, 0xE0, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x00, 0x00, 0x00, 0x00,
+ 0x60, 0xC0, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x01, 0x83,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0x00, 0x00, 0x00, 0x06, 0x0C, 0x00,
+ 0x00, 0x00, 0x00, 0xC1, 0x80, 0x00, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00,
+ 0x00, 0x06, 0x06, 0x00, 0x00, 0x00, 0x01, 0xC0, 0xC0, 0x00, 0x00, 0x00,
+ 0x70, 0x30, 0x00, 0x00, 0x00, 0x1C, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00,
+ 0xC0, 0x00, 0x00, 0x03, 0x80, 0x13, 0xFF, 0x00, 0x00, 0xE0, 0x07, 0xFF,
+ 0xF0, 0x00, 0x70, 0x00, 0xF0, 0x06, 0x00, 0x0C, 0x00, 0x38, 0x00, 0xC0,
+ 0x03, 0x00, 0x06, 0x00, 0x18, 0x00, 0x60, 0x00, 0xC0, 0x03, 0x01, 0xFC,
+ 0x00, 0x18, 0x00, 0x67, 0xFF, 0x00, 0x03, 0x80, 0x38, 0xC0, 0x00, 0x00,
+ 0x3F, 0xFF, 0xD8, 0x00, 0x00, 0x07, 0xFE, 0x1F, 0x00, 0x00, 0x01, 0xE0,
+ 0x01, 0xE0, 0x00, 0x00, 0x3C, 0x00, 0x1C, 0x00, 0x00, 0x0F, 0x00, 0x03,
+ 0x80, 0x00, 0x03, 0xA0, 0x00, 0xF0, 0x00, 0x00, 0xE6, 0x00, 0x1E, 0x00,
+ 0x00, 0x78, 0xF0, 0x1F, 0xC0, 0x00, 0x1C, 0x0F, 0xFF, 0xD8, 0x00, 0x02,
+ 0x01, 0xC0, 0x7B, 0x00, 0x00, 0x00, 0x60, 0x03, 0xE0, 0x00, 0x00, 0x0C,
+ 0x00, 0x3C, 0x00, 0x00, 0x01, 0x80, 0x07, 0x80, 0x00, 0x00, 0x30, 0x00,
+ 0xF0, 0x00, 0x00, 0x07, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x7F, 0x86, 0xFF,
+ 0x80, 0x00, 0x1F, 0xFF, 0x9F, 0xFE, 0x00, 0x03, 0x00, 0xC0, 0x01, 0xF0,
+ 0x00, 0xC0, 0x18, 0x00, 0x07, 0x00, 0x18, 0x03, 0x00, 0x00, 0x3C, 0x01,
+ 0x80, 0x60, 0x00, 0x03, 0xFC, 0x7E, 0x1C, 0x00, 0x00, 0x0F, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x01, 0x80, 0xF8, 0x00,
+ 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xE0, 0x78, 0x38, 0x00, 0x03,
+ 0xC0, 0x0C, 0x03, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x60, 0x00, 0xF8, 0x00,
+ 0x60, 0x0C, 0x3F, 0xFC, 0x00, 0x06, 0x03, 0xC7, 0xF8, 0x00, 0x00, 0xFF,
+ 0xFC, 0xC0, 0x00, 0x00, 0x0F, 0xE0, 0xD8, 0x00, 0x00, 0x03, 0x80, 0x0F,
+ 0x00, 0x00, 0x00, 0x60, 0x01, 0xE0, 0x00, 0x00, 0x0C, 0x00, 0x3C, 0x00,
+ 0x00, 0x01, 0x80, 0x07, 0x80, 0x00, 0x00, 0x30, 0x01, 0xB0, 0x00, 0x04,
+ 0x03, 0xFF, 0xF6, 0x00, 0x00, 0xE0, 0x7F, 0xFE, 0xC0, 0x00, 0x0F, 0x1C,
+ 0x01, 0xF8, 0x00, 0x00, 0x73, 0x00, 0x0F, 0x00, 0x00, 0x07, 0xC0, 0x01,
+ 0xE0, 0x00, 0x00, 0x78, 0x00, 0x1C, 0x00, 0x00, 0x07, 0x80, 0x07, 0x80,
+ 0x00, 0x00, 0xF8, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0x3F, 0xC0, 0x00,
+ 0x01, 0xFF, 0xFE, 0xFF, 0xE0, 0x00, 0x70, 0x03, 0x80, 0x7E, 0x00, 0x0C,
+ 0x00, 0x30, 0x00, 0xC0, 0x01, 0x80, 0x06, 0x00, 0x18, 0x00, 0x30, 0x00,
+ 0xC0, 0x01, 0x80, 0x07, 0x00, 0x18, 0x00, 0x18, 0x00, 0x78, 0x03, 0x00,
+ 0x01, 0xC0, 0x0F, 0xFF, 0xC0, 0x00, 0x1E, 0x00, 0x8F, 0xF0, 0x00, 0x00,
+ 0xE0, 0x18, 0x00, 0x00, 0x00, 0x0E, 0x03, 0x00, 0x00, 0x00, 0x00, 0x60,
+ 0x60, 0x00, 0x00, 0x00, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0,
+ 0x00, 0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x01, 0x83, 0x00, 0x00,
+ 0x00, 0x00, 0x30, 0x60, 0x00, 0x00, 0x00, 0x06, 0x0C, 0x00, 0x00, 0x00,
+ 0x00, 0xC1, 0x80, 0x00, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x00, 0x03,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x00, 0x00, 0x00, 0x06, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00,
+ 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+ 0x00, 0x07, 0xE0, 0x07, 0xE0, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xF8, 0x00,
+ 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00,
+ 0x00, 0xE0, 0x00, 0x00, 0x07, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60,
+ 0x0E, 0x00, 0x00, 0x00, 0x00, 0x70, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x30,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x00, 0x00, 0x00, 0x00, 0x1C,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x70, 0x00, 0x00, 0x00, 0x00, 0x0C,
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x03, 0xC0, 0x03, 0xC0, 0x06,
+ 0x60, 0x07, 0xE0, 0x07, 0xE0, 0x06, 0xC0, 0x0C, 0x30, 0x0C, 0x30, 0x03,
+ 0xC0, 0x18, 0x30, 0x0C, 0x38, 0x03, 0xC0, 0x18, 0x18, 0x18, 0x18, 0x03,
+ 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03,
+ 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x0D, 0x00, 0x00, 0x4A, 0x03,
+ 0xC0, 0x69, 0x00, 0x00, 0xD2, 0x03, 0xC0, 0x4B, 0x00, 0x00, 0x96, 0x03,
+ 0xC0, 0xD2, 0x00, 0x01, 0xA4, 0x03, 0x60, 0x10, 0x00, 0x00, 0x20, 0x06,
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C,
+ 0x38, 0x01, 0xF0, 0x0F, 0x80, 0x1C, 0x18, 0x00, 0x7F, 0xFE, 0x00, 0x18,
+ 0x1C, 0x00, 0x0F, 0xF0, 0x00, 0x30, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x70,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0,
+ 0x03, 0x80, 0x00, 0x00, 0x01, 0x80, 0x01, 0xE0, 0x00, 0x00, 0x07, 0x00,
+ 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00,
+ 0x00, 0x1F, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x07, 0xE0, 0x07, 0xE0, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFC,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0xC0, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x1F,
+ 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x1E, 0x00, 0x00,
+ 0x00, 0x1C, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00,
+ 0x1C, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x38,
+ 0x38, 0x00, 0x0C, 0x3C, 0x00, 0x00, 0x0F, 0x0C, 0x00, 0x0E, 0x38, 0x00,
+ 0x00, 0x01, 0xC7, 0x00, 0x06, 0x38, 0x00, 0x00, 0x00, 0x71, 0x80, 0x07,
+ 0x18, 0x00, 0x00, 0x00, 0x18, 0xE0, 0x03, 0x08, 0x00, 0x00, 0x00, 0x04,
+ 0x30, 0x01, 0x80, 0x3E, 0x00, 0x07, 0xC0, 0x18, 0x00, 0xC0, 0x71, 0xC0,
+ 0x0C, 0x38, 0x0C, 0x00, 0xC0, 0x60, 0x30, 0x0C, 0x06, 0x03, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xC0, 0x1F, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0xE0, 0x1F, 0x60, 0x00, 0x00,
+ 0x00, 0x06, 0xF8, 0x3C, 0x30, 0x00, 0x00, 0x00, 0x03, 0x0E, 0x38, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x83, 0x98, 0x18, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0xD8, 0x0C, 0x60, 0x00, 0x00, 0x06, 0x30, 0x3C, 0x04, 0x6F, 0xC0, 0x00,
+ 0xFF, 0x98, 0x1E, 0x02, 0x30, 0x1F, 0xFF, 0xE0, 0xC4, 0x0F, 0x03, 0x1C,
+ 0x00, 0x00, 0x00, 0xE2, 0x07, 0x81, 0x07, 0xE0, 0x00, 0x07, 0xE1, 0x83,
+ 0x61, 0x83, 0xFF, 0xFF, 0xFF, 0xF0, 0x63, 0x1F, 0xC0, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x3F, 0x86, 0x30, 0x3F, 0xFF, 0xFF, 0xF0, 0x32, 0x00, 0x18, 0x0F,
+ 0xFF, 0xFF, 0xF0, 0x18, 0x00, 0x06, 0x03, 0xFC, 0x0F, 0xF0, 0x18, 0x00,
+ 0x03, 0x80, 0x78, 0x01, 0xE0, 0x1C, 0x00, 0x00, 0xE0, 0x0E, 0x01, 0xC0,
+ 0x1C, 0x00, 0x00, 0x30, 0x00, 0xFF, 0x00, 0x0C, 0x00, 0x00, 0x0E, 0x00,
+ 0x00, 0x00, 0x1C, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0xE0, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C,
+ 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0xF8,
+ 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 0x00, 0x00,
+ 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x1F, 0x03, 0x80, 0x00, 0x00,
+ 0x1F, 0x3F, 0x03, 0x00, 0x00, 0x00, 0x7F, 0xC3, 0x00, 0x07, 0x80, 0x00,
+ 0xC7, 0x83, 0x00, 0x1F, 0x80, 0x03, 0x07, 0x03, 0x00, 0x63, 0x00, 0x06,
+ 0x07, 0x03, 0x01, 0xC6, 0x00, 0x06, 0x07, 0x03, 0x03, 0x0C, 0x00, 0x06,
+ 0x07, 0x03, 0x06, 0x18, 0x00, 0x0E, 0x07, 0x03, 0x0C, 0x30, 0x00, 0x7E,
+ 0x07, 0x03, 0x18, 0x60, 0x00, 0xC6, 0x07, 0x03, 0x30, 0xC0, 0x03, 0x06,
+ 0x07, 0x03, 0x61, 0x80, 0x06, 0x06, 0x07, 0x03, 0xC1, 0x80, 0x0E, 0x06,
+ 0x07, 0x03, 0x83, 0x00, 0x0E, 0x06, 0x07, 0x03, 0x06, 0x00, 0x0E, 0x06,
+ 0x06, 0x06, 0x06, 0x00, 0x3E, 0x06, 0x00, 0x18, 0x0C, 0x00, 0xFE, 0x06,
+ 0x00, 0x30, 0x18, 0x03, 0x8E, 0x06, 0x00, 0x40, 0x18, 0x06, 0x0E, 0x06,
+ 0x01, 0x80, 0x30, 0x0C, 0x0E, 0x00, 0x03, 0x00, 0x30, 0x1C, 0x0E, 0x00,
+ 0x06, 0x00, 0x60, 0x1C, 0x0E, 0x00, 0x0C, 0x00, 0x60, 0x1C, 0x0E, 0x00,
+ 0x18, 0x00, 0xC0, 0x1C, 0x08, 0x00, 0x30, 0x01, 0x80, 0x1C, 0x00, 0x00,
+ 0x30, 0x03, 0x0C, 0x1C, 0x00, 0x00, 0x60, 0x02, 0x0C, 0x1C, 0x00, 0x00,
+ 0x60, 0x0F, 0x0E, 0x1C, 0x00, 0x00, 0xC0, 0x1B, 0x0C, 0x1C, 0x00, 0x00,
+ 0x00, 0x36, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x67, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0xC7, 0x80, 0x1C, 0x00, 0x00, 0x03, 0x07, 0x00, 0x1C, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1C, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x0C, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x0E, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x0F, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x07, 0x80, 0xF0,
+ 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00,
+ 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x01, 0x80, 0x06, 0x60, 0x00, 0x00,
+ 0x07, 0x80, 0x1C, 0xC0, 0x00, 0x00, 0x0F, 0xC0, 0x31, 0x80, 0x00, 0x00,
+ 0x19, 0xC0, 0xC3, 0x00, 0x00, 0x00, 0x11, 0xE1, 0x82, 0x00, 0x00, 0x00,
+ 0x30, 0xE6, 0x06, 0x00, 0x00, 0x00, 0x60, 0xFF, 0xCC, 0x00, 0x00, 0x00,
+ 0xC1, 0xFF, 0xF8, 0x3F, 0x80, 0x01, 0x8F, 0x80, 0xFF, 0xFF, 0x00, 0x01,
+ 0xBC, 0x00, 0x7E, 0x06, 0x00, 0x03, 0xE0, 0x00, 0x38, 0x18, 0x00, 0x07,
+ 0x80, 0x00, 0x38, 0x70, 0x00, 0x0E, 0x00, 0x00, 0x39, 0xC0, 0x01, 0xF8,
+ 0x00, 0x00, 0x33, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x7C, 0x03, 0xF0, 0xC0,
+ 0x00, 0x00, 0x78, 0x06, 0x03, 0x80, 0x00, 0x00, 0xE0, 0x0E, 0x06, 0x00,
+ 0x00, 0x00, 0xC0, 0x0F, 0x0C, 0x00, 0x00, 0x01, 0xE0, 0x07, 0x18, 0x00,
+ 0x00, 0x03, 0xE0, 0x07, 0xB0, 0x00, 0x00, 0x06, 0xF0, 0x03, 0xE0, 0x00,
+ 0x00, 0x0C, 0x70, 0x01, 0xC0, 0x00, 0x00, 0x18, 0x78, 0x01, 0x80, 0x00,
+ 0x00, 0x30, 0x38, 0x03, 0x80, 0x00, 0x00, 0xC0, 0x30, 0x0F, 0x00, 0x00,
+ 0x01, 0x8F, 0xE0, 0x3F, 0x00, 0x00, 0x07, 0xFF, 0x00, 0x66, 0x00, 0x00,
+ 0x0F, 0xC0, 0x01, 0x8E, 0x00, 0x00, 0x38, 0x00, 0x07, 0x0E, 0x00, 0x00,
+ 0xF0, 0x00, 0x0C, 0x0E, 0x00, 0x03, 0xE0, 0x00, 0x30, 0x3F, 0x00, 0x1E,
+ 0xC0, 0x00, 0x6F, 0xFF, 0x80, 0xF8, 0x80, 0x00, 0xFE, 0x0F, 0xFF, 0xC1,
+ 0x80, 0x00, 0xC0, 0x19, 0xFF, 0x83, 0x00, 0x00, 0x00, 0x30, 0x33, 0x86,
+ 0x00, 0x00, 0x00, 0x60, 0xE3, 0xCC, 0x00, 0x00, 0x00, 0x41, 0x81, 0xCC,
+ 0x00, 0x00, 0x00, 0xC6, 0x01, 0xF8, 0x00, 0x00, 0x01, 0x9C, 0x00, 0xF0,
+ 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x06, 0xC0, 0x00, 0x00,
+ 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xC0, 0x00,
+ 0x00, 0x00, 0x00, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00,
+ 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0xE0, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x60, 0x00, 0x01, 0x80, 0x00, 0x00, 0x30, 0x00, 0x00,
+ 0xC0, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x30, 0x00, 0x07, 0xFE, 0x00, 0x00,
+ 0x18, 0x00, 0x07, 0x83, 0xC0, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x60, 0x00,
+ 0x02, 0x00, 0x03, 0x00, 0x18, 0x00, 0x01, 0x00, 0x03, 0x80, 0x06, 0x00,
+ 0x01, 0x80, 0x01, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x01, 0x80,
+ 0x00, 0x70, 0x07, 0xE0, 0x00, 0x00, 0x00, 0x3E, 0x0F, 0xF0, 0x00, 0x00,
+ 0x00, 0x01, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x9E, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x60, 0x00, 0x60, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x78,
+ 0x00, 0x78, 0x00, 0x78, 0x00, 0x7C, 0x00, 0x7C, 0x00, 0x7C, 0x00, 0x3E,
+ 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x07,
+ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0xC0, 0x00, 0x00,
+ 0x00, 0x70, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x78, 0x00, 0xF0, 0x00, 0x00,
+ 0x00, 0x7C, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x7C, 0x00, 0x00,
+ 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF8,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0x80, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x78,
+ 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0xE0, 0x00, 0x00, 0x00,
+ 0x1C, 0x00, 0x01, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x03, 0x80, 0x00,
+ 0x00, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x0C,
+ 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0x30, 0x00, 0x00, 0xFF, 0xE0, 0x00,
+ 0x00, 0xE0, 0x00, 0x07, 0x83, 0xC0, 0x00, 0x01, 0x80, 0x00, 0x38, 0x03,
+ 0x80, 0x00, 0x06, 0x00, 0x01, 0xC0, 0x07, 0x00, 0x00, 0x18, 0x00, 0x06,
+ 0x00, 0x0C, 0x00, 0x00, 0x60, 0x00, 0x38, 0x00, 0x38, 0x00, 0x01, 0x80,
+ 0x00, 0xC0, 0x00, 0x60, 0x00, 0x06, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00,
+ 0x1C, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x03, 0xF0, 0x00, 0x00,
+ 0x00, 0x00, 0x38, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xC0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xB8,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x19, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x81, 0x80, 0x00, 0x00, 0x00, 0x38,
+ 0x0F, 0xFF, 0x00, 0x00, 0x00, 0x0F, 0xC0, 0x07, 0xF7, 0x00, 0x60, 0x00,
+ 0x7C, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x07, 0x00, 0x00, 0x00, 0x07, 0xF3,
+ 0xC0, 0x78, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xC0, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0xF8, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x1F, 0xE0, 0x00,
+ 0x1F, 0xFE, 0x00, 0x07, 0xFF, 0x80, 0x07, 0xCF, 0xF8, 0x01, 0xF3, 0xBE,
+ 0x00, 0xFC, 0xD9, 0xC0, 0x38, 0x3C, 0xF0, 0x1F, 0xEC, 0xFE, 0x07, 0xE3,
+ 0x6F, 0x83, 0xB7, 0xC7, 0xB0, 0xDF, 0x33, 0xD8, 0x33, 0x1C, 0x39, 0x99,
+ 0xBB, 0x1C, 0xC7, 0xF0, 0xC1, 0xD9, 0xD9, 0xF0, 0xCE, 0x6F, 0x0E, 0x1E,
+ 0xFF, 0x8F, 0x0E, 0x66, 0x70, 0xF1, 0xB6, 0x78, 0x30, 0xF6, 0xC3, 0x8D,
+ 0x99, 0xE1, 0x83, 0x8D, 0xBC, 0x3C, 0xCD, 0x8E, 0x1C, 0x3C, 0xCF, 0xE3,
+ 0x6C, 0x78, 0x61, 0xE3, 0x6C, 0x7F, 0x33, 0xC3, 0x87, 0x1B, 0x33, 0xC3,
+ 0xFB, 0x1C, 0x1C, 0x79, 0x9B, 0x1C, 0x3D, 0xF0, 0xC1, 0xE6, 0xD8, 0xF0,
+ 0xE3, 0xC7, 0x0E, 0x1F, 0x67, 0x87, 0x0F, 0x3C, 0x30, 0xF1, 0xBE, 0x38,
+ 0x30, 0xFB, 0x63, 0x8D, 0x98, 0xE1, 0xC3, 0x8D, 0xE6, 0x3C, 0xCF, 0x86,
+ 0x1E, 0x3E, 0xC6, 0x63, 0x6C, 0x78, 0x71, 0xF3, 0x7C, 0x63, 0x33, 0xC3,
+ 0x87, 0x99, 0xB3, 0xCC, 0x3B, 0x1C, 0x1C, 0x6D, 0x8F, 0x1C, 0xC1, 0xF0,
+ 0xE1, 0xE6, 0x78, 0x70, 0xF8, 0x1F, 0x0F, 0x1B, 0x63, 0x83, 0x0F, 0x80,
+ 0xF8, 0xF9, 0x9E, 0x1C, 0x38, 0xF0, 0x0F, 0xCD, 0xD8, 0xE1, 0xE3, 0xCF,
+ 0x00, 0x7E, 0xCF, 0x86, 0x1F, 0x36, 0xE0, 0x03, 0x3C, 0x38, 0x71, 0xBB,
+ 0x3C, 0x00, 0x39, 0xC1, 0x87, 0x99, 0xF1, 0xC0, 0x01, 0xCC, 0x1C, 0x6D,
+ 0x87, 0x38, 0x00, 0x0C, 0xE1, 0xE6, 0x78, 0x33, 0x00, 0x00, 0x6F, 0x1B,
+ 0x63, 0x83, 0xE0, 0x00, 0x03, 0xD9, 0x9E, 0x1C, 0x3C, 0x00, 0x00, 0x1C,
+ 0xF8, 0xE1, 0xE3, 0x80, 0x00, 0x00, 0xC7, 0x87, 0x1F, 0x30, 0x00, 0x00,
+ 0x06, 0x38, 0x79, 0x9E, 0x00, 0x00, 0x00, 0x31, 0xC7, 0xD8, 0xC0, 0x00,
+ 0x00, 0x01, 0x9E, 0x6F, 0x98, 0x00, 0x00, 0x00, 0x0D, 0xB6, 0x3B, 0x00,
+ 0x00, 0x00, 0x00, 0x79, 0xE1, 0xE0, 0x00, 0x00, 0x00, 0x03, 0x8E, 0x1C,
+ 0x00, 0x00, 0x00, 0x00, 0x0C, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67,
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x03, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x00,
+ 0x3F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x00, 0x00, 0x01,
+ 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07,
+ 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x1F,
+ 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF,
+ 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF,
+ 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF,
+ 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF,
+ 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x3F, 0xE3, 0xFF,
+ 0x1F, 0xF0, 0x00, 0x7F, 0x03, 0xF8, 0x0F, 0xE0, 0x00, 0xFC, 0x03, 0xE0,
+ 0x0F, 0xE0, 0x07, 0xF0, 0xC3, 0x87, 0x1F, 0xC0, 0x1F, 0xE3, 0xC7, 0x1F,
+ 0x1F, 0xE0, 0xFF, 0xC7, 0x8E, 0x3E, 0x3F, 0xE1, 0xFF, 0x8F, 0x1C, 0x7C,
+ 0x7F, 0xE7, 0xFF, 0x0C, 0x38, 0x71, 0xFF, 0xDF, 0xFF, 0x00, 0xF8, 0x03,
+ 0xFF, 0xFF, 0xFF, 0x03, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0x9F, 0xFC, 0x7F,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF,
+ 0xF7, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xEF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF,
+ 0x8F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0x1F, 0xFF, 0xC0, 0x00, 0xFF, 0xFC,
+ 0x1F, 0xFF, 0xE0, 0x07, 0xFF, 0xF0, 0x1F, 0xFF, 0xF0, 0x3F, 0xFF, 0xE0,
+ 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
+ 0x0F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x18, 0xE0, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x00, 0x00, 0x00,
+ 0x01, 0x81, 0x80, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x06, 0x38, 0x00, 0x00, 0x00, 0x00,
+ 0x07, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x03,
+ 0xC0, 0x3C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x0E, 0x00, 0x00, 0x01, 0x80, 0x00, 0x0E, 0x00, 0x00, 0x06, 0x04,
+ 0x00, 0x0C, 0x00, 0x00, 0x08, 0x38, 0x00, 0x0C, 0x00, 0x00, 0x30, 0xE0,
+ 0x00, 0x08, 0x00, 0x00, 0x43, 0x00, 0x00, 0x18, 0x00, 0x01, 0x86, 0x00,
+ 0x00, 0x10, 0x00, 0x03, 0x18, 0x00, 0x00, 0x30, 0x00, 0x04, 0x30, 0x00,
+ 0x00, 0x60, 0x00, 0x18, 0x40, 0x00, 0x00, 0xC0, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x60, 0x00, 0x00, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00,
+ 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0C, 0x00, 0x04, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x20, 0x00, 0x60, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x80, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x3F, 0xFF, 0xFE, 0x00,
+ 0x80, 0x07, 0xE0, 0x00, 0x00, 0x01, 0x80, 0x18, 0x00, 0x00, 0x00, 0x01,
+ 0x80, 0x60, 0x00, 0x00, 0x00, 0x03, 0x81, 0x80, 0x00, 0x00, 0x00, 0x03,
+ 0x86, 0x00, 0x00, 0x00, 0x00, 0x03, 0x18, 0x00, 0xFF, 0xFF, 0xF0, 0x03,
+ 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFB,
+ 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xFB,
+ 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xF3, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0x81,
+ 0xFF, 0xFF, 0x87, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00,
+ 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00,
+ 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x07, 0xE0, 0x00, 0x00,
+ 0x1F, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x0E, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x07, 0x00, 0x03,
+ 0x80, 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x06,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x0E, 0x08, 0x00, 0x00, 0x30, 0x70, 0x1C,
+ 0x3C, 0x00, 0x00, 0x3C, 0x38, 0x18, 0xE0, 0x00, 0x00, 0x0F, 0x18, 0x39,
+ 0xC0, 0x00, 0x00, 0x03, 0x9C, 0x33, 0x80, 0x00, 0x00, 0x01, 0xCC, 0x73,
+ 0x00, 0x00, 0x00, 0x00, 0xCC, 0x60, 0x1F, 0x00, 0x00, 0xF8, 0x06, 0x60,
+ 0x7F, 0xC0, 0x03, 0xFE, 0x06, 0x60, 0xF3, 0xE0, 0x07, 0x8F, 0x06, 0xC0,
+ 0xF3, 0xA0, 0x07, 0x8F, 0x03, 0xC0, 0x33, 0x80, 0x03, 0x8C, 0x03, 0xC0,
+ 0x33, 0x80, 0x03, 0x8C, 0x03, 0xC0, 0x33, 0x80, 0x03, 0x8C, 0x03, 0xC0,
+ 0x33, 0x80, 0x03, 0x8C, 0x03, 0xC0, 0x33, 0x80, 0x03, 0x8C, 0x03, 0xC0,
+ 0x33, 0x80, 0x03, 0x8C, 0x03, 0xC0, 0x33, 0xBF, 0xFB, 0x8C, 0x03, 0xC0,
+ 0x33, 0xFC, 0x7F, 0x8C, 0x03, 0xC0, 0x33, 0xC0, 0x07, 0x8C, 0x03, 0x60,
+ 0x33, 0x80, 0x03, 0x8C, 0x06, 0x60, 0x33, 0x80, 0x03, 0x8C, 0x06, 0x60,
+ 0x33, 0x80, 0x03, 0x8C, 0x06, 0x30, 0x33, 0xFF, 0xFF, 0x8C, 0x0C, 0x30,
+ 0x33, 0xFF, 0xFF, 0x8C, 0x0C, 0x38, 0x33, 0xFF, 0xFF, 0x8C, 0x18, 0x18,
+ 0x33, 0xFF, 0xFF, 0x8C, 0x18, 0x0C, 0x33, 0xFF, 0xFF, 0x8C, 0x30, 0x0E,
+ 0x33, 0xF0, 0x1F, 0x8C, 0x70, 0x06, 0x33, 0x80, 0x03, 0x8C, 0x60, 0x03,
+ 0x33, 0x80, 0x03, 0x8C, 0xC0, 0x01, 0xB3, 0x80, 0x03, 0x8D, 0x80, 0x07,
+ 0xF3, 0x80, 0x03, 0x8F, 0xC0, 0x0E, 0x03, 0x80, 0x03, 0x80, 0xF0, 0x0C,
+ 0x01, 0xE0, 0x0F, 0x00, 0x30, 0x07, 0x00, 0x78, 0x1C, 0x00, 0xE0, 0x03,
+ 0x00, 0x18, 0x18, 0x00, 0xC0, 0x03, 0x80, 0x3F, 0xFC, 0x03, 0xC0, 0x01,
+ 0xFF, 0xF7, 0xEF, 0xFF, 0x80, 0x00, 0x7F, 0xC0, 0x03, 0xFE, 0x00, 0x00,
+ 0x00, 0x1D, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0x00,
+ 0x03, 0x33, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xCC, 0x00, 0x00, 0x00, 0x00,
+ 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x06,
+ 0x33, 0x00, 0x00, 0x00, 0x00, 0x18, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x63,
+ 0x18, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x07, 0x7B,
+ 0x80, 0x00, 0x00, 0x00, 0x18, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x43, 0x18,
+ 0x00, 0x00, 0x00, 0x03, 0x0C, 0x60, 0x00, 0x00, 0x00, 0x0C, 0x30, 0x80,
+ 0x00, 0x00, 0x00, 0x30, 0xC3, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0C, 0x00,
+ 0x00, 0x00, 0x03, 0x0C, 0x30, 0x00, 0x00, 0x00, 0x0C, 0x30, 0xC0, 0x00,
+ 0x00, 0x00, 0x60, 0xC3, 0x00, 0x00, 0x00, 0x01, 0x83, 0x06, 0x00, 0x00,
+ 0x00, 0x06, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x30, 0x30, 0x60, 0x00, 0x00,
+ 0x00, 0xC0, 0xC0, 0x80, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00,
+ 0x0C, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x00, 0x00, 0x01,
+ 0x80, 0xC0, 0xC0, 0x00, 0x00, 0x36, 0x03, 0x01, 0xA0, 0x00, 0x03, 0xF0,
+ 0x0C, 0x07, 0xF0, 0x00, 0x3F, 0x80, 0x30, 0x07, 0xF0, 0x03, 0xCC, 0x00,
+ 0xC0, 0x18, 0xF0, 0x7C, 0x18, 0x03, 0x00, 0x60, 0xF3, 0xC0, 0x60, 0x0C,
+ 0x01, 0x80, 0xE0, 0x01, 0xC0, 0x30, 0x0C, 0x00, 0x80, 0x03, 0x01, 0xE0,
+ 0x70, 0x00, 0x00, 0x06, 0x1F, 0xC1, 0x80, 0x00, 0x00, 0x1C, 0x63, 0x8C,
+ 0x00, 0x00, 0x00, 0x3B, 0x03, 0x70, 0x00, 0x00, 0x00, 0x7C, 0x0F, 0x80,
+ 0x00, 0x00, 0x00, 0xF0, 0x7C, 0x00, 0x00, 0x00, 0x01, 0xE1, 0xE0, 0x00,
+ 0x00, 0x00, 0x03, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00,
+ 0x00, 0x00, 0xE0, 0x38, 0x00, 0x00, 0x00, 0x07, 0x00, 0x38, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+ 0x07, 0xE0, 0x07, 0xE0, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xF8, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00,
+ 0xE0, 0x00, 0x00, 0x07, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0xC0, 0x06, 0x03, 0x00, 0x00, 0x00, 0x60, 0x0E,
+ 0x0F, 0x00, 0x00, 0x00, 0x70, 0x1C, 0x1C, 0x00, 0x00, 0x00, 0x30, 0x18,
+ 0x30, 0x00, 0x00, 0x00, 0x18, 0x38, 0x60, 0x00, 0x00, 0x00, 0x1C, 0x30,
+ 0x40, 0x00, 0x00, 0x18, 0x0C, 0x70, 0x03, 0xC0, 0x00, 0x78, 0x0C, 0x60,
+ 0x03, 0xC0, 0x00, 0xE0, 0x06, 0x60, 0x07, 0xE0, 0x03, 0xC0, 0x06, 0x60,
+ 0x07, 0xE0, 0x07, 0x00, 0x06, 0xC0, 0x07, 0xE0, 0x0F, 0xC0, 0x03, 0xC0,
+ 0x03, 0xC0, 0x07, 0xF8, 0x03, 0xC0, 0x03, 0xC0, 0x00, 0x38, 0x03, 0xC0,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0x03, 0xC0,
+ 0x00, 0x00, 0x00, 0x1F, 0x83, 0xC0, 0x00, 0x00, 0xE0, 0x3F, 0xC3, 0xC0,
+ 0x00, 0x00, 0xF0, 0x3F, 0xC0, 0x60, 0x00, 0x00, 0x38, 0x3F, 0xFC, 0x60,
+ 0x00, 0x00, 0x18, 0x3F, 0xFE, 0x60, 0x00, 0x00, 0x38, 0x3F, 0xFF, 0x70,
+ 0x00, 0x00, 0x70, 0x3F, 0xFF, 0x30, 0x00, 0x00, 0x70, 0x1F, 0xFF, 0x38,
+ 0x00, 0x00, 0x18, 0x1F, 0xFF, 0x18, 0x00, 0x00, 0x18, 0x1F, 0xFE, 0x1C,
+ 0x00, 0x00, 0x38, 0x3F, 0xFC, 0x0E, 0x00, 0x00, 0xF0, 0x3F, 0xF8, 0x07,
+ 0x00, 0x00, 0xE0, 0x0F, 0xE0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x07, 0x80, 0x00,
+ 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x1F, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x07, 0xE0, 0x07, 0xE0, 0x00, 0x00,
+ 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x01, 0x80, 0x00, 0x00,
+ 0x07, 0xE0, 0x06, 0x00, 0x00, 0xC0, 0x3C, 0x00, 0x00, 0x38, 0x03, 0x01,
+ 0xC0, 0x18, 0x00, 0x60, 0x1C, 0x06, 0x00, 0x60, 0x00, 0xC0, 0x00, 0x38,
+ 0x00, 0xC0, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x0E,
+ 0x00, 0x00, 0x19, 0x80, 0x0C, 0xFE, 0x00, 0x00, 0x67, 0x00, 0x37, 0x18,
+ 0x00, 0x01, 0x84, 0x00, 0xF8, 0x30, 0xF0, 0x06, 0x00, 0x01, 0xC0, 0xC7,
+ 0xF0, 0x18, 0x00, 0x03, 0x02, 0x38, 0xF0, 0xE0, 0x1C, 0x0F, 0xF8, 0xC0,
+ 0xF7, 0x00, 0x78, 0x3F, 0xC3, 0x00, 0xF8, 0x00, 0x40, 0x40, 0x0C, 0x00,
+ 0x00, 0x00, 0x1F, 0x80, 0x18, 0x00, 0x00, 0x01, 0xFF, 0x80, 0x60, 0x60,
+ 0x00, 0x0F, 0x1F, 0x80, 0xC1, 0x80, 0x00, 0x30, 0x67, 0x03, 0x06, 0x00,
+ 0x01, 0xC1, 0x8E, 0x38, 0x00, 0x00, 0x06, 0x06, 0x0F, 0xE0, 0x00, 0x00,
+ 0x18, 0x18, 0x3E, 0x00, 0x07, 0x00, 0xE0, 0xE3, 0xF0, 0x00, 0x3C, 0x03,
+ 0x83, 0x0C, 0xC1, 0x81, 0xC0, 0x1E, 0x18, 0x71, 0x87, 0x0C, 0x00, 0x78,
+ 0x61, 0x86, 0x08, 0x30, 0x01, 0xF0, 0x06, 0x18, 0x00, 0x80, 0x0C, 0xC0,
+ 0x00, 0x30, 0x06, 0x00, 0x33, 0x00, 0x00, 0xC0, 0x18, 0x01, 0xC6, 0x00,
+ 0x1F, 0xC0, 0x60, 0x07, 0x1C, 0x0F, 0xEF, 0x81, 0x00, 0x1C, 0x38, 0x3E,
+ 0x37, 0x8C, 0x00, 0xD8, 0x70, 0x00, 0xCF, 0xE0, 0x03, 0x30, 0xE0, 0x06,
+ 0x0F, 0x00, 0x18, 0xE1, 0xF0, 0x38, 0x00, 0x00, 0x61, 0xC1, 0xFF, 0xC0,
+ 0x00, 0x73, 0xC3, 0x81, 0xFC, 0x00, 0x01, 0xCF, 0x07, 0x87, 0xC0, 0x00,
+ 0x00, 0x36, 0x07, 0xFC, 0x00, 0x20, 0x01, 0x9C, 0x0F, 0x80, 0x00, 0xC0,
+ 0x06, 0x3C, 0xF8, 0x00, 0x03, 0x00, 0x30, 0x3F, 0x00, 0x00, 0x04, 0x0C,
+ 0xC1, 0xF0, 0x00, 0x00, 0x00, 0x3B, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x6F,
+ 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xE0, 0x07, 0x80, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x38, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x0C, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x00, 0x01,
+ 0x80, 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x06,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0C,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x30,
+ 0x03, 0xC0, 0x03, 0xC0, 0x0C, 0x60, 0x03, 0xC0, 0x03, 0xC0, 0x06, 0x60,
+ 0x07, 0xE0, 0x07, 0xE0, 0x06, 0x60, 0x07, 0xE0, 0x07, 0xE0, 0x06, 0xC0,
+ 0x07, 0xE0, 0x07, 0xE0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
+ 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0,
+ 0x60, 0x00, 0x00, 0x06, 0x03, 0xC0, 0xDF, 0x80, 0x01, 0xFF, 0x03, 0x60,
+ 0xC0, 0x7F, 0xFF, 0x83, 0x06, 0x60, 0xE0, 0x00, 0x00, 0x07, 0x06, 0x60,
+ 0xFE, 0x00, 0x00, 0x7F, 0x06, 0x30, 0x7F, 0xFF, 0xFF, 0xFE, 0x0C, 0x30,
+ 0x3F, 0xFF, 0xFF, 0xFC, 0x0C, 0x38, 0x1F, 0xFF, 0xFF, 0xF8, 0x18, 0x18,
+ 0x0F, 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0xF8, 0x1F, 0xE0, 0x30, 0x0C,
+ 0x01, 0xE0, 0x07, 0x80, 0x30, 0x06, 0x00, 0x70, 0x0E, 0x00, 0x60, 0x03,
+ 0x00, 0x0F, 0xF0, 0x00, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00,
+ 0xC0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00,
+ 0x1C, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x00, 0x00, 0xE0, 0x00, 0x00,
+ 0x01, 0xE0, 0x07, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00,
+ 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+ 0x07, 0xE0, 0x07, 0xE0, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xF8, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00,
+ 0xE0, 0x00, 0x00, 0x07, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0xC0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x0E,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x1C, 0x03, 0x00, 0x01, 0x80, 0x30, 0x18,
+ 0x07, 0x00, 0x00, 0xE0, 0x18, 0x38, 0x3C, 0x00, 0x00, 0x7C, 0x1C, 0x30,
+ 0xF8, 0x00, 0x00, 0x1F, 0x0C, 0x70, 0x40, 0x00, 0x00, 0x02, 0x0C, 0x60,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60,
+ 0x0F, 0xC0, 0x03, 0xF0, 0x06, 0xC0, 0x11, 0xE0, 0x06, 0x38, 0x03, 0xC0,
+ 0x60, 0xF8, 0x18, 0x1E, 0x03, 0xC0, 0x40, 0xF8, 0x10, 0x1E, 0x03, 0xC0,
+ 0xC0, 0xFC, 0x30, 0x1F, 0x03, 0xC0, 0xC1, 0xFC, 0x30, 0x3F, 0x03, 0xC0,
+ 0xE3, 0xFC, 0x38, 0xFF, 0x03, 0xC0, 0xFF, 0x3C, 0x3F, 0xCF, 0x03, 0xC0,
+ 0xFF, 0x3C, 0x3F, 0xCF, 0x03, 0xC0, 0x7F, 0xF8, 0x1F, 0xFE, 0x03, 0xC0,
+ 0x7F, 0xF8, 0x1F, 0xFE, 0x03, 0x60, 0x3F, 0xF0, 0x0F, 0xFC, 0x06, 0x60,
+ 0x0F, 0xC0, 0x03, 0xF0, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x38,
+ 0x00, 0x00, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x1C,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0x0E, 0x00, 0x07, 0xE0, 0x00, 0x70, 0x06,
+ 0x00, 0x1F, 0xF0, 0x00, 0x60, 0x03, 0x00, 0x08, 0x10, 0x00, 0xC0, 0x03,
+ 0x80, 0x00, 0x00, 0x01, 0x80, 0x01, 0xE0, 0x00, 0x00, 0x07, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x1F, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x07, 0xE0, 0x07, 0xE0, 0x00, 0x00,
+ 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00,
+ 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00,
+ 0x00, 0x1F, 0x80, 0x1F, 0x00, 0x60, 0x00, 0x0F, 0x80, 0x00, 0x78, 0x0E,
+ 0x00, 0x03, 0xC0, 0x00, 0x03, 0xC3, 0xC0, 0x00, 0xE0, 0x00, 0x00, 0x1C,
+ 0xCC, 0x00, 0x38, 0x00, 0x00, 0x01, 0xF8, 0xC0, 0x1C, 0x00, 0x00, 0x00,
+ 0x1E, 0x1C, 0x03, 0x00, 0x00, 0x00, 0x01, 0x81, 0x80, 0xC0, 0x00, 0x00,
+ 0x00, 0x70, 0x38, 0x38, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x0E, 0x00, 0x00,
+ 0x00, 0x03, 0x80, 0x71, 0x80, 0x00, 0x00, 0x00, 0x60, 0x06, 0x70, 0x00,
+ 0x00, 0x00, 0x0C, 0x00, 0xCC, 0x00, 0x00, 0x00, 0x01, 0x80, 0x1B, 0x80,
+ 0x00, 0x00, 0x00, 0x30, 0x03, 0x60, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6C,
+ 0x00, 0x00, 0x00, 0x20, 0x60, 0x19, 0x80, 0x1F, 0xC0, 0x3F, 0x86, 0x06,
+ 0x60, 0x06, 0x1C, 0x0E, 0x38, 0x3F, 0x8C, 0x00, 0x81, 0x81, 0x01, 0x00,
+ 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xC0, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0C, 0x60, 0x30, 0x00, 0x00, 0x07, 0x01, 0x8C, 0x0D, 0xF8,
+ 0x00, 0x1F, 0xE0, 0x70, 0xC1, 0x80, 0xFF, 0xFE, 0x06, 0x0C, 0x18, 0x38,
+ 0x00, 0x00, 0x01, 0x81, 0x83, 0x07, 0xF0, 0x00, 0x03, 0xF0, 0x30, 0x70,
+ 0x7F, 0xFF, 0xFF, 0xFC, 0x0C, 0x06, 0x07, 0xFF, 0xFF, 0xFF, 0x81, 0x80,
+ 0xE0, 0x7F, 0xFF, 0xFF, 0xE0, 0x70, 0x0C, 0x07, 0xFF, 0xFF, 0xF8, 0x0C,
+ 0x01, 0xC0, 0x7F, 0x81, 0xFE, 0x03, 0x00, 0x1C, 0x07, 0xC0, 0x0F, 0x00,
+ 0xE0, 0x01, 0x80, 0x1C, 0x03, 0x80, 0x38, 0x00, 0x18, 0x00, 0xFF, 0x80,
+ 0x0E, 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0x80, 0x00, 0x1C, 0x00, 0x00,
+ 0x00, 0xE0, 0x00, 0x01, 0xC0, 0x00, 0x00, 0x38, 0x00, 0x00, 0x1E, 0x00,
+ 0x00, 0x1E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x0F,
+ 0xC0, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00,
+ 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x00, 0x0F,
+ 0xE0, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x00, 0x01, 0xFE, 0x00, 0x00,
+ 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x00, 0x03, 0xFF,
+ 0x00, 0x00, 0x00, 0x1F, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00,
+ 0x07, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xE0,
+ 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x08, 0x1F,
+ 0xFF, 0xC0, 0x00, 0xC1, 0xFF, 0xBE, 0x00, 0x0C, 0x1F, 0xF3, 0xF0, 0x00,
+ 0xE0, 0xFF, 0x1F, 0x80, 0x0F, 0x0F, 0xF1, 0xF8, 0x00, 0x78, 0x7F, 0x0F,
+ 0xC0, 0x07, 0xE7, 0xF0, 0x7E, 0x00, 0x3F, 0x3F, 0x03, 0xF0, 0x01, 0xF9,
+ 0xF0, 0x1F, 0x81, 0x1F, 0xEF, 0x80, 0xFE, 0x08, 0xFF, 0xF8, 0x07, 0xFD,
+ 0xE7, 0xFF, 0xC0, 0x3F, 0xFF, 0xBF, 0xFE, 0x00, 0xFF, 0xFD, 0xFF, 0xF0,
+ 0x07, 0xFF, 0xEF, 0xFF, 0x80, 0x1F, 0xFF, 0x7F, 0xFC, 0x00, 0x7F, 0xFF,
+ 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0xFB, 0x80, 0x07, 0xFF, 0xFF, 0xCE, 0x00,
+ 0x1F, 0xFB, 0xFC, 0x20, 0x00, 0xFF, 0x9F, 0xE0, 0x00, 0x03, 0xFC, 0xFF,
+ 0x00, 0x00, 0x1F, 0xE3, 0xF8, 0x00, 0x00, 0xFF, 0x1F, 0xC0, 0x00, 0x07,
+ 0xF0, 0x7E, 0x00, 0x00, 0x3F, 0x83, 0xF0, 0x00, 0x01, 0xF8, 0x0F, 0xC0,
+ 0x00, 0x1F, 0x80, 0x3E, 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x0F, 0x80,
+ 0x03, 0xF0, 0x01, 0xF8, 0x00, 0x07, 0xE0, 0x3F, 0x00, 0x00, 0x0F, 0xFF,
+ 0xE0, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF8, 0x00,
+ 0x00, 0x00, 0x00, 0xFC, 0x1F, 0xC0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x38,
+ 0x00, 0x00, 0x00, 0x70, 0x00, 0x07, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x06, 0x00, 0x00,
+ 0x01, 0x80, 0x00, 0x03, 0x00, 0x10, 0x00, 0x30, 0x00, 0x01, 0x80, 0x3F,
+ 0x80, 0x0E, 0x00, 0x00, 0x60, 0x1C, 0x70, 0x01, 0x80, 0x00, 0x30, 0x0E,
+ 0x06, 0x00, 0x30, 0x00, 0x18, 0x03, 0x00, 0xC0, 0x0E, 0x00, 0x06, 0x00,
+ 0x80, 0x37, 0x81, 0x80, 0x03, 0x00, 0x60, 0x07, 0x60, 0x30, 0x00, 0xC1,
+ 0xF8, 0x01, 0x98, 0x0C, 0x00, 0x60, 0x1E, 0x00, 0x46, 0x01, 0x80, 0x18,
+ 0x00, 0x80, 0x01, 0x80, 0x30, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x0E, 0x03,
+ 0x00, 0x0C, 0x00, 0x30, 0x01, 0x81, 0x80, 0x01, 0x80, 0x0C, 0x00, 0x30,
+ 0x60, 0x00, 0x70, 0x03, 0x00, 0x06, 0x18, 0x00, 0x0E, 0x00, 0x60, 0x01,
+ 0x8C, 0x00, 0x0F, 0xC0, 0x18, 0x00, 0x33, 0x00, 0x0F, 0xF0, 0x03, 0x00,
+ 0x0C, 0xC0, 0x01, 0x06, 0x00, 0x60, 0x01, 0xB0, 0x00, 0x01, 0x80, 0x0C,
+ 0x00, 0x6C, 0x00, 0x00, 0x20, 0x01, 0xC0, 0x0B, 0x00, 0x00, 0x0C, 0x00,
+ 0x38, 0x03, 0xC0, 0x00, 0x01, 0x80, 0x06, 0x00, 0xF0, 0x00, 0x00, 0x60,
+ 0x00, 0x00, 0x3C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0D, 0x80, 0x00, 0x03,
+ 0x80, 0x00, 0x03, 0x60, 0x00, 0x00, 0x70, 0x00, 0x00, 0x9C, 0x00, 0x00,
+ 0x0E, 0x00, 0x00, 0x63, 0x80, 0x00, 0x01, 0xC0, 0x00, 0x18, 0x70, 0x00,
+ 0x00, 0x38, 0x00, 0x0C, 0x0F, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0xF8,
+ 0x00, 0x00, 0x70, 0x03, 0x80, 0x0F, 0xFF, 0x7F, 0xFF, 0xFF, 0xC0, 0x00,
+ 0x7F, 0xFF, 0xF0, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xC0, 0x00, 0x00,
+ 0x00, 0x00, 0x0F, 0xC3, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x38,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0xE0,
+ 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0xC0, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xC0,
+ 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x70, 0x0C, 0x00, 0x00,
+ 0x00, 0x60, 0x1C, 0xFC, 0x0C, 0x00, 0x00, 0x00, 0x30, 0x3E, 0xC6, 0x18,
+ 0x04, 0x00, 0x60, 0x18, 0x63, 0xC6, 0x38, 0x0E, 0x00, 0xF0, 0x18, 0xC3,
+ 0xC3, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x83, 0xC1, 0xF0, 0x0F, 0x00, 0xF0,
+ 0x0F, 0x83, 0x60, 0x30, 0x0E, 0x00, 0x60, 0x18, 0x06, 0x60, 0x18, 0x00,
+ 0x00, 0x00, 0x18, 0x0E, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x38,
+ 0x0C, 0x00, 0x38, 0x00, 0x60, 0x18, 0x1C, 0x06, 0x00, 0x3C, 0x00, 0x60,
+ 0x30, 0x0C, 0x06, 0x00, 0x7C, 0x00, 0xC0, 0x70, 0x06, 0x03, 0x00, 0x38,
+ 0x00, 0xC0, 0x60, 0x07, 0x03, 0x00, 0x00, 0x01, 0x80, 0xE0, 0x07, 0x01,
+ 0x80, 0x00, 0x01, 0x81, 0xE0, 0x0D, 0x81, 0x80, 0x00, 0x01, 0x81, 0xA0,
+ 0x0D, 0x81, 0x80, 0x00, 0x03, 0x03, 0x30, 0x08, 0xC0, 0xC0, 0x00, 0x03,
+ 0x03, 0x30, 0x18, 0xC0, 0xC0, 0x00, 0x03, 0x03, 0x10, 0x18, 0xC0, 0xC0,
+ 0x00, 0x03, 0x02, 0x10, 0x18, 0xC0, 0xC0, 0x00, 0x02, 0x02, 0x18, 0x18,
+ 0x00, 0xC0, 0x00, 0x02, 0x00, 0x18, 0x18, 0x00, 0xC0, 0x00, 0x06, 0x00,
+ 0x18, 0x18, 0x00, 0xC0, 0x00, 0x02, 0x00, 0x18, 0x18, 0x00, 0xC0, 0x00,
+ 0x02, 0x00, 0x18, 0x18, 0x00, 0xC0, 0x00, 0x03, 0x00, 0x10, 0x08, 0x00,
+ 0xC0, 0x00, 0x03, 0x00, 0x30, 0x0C, 0x01, 0xFF, 0xFF, 0xFF, 0x00, 0x70,
+ 0x06, 0x03, 0xFF, 0xFF, 0xFF, 0x80, 0xE0, 0x03, 0xFF, 0x00, 0x00, 0x00,
+ 0xFF, 0xC0, 0x01, 0xFE, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x00, 0x1F,
+ 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, 0xE0,
+ 0x07, 0xE0, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x3C, 0x00,
+ 0x00, 0x3C, 0x00, 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00, 0xE0, 0x00,
+ 0x00, 0x07, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0xC0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x0E, 0x00, 0x00,
+ 0x00, 0x00, 0x70, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00,
+ 0x00, 0x00, 0x18, 0x38, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x30, 0x00, 0x00,
+ 0x00, 0x00, 0x0C, 0x70, 0x0F, 0xC0, 0x03, 0xF0, 0x0C, 0x60, 0x3F, 0xF0,
+ 0x0F, 0xFC, 0x06, 0x60, 0x6F, 0xD8, 0x1B, 0xF6, 0x06, 0x60, 0x6F, 0xD8,
+ 0x1B, 0xF6, 0x06, 0xC0, 0xC7, 0x8C, 0x31, 0xE3, 0x03, 0xC0, 0xC0, 0x0C,
+ 0x30, 0x03, 0x03, 0xC0, 0xC0, 0x0C, 0x30, 0x03, 0x03, 0xC0, 0xC0, 0x0C,
+ 0x30, 0x03, 0x03, 0xC0, 0xE0, 0x1C, 0x38, 0x07, 0x03, 0xC0, 0x60, 0x18,
+ 0x18, 0x06, 0x03, 0xC0, 0x38, 0x70, 0x0E, 0x1C, 0x03, 0xC0, 0x1F, 0xE0,
+ 0x07, 0xF8, 0x03, 0xC0, 0x0F, 0xC0, 0x03, 0xF0, 0x03, 0xC0, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x38, 0x00, 0x00,
+ 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x1C, 0x00, 0x00,
+ 0x00, 0x00, 0x30, 0x0E, 0x00, 0x3F, 0xFC, 0x00, 0x70, 0x06, 0x00, 0x3F,
+ 0xFC, 0x00, 0x60, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x80, 0x00,
+ 0x00, 0x01, 0x80, 0x01, 0xE0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x70, 0x00,
+ 0x00, 0x0E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x1F, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0x07, 0xE0, 0x07, 0xE0, 0x00, 0x00, 0x01, 0xFF,
+ 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00,
+ 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x0F, 0x80,
+ 0x00, 0x00, 0x03, 0xC0, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x07, 0x00, 0x00,
+ 0x00, 0xE0, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x38,
+ 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x0C, 0x00,
+ 0x00, 0x60, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x01, 0xC0, 0x00, 0x00, 0x78, 0x03, 0x80, 0x01, 0x80, 0x00,
+ 0x01, 0x8C, 0x01, 0x80, 0x03, 0x80, 0x00, 0x01, 0x06, 0x01, 0xC0, 0x03,
+ 0x00, 0x7C, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x00, 0xC7, 0x03, 0x00, 0x00,
+ 0xC0, 0x06, 0x01, 0x83, 0x00, 0x00, 0x00, 0x60, 0x06, 0x01, 0x80, 0x00,
+ 0x00, 0x00, 0x60, 0x06, 0x01, 0x80, 0x00, 0x00, 0x60, 0x60, 0x0C, 0x00,
+ 0x00, 0x00, 0x00, 0x60, 0x70, 0x0C, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x30,
+ 0x0C, 0x00, 0x00, 0x00, 0x01, 0x80, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x30, 0x0C, 0x02, 0x00,
+ 0x00, 0x1C, 0x00, 0x30, 0x0C, 0x03, 0xC0, 0x00, 0x70, 0x00, 0x30, 0x0C,
+ 0x00, 0xF8, 0x03, 0xC0, 0x00, 0x30, 0x0C, 0x00, 0x1F, 0xFF, 0x00, 0x00,
+ 0x30, 0x0D, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xB0, 0x1E, 0x60, 0x00, 0x00,
+ 0x00, 0x04, 0xF8, 0x3E, 0x31, 0xC0, 0x00, 0x03, 0x88, 0x7C, 0x22, 0x1A,
+ 0x60, 0x00, 0x06, 0x58, 0xC4, 0x23, 0x0E, 0x30, 0x00, 0x0C, 0x70, 0xC4,
+ 0x31, 0x8C, 0x30, 0x00, 0x0C, 0x61, 0x8C, 0x70, 0x84, 0x30, 0x00, 0x0C,
+ 0x63, 0x0E, 0xC8, 0xC2, 0x30, 0x00, 0x0C, 0x42, 0x1B, 0xC4, 0x60, 0x18,
+ 0x00, 0x18, 0x04, 0x23, 0xE6, 0x20, 0x18, 0x00, 0x18, 0x04, 0x47, 0x63,
+ 0x00, 0x18, 0x00, 0x18, 0x00, 0x86, 0x71, 0x80, 0x0C, 0x00, 0x30, 0x01,
+ 0x0E, 0xCC, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x3B, 0xC6, 0x00, 0x0C, 0x00,
+ 0x30, 0x00, 0x63, 0xC2, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x43, 0x60, 0x00,
+ 0x0C, 0x00, 0x30, 0x00, 0x06, 0x30, 0x00, 0x18, 0x00, 0x18, 0x00, 0x0C,
+ 0x1C, 0x00, 0x18, 0x00, 0x18, 0x00, 0x38, 0x07, 0x00, 0x3F, 0xC3, 0xFC,
+ 0x00, 0xE0, 0x01, 0xC0, 0xE7, 0xFF, 0xE7, 0x03, 0x80, 0x00, 0x3F, 0x80,
+ 0x00, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x07, 0xE0, 0x00, 0x00, 0x1F,
+ 0x00, 0x00, 0xF8, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x0E, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x07, 0x00, 0x01, 0x80,
+ 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x60, 0x0E, 0x03, 0x80, 0x00, 0x00, 0x70, 0x1C, 0x0F,
+ 0x80, 0x00, 0x00, 0x30, 0x18, 0x1C, 0x00, 0x00, 0x00, 0x18, 0x38, 0x38,
+ 0x00, 0x00, 0x00, 0x1C, 0x30, 0x70, 0x00, 0x00, 0x00, 0x0C, 0x70, 0x60,
+ 0x00, 0x00, 0x00, 0x0C, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00,
+ 0x00, 0x00, 0x18, 0x06, 0x60, 0x03, 0xC0, 0x00, 0x78, 0x06, 0xC0, 0x03,
+ 0xC0, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0xE0, 0x03, 0xC0, 0x03, 0xC0, 0x07,
+ 0xE0, 0x07, 0x00, 0x03, 0xC0, 0x07, 0xE0, 0x0F, 0xC0, 0x03, 0xC0, 0x03,
+ 0xC0, 0x07, 0xF8, 0x03, 0xC0, 0x03, 0x80, 0x00, 0x38, 0x03, 0xC0, 0x00,
+ 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00,
+ 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x60, 0x00,
+ 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x03,
+ 0x00, 0x00, 0x40, 0x06, 0x70, 0x03, 0xC0, 0x01, 0xC0, 0x0C, 0x30, 0x01,
+ 0xF0, 0x0F, 0x80, 0x0C, 0x38, 0x00, 0x7F, 0xFE, 0x00, 0x1C, 0x18, 0x00,
+ 0x0F, 0xF0, 0x00, 0x18, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0E, 0x00,
+ 0x00, 0x00, 0x00, 0x70, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0xC0, 0x03, 0x80, 0x00, 0x00, 0x01, 0x80, 0x01, 0xE0,
+ 0x00, 0x00, 0x07, 0x00, 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x3C,
+ 0x00, 0x00, 0x3C, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x07,
+ 0xE0, 0x07, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
+ 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xEF, 0xF8, 0x00, 0x00, 0x00,
+ 0xFB, 0xC0, 0x1F, 0x00, 0x00, 0x00, 0xF0, 0x70, 0x03, 0xC0, 0x00, 0x01,
+ 0xE0, 0x08, 0x00, 0x78, 0x00, 0x01, 0xC0, 0x00, 0x3F, 0x8E, 0x00, 0x01,
+ 0xC1, 0xE0, 0x1F, 0xF3, 0x80, 0x01, 0xC0, 0xF0, 0x00, 0x3C, 0xE0, 0x01,
+ 0xC0, 0xFC, 0x00, 0x00, 0x38, 0x01, 0xC0, 0x7E, 0x00, 0x00, 0x0E, 0x00,
+ 0xC0, 0x3F, 0x00, 0x78, 0x03, 0x00, 0xE0, 0x1F, 0x00, 0x3C, 0x01, 0xC0,
+ 0x60, 0x07, 0x80, 0x3F, 0x00, 0x60, 0x60, 0x00, 0x00, 0x1F, 0x80, 0x18,
+ 0x30, 0x00, 0x00, 0x0F, 0xC0, 0x0C, 0x38, 0x00, 0x00, 0x03, 0xC0, 0x03,
+ 0x18, 0x00, 0x00, 0x01, 0xE0, 0x01, 0x8C, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xCE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x80, 0x00, 0x7F, 0x00, 0x00,
+ 0x06, 0xC0, 0x01, 0xFF, 0xE0, 0x00, 0x03, 0x60, 0x00, 0xE0, 0x3C, 0x00,
+ 0x01, 0xB0, 0x00, 0x00, 0x07, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x01, 0x80,
+ 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0xC0, 0x00, 0x00, 0x00,
+ 0x00, 0x1E, 0x30, 0x01, 0xFC, 0x00, 0x00, 0x1B, 0x0C, 0x07, 0x03, 0x00,
+ 0x00, 0x0D, 0x86, 0x0C, 0x01, 0x80, 0x00, 0x06, 0x61, 0x98, 0x03, 0xC0,
+ 0x00, 0x03, 0x30, 0x70, 0x0F, 0xC0, 0x00, 0x03, 0x08, 0x00, 0x1F, 0x80,
+ 0x00, 0x01, 0x86, 0x00, 0x1E, 0x00, 0x00, 0x01, 0x83, 0x00, 0x3E, 0x00,
+ 0x00, 0x01, 0xC1, 0x80, 0x1B, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x08, 0x80,
+ 0x00, 0x00, 0xC0, 0x60, 0x00, 0x40, 0x00, 0x00, 0xE0, 0x30, 0x00, 0xF0,
+ 0x00, 0x00, 0xE0, 0x18, 0x00, 0xD8, 0x00, 0x00, 0xE0, 0x0C, 0x00, 0x0C,
+ 0x00, 0x00, 0xE0, 0x06, 0x00, 0x0C, 0x00, 0x01, 0xE0, 0x01, 0x00, 0x0F,
+ 0x00, 0x03, 0xC0, 0x00, 0xC0, 0x01, 0x80, 0x07, 0x80, 0x00, 0x30, 0x01,
+ 0xFE, 0xFF, 0x00, 0x00, 0x0E, 0x03, 0xBF, 0xFC, 0x00, 0x00, 0x01, 0xFE,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x00, 0x00, 0x00,
+ 0x03, 0x9C, 0xC8, 0x00, 0x00, 0x00, 0x0E, 0x19, 0x8E, 0x00, 0x00, 0x00,
+ 0x78, 0x33, 0x9F, 0xC0, 0x00, 0x03, 0xC0, 0x67, 0x3D, 0xF0, 0x00, 0x0E,
+ 0x01, 0xCC, 0x6C, 0x3C, 0x00, 0x18, 0x07, 0x38, 0xC8, 0x0E, 0x00, 0x30,
+ 0x04, 0x63, 0x10, 0x03, 0x80, 0x60, 0x00, 0xC6, 0x60, 0x01, 0xC0, 0x60,
+ 0x00, 0x18, 0xC0, 0x00, 0xE0, 0xC0, 0x00, 0x13, 0x00, 0x00, 0x60, 0xC0,
+ 0x00, 0x0F, 0x00, 0x00, 0x30, 0xC0, 0x00, 0x1B, 0x00, 0x00, 0x38, 0xC0,
+ 0x3F, 0x63, 0x00, 0x00, 0x18, 0xC0, 0x60, 0x8E, 0x00, 0x00, 0x0C, 0xC0,
+ 0x00, 0x1C, 0x00, 0x00, 0x0C, 0x60, 0x00, 0x78, 0x00, 0x00, 0x06, 0x70,
+ 0x00, 0xE0, 0x01, 0xC0, 0x06, 0x38, 0x03, 0xE0, 0x03, 0xE0, 0x06, 0x1C,
+ 0x1F, 0xF0, 0x03, 0xF0, 0x07, 0x1F, 0xFB, 0xF0, 0x03, 0xF0, 0x03, 0x30,
+ 0x83, 0xF0, 0x03, 0xF0, 0x03, 0x30, 0x01, 0xE0, 0x03, 0xE0, 0x03, 0x30,
+ 0x01, 0xE0, 0x01, 0xC0, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x00, 0x03, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x18, 0x00, 0x00, 0x00, 0x00, 0x06, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x0C, 0x00, 0xE0, 0x01, 0x80, 0x0E, 0x0C,
+ 0x00, 0xFF, 0xFF, 0xC0, 0x0C, 0x0C, 0x00, 0x1F, 0xFE, 0x00, 0x1C, 0x06,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0x38, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x70, 0x01, 0x80, 0x00, 0x00, 0x00, 0x60, 0x00,
+ 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x60, 0x00, 0x00, 0x03, 0x80, 0x00,
+ 0x38, 0x00, 0x00, 0x07, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x1E, 0x00, 0x00,
+ 0x0F, 0x80, 0x00, 0x7C, 0x00, 0x00, 0x03, 0xF0, 0x03, 0xF0, 0x00, 0x00,
+ 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0x00, 0x1F, 0xC0, 0x00, 0x00, 0x06, 0x38, 0x00, 0x00,
+ 0x03, 0x07, 0x00, 0x00, 0x00, 0xC0, 0xF0, 0x00, 0x00, 0x18, 0x0E, 0x00,
+ 0x00, 0x07, 0x01, 0xC0, 0x00, 0x03, 0xE0, 0x3C, 0x00, 0x01, 0xDC, 0x03,
+ 0x80, 0x00, 0x61, 0xC0, 0x70, 0x00, 0x18, 0x38, 0x06, 0x00, 0x06, 0x07,
+ 0x00, 0xC0, 0x00, 0xC0, 0xF0, 0x30, 0x00, 0x38, 0x0C, 0x06, 0x00, 0x07,
+ 0x01, 0x81, 0xC0, 0x00, 0xE0, 0x60, 0x30, 0x00, 0x1F, 0xEC, 0x06, 0x00,
+ 0x3F, 0x7F, 0x01, 0x80, 0x1C, 0x01, 0xF8, 0x30, 0x1E, 0x00, 0x0F, 0x0C,
+ 0x0E, 0x00, 0x00, 0xE3, 0x0E, 0x00, 0x00, 0x18, 0x63, 0x00, 0x7C, 0x03,
+ 0x19, 0x80, 0x7F, 0xC0, 0xC6, 0x60, 0x78, 0x38, 0x19, 0x98, 0x38, 0x06,
+ 0x06, 0x23, 0xFC, 0x00, 0xC0, 0xCC, 0x7C, 0x00, 0x30, 0x03, 0x3C, 0x00,
+ 0x0C, 0x00, 0xDF, 0x80, 0x03, 0x00, 0x3E, 0x30, 0x00, 0xC0, 0x0F, 0x0E,
+ 0x00, 0x30, 0x03, 0xC1, 0x80, 0x0C, 0x00, 0xB0, 0x30, 0x06, 0x00, 0x6C,
+ 0x0E, 0x03, 0x80, 0x1B, 0x01, 0xC1, 0xC0, 0x06, 0x60, 0x3F, 0xE0, 0x03,
+ 0x18, 0x03, 0xE0, 0x00, 0xC7, 0x00, 0x00, 0x00, 0x30, 0xC0, 0x00, 0x00,
+ 0x18, 0x18, 0x00, 0x00, 0x06, 0x07, 0x00, 0x00, 0x03, 0x00, 0xE0, 0x00,
+ 0x01, 0xC0, 0x1C, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x00, 0xF0, 0x00, 0x3E,
+ 0x01, 0xF0, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0xF0, 0x7F,
+ 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0,
+ 0x7F, 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0x80, 0x00, 0x07, 0x81, 0xE0,
+ 0x00, 0x07, 0x81, 0xE0, 0x00, 0x07, 0x01, 0xE0, 0x00, 0x0F, 0x01, 0xC0,
+ 0x00, 0x0F, 0x03, 0xC0, 0x00, 0x0F, 0x03, 0xC0, 0x00, 0x0E, 0x03, 0xC0,
+ 0x00, 0x1E, 0x03, 0x80, 0x00, 0x1E, 0x03, 0x80, 0x00, 0x1E, 0x07, 0x80,
+ 0x1F, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF,
+ 0x1F, 0xFF, 0xFF, 0xFF, 0x00, 0x3C, 0x0F, 0x00, 0x00, 0x38, 0x0F, 0x00,
+ 0x00, 0x78, 0x0E, 0x00, 0x00, 0x78, 0x1E, 0x00, 0x00, 0x70, 0x1E, 0x00,
+ 0x00, 0xF0, 0x1E, 0x00, 0x00, 0xF0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xF8,
+ 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xF8,
+ 0x01, 0xE0, 0x78, 0x00, 0x01, 0xC0, 0x78, 0x00, 0x01, 0xC0, 0x78, 0x00,
+ 0x03, 0xC0, 0x70, 0x00, 0x03, 0xC0, 0xF0, 0x00, 0x03, 0xC0, 0xF0, 0x00,
+ 0x03, 0x80, 0xF0, 0x00, 0x07, 0x80, 0xE0, 0x00, 0x07, 0x80, 0xE0, 0x00,
+ 0x07, 0x81, 0xE0, 0x00, 0x00, 0x30, 0x00, 0x00, 0xC0, 0x00, 0x03, 0x00,
+ 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00, 0x07, 0xFF, 0x00, 0xFF, 0xFF, 0x07,
+ 0xFF, 0xFC, 0x3F, 0xFF, 0xF1, 0xF8, 0xC1, 0xCF, 0x83, 0x00, 0x3C, 0x0C,
+ 0x00, 0xF0, 0x30, 0x03, 0xC0, 0xC0, 0x0F, 0x03, 0x00, 0x3E, 0x0C, 0x00,
+ 0x7E, 0x30, 0x01, 0xFF, 0xC0, 0x03, 0xFF, 0xE0, 0x03, 0xFF, 0xF0, 0x03,
+ 0xFF, 0xE0, 0x00, 0xFF, 0xC0, 0x03, 0x1F, 0x80, 0x0C, 0x1F, 0x00, 0x30,
+ 0x7C, 0x00, 0xC0, 0xF0, 0x03, 0x03, 0xC0, 0x0C, 0x0F, 0x00, 0x30, 0x7E,
+ 0x00, 0xC3, 0xEF, 0x83, 0x3F, 0xBF, 0xFF, 0xFC, 0xFF, 0xFF, 0xE1, 0xFF,
+ 0xFF, 0x00, 0x7F, 0xE0, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00, 0x00, 0xC0,
+ 0x00, 0x03, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00, 0x00, 0xC0, 0x00,
+ 0x07, 0xE0, 0x00, 0x0E, 0x00, 0x3F, 0xF0, 0x00, 0x3C, 0x00, 0xFF, 0xF0,
+ 0x00, 0x70, 0x03, 0xE1, 0xE0, 0x01, 0xE0, 0x07, 0x81, 0xE0, 0x03, 0x80,
+ 0x0F, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x03, 0xC0, 0x3C, 0x00, 0x78, 0x07,
+ 0x80, 0x70, 0x00, 0xF0, 0x0F, 0x01, 0xE0, 0x01, 0xE0, 0x1E, 0x03, 0x80,
+ 0x03, 0xC0, 0x3C, 0x0F, 0x00, 0x07, 0x80, 0x78, 0x1C, 0x00, 0x0F, 0x00,
+ 0xF0, 0x70, 0x00, 0x0F, 0x03, 0xC1, 0xE0, 0x00, 0x1E, 0x07, 0x83, 0x80,
+ 0x00, 0x3E, 0x1F, 0x0F, 0x00, 0x00, 0x3F, 0xFC, 0x1C, 0x00, 0x00, 0x3F,
+ 0xF0, 0x78, 0x1F, 0x80, 0x1F, 0x81, 0xE0, 0xFF, 0xC0, 0x00, 0x03, 0x83,
+ 0xFF, 0xC0, 0x00, 0x0F, 0x0F, 0x87, 0xC0, 0x00, 0x1C, 0x1E, 0x07, 0x80,
+ 0x00, 0x78, 0x3C, 0x0F, 0x00, 0x00, 0xE0, 0xF0, 0x0F, 0x00, 0x03, 0x81,
+ 0xE0, 0x1E, 0x00, 0x0F, 0x03, 0xC0, 0x3C, 0x00, 0x1C, 0x07, 0x80, 0x78,
+ 0x00, 0x78, 0x0F, 0x00, 0xF0, 0x00, 0xE0, 0x1E, 0x01, 0xE0, 0x03, 0xC0,
+ 0x3C, 0x03, 0xC0, 0x07, 0x00, 0x3C, 0x0F, 0x00, 0x1C, 0x00, 0x78, 0x1E,
+ 0x00, 0x78, 0x00, 0xF8, 0x78, 0x00, 0xE0, 0x00, 0xFF, 0xF0, 0x03, 0xC0,
+ 0x00, 0xFF, 0xC0, 0x07, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x3F, 0x80, 0x00,
+ 0x00, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xF8, 0x00,
+ 0x07, 0xE0, 0x78, 0x00, 0x0F, 0x80, 0x08, 0x00, 0x0F, 0x80, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00,
+ 0x07, 0xE0, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00,
+ 0x1F, 0xFC, 0x00, 0x00, 0x1E, 0x7E, 0x00, 0x3C, 0x3E, 0x3F, 0x00, 0x3C,
+ 0x7C, 0x1F, 0x80, 0x7C, 0x78, 0x0F, 0xC0, 0x78, 0xF8, 0x07, 0xE0, 0x78,
+ 0xF0, 0x03, 0xF0, 0x78, 0xF0, 0x01, 0xF8, 0xF0, 0xF0, 0x00, 0xFC, 0xF0,
+ 0xF0, 0x00, 0x7E, 0xE0, 0xF0, 0x00, 0x3F, 0xE0, 0xF8, 0x00, 0x1F, 0xC0,
+ 0x78, 0x00, 0x0F, 0xC0, 0x7C, 0x00, 0x0F, 0xC0, 0x3E, 0x00, 0x3F, 0xE0,
+ 0x3F, 0xC0, 0xFF, 0xF0, 0x1F, 0xFF, 0xFC, 0xF8, 0x0F, 0xFF, 0xF8, 0x7E,
+ 0x03, 0xFF, 0xE0, 0x3F, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF0, 0x03, 0xE0, 0x78, 0x1E, 0x03, 0xC0, 0xF0, 0x1E, 0x07,
+ 0x80, 0xF0, 0x3C, 0x07, 0x80, 0xF0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x07,
+ 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80,
+ 0xF0, 0x1E, 0x03, 0xC0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x01, 0xE0, 0x3C,
+ 0x07, 0x80, 0x78, 0x0F, 0x00, 0xF0, 0x1E, 0x01, 0xE0, 0x3C, 0x03, 0xC0,
+ 0x7C, 0xF8, 0x0F, 0x00, 0xF0, 0x1E, 0x01, 0xE0, 0x3C, 0x03, 0xC0, 0x78,
+ 0x07, 0x80, 0xF0, 0x1E, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x0F, 0x01,
+ 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0,
+ 0x3C, 0x07, 0x81, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x3C, 0x07, 0x80, 0xF0,
+ 0x3C, 0x07, 0x81, 0xE0, 0x3C, 0x0F, 0x01, 0xE0, 0x78, 0x1F, 0x00, 0x00,
+ 0x70, 0x00, 0x03, 0x80, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x04, 0x07, 0x01,
+ 0x78, 0x38, 0x3F, 0xE1, 0xC3, 0xE7, 0xCE, 0x7C, 0x0F, 0x77, 0x80, 0x1F,
+ 0xF0, 0x00, 0x7F, 0x00, 0x03, 0xF8, 0x00, 0x3F, 0xE0, 0x07, 0xBB, 0xC0,
+ 0xF9, 0xCF, 0x9F, 0x0E, 0x1F, 0xF0, 0x70, 0x7A, 0x03, 0x80, 0x80, 0x1C,
+ 0x00, 0x00, 0xE0, 0x00, 0x07, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x80,
+ 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x01, 0xE0, 0x00,
+ 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00,
+ 0x01, 0xE0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x78, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x07, 0x80, 0x03, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFC, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x01, 0xE0, 0x00,
+ 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00,
+ 0x01, 0xE0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x78, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E,
+ 0x00, 0x00, 0x3C, 0xF3, 0xCF, 0x3C, 0xE7, 0x9C, 0x73, 0xCE, 0x00, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x00, 0x1F,
+ 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x3E, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C,
+ 0x00, 0x78, 0x00, 0x78, 0x00, 0x78, 0x00, 0xF8, 0x00, 0xF0, 0x00, 0xF0,
+ 0x01, 0xF0, 0x01, 0xE0, 0x01, 0xE0, 0x01, 0xE0, 0x03, 0xC0, 0x03, 0xC0,
+ 0x03, 0xC0, 0x07, 0x80, 0x07, 0x80, 0x07, 0x80, 0x0F, 0x80, 0x0F, 0x00,
+ 0x0F, 0x00, 0x1F, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x3C, 0x00,
+ 0x3C, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0x78, 0x00, 0x78, 0x00, 0xF8, 0x00,
+ 0xF0, 0x00, 0x00, 0x7F, 0x00, 0x03, 0xFF, 0xC0, 0x07, 0xFF, 0xE0, 0x0F,
+ 0xFF, 0xF0, 0x1F, 0x81, 0xF8, 0x1F, 0x00, 0xF8, 0x3E, 0x00, 0x7C, 0x3C,
+ 0x00, 0x3C, 0x7C, 0x00, 0x3E, 0x78, 0x00, 0x1E, 0x78, 0x00, 0x1E, 0xF8,
+ 0x00, 0x1E, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0,
+ 0x00, 0x0F, 0xF8, 0x00, 0x1E, 0x78, 0x00, 0x1E, 0x78, 0x00, 0x1E, 0x7C,
+ 0x00, 0x3E, 0x3C, 0x00, 0x3C, 0x3E, 0x00, 0x7C, 0x1F, 0x00, 0xF8, 0x1F,
+ 0x81, 0xF8, 0x0F, 0xFF, 0xF0, 0x07, 0xFF, 0xE0, 0x03, 0xFF, 0xC0, 0x00,
+ 0xFE, 0x00, 0x03, 0xF0, 0x03, 0xFF, 0x00, 0xFF, 0xF0, 0x0F, 0xFF, 0x00,
+ 0xFC, 0xF0, 0x0C, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0,
+ 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F,
+ 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00,
+ 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00,
+ 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00,
+ 0x00, 0xF0, 0x00, 0x0F, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x07, 0xFC, 0x00, 0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x3F,
+ 0xFF, 0xFC, 0xFE, 0x03, 0xF3, 0x80, 0x03, 0xE8, 0x00, 0x07, 0x80, 0x00,
+ 0x1F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F,
+ 0x00, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x80, 0x00, 0x3C, 0x00,
+ 0x01, 0xF0, 0x00, 0x0F, 0x80, 0x00, 0x7C, 0x00, 0x03, 0xE0, 0x00, 0x1F,
+ 0x00, 0x00, 0xF8, 0x00, 0x07, 0xC0, 0x00, 0x3E, 0x00, 0x01, 0xF0, 0x00,
+ 0x0F, 0x80, 0x00, 0x7C, 0x00, 0x03, 0xE0, 0x00, 0x1F, 0x00, 0x00, 0xF8,
+ 0x00, 0x07, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xC0, 0x0F, 0xFE, 0x00, 0xFF, 0xFF, 0x01, 0xFF, 0xFF,
+ 0x83, 0xFF, 0xFF, 0x87, 0x00, 0x3F, 0x80, 0x00, 0x1F, 0x00, 0x00, 0x1F,
+ 0x00, 0x00, 0x1E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x78, 0x00, 0x00, 0xF0,
+ 0x00, 0x01, 0xE0, 0x00, 0x07, 0x80, 0x00, 0x1F, 0x00, 0x00, 0xFC, 0x01,
+ 0xFF, 0xF0, 0x03, 0xFF, 0x80, 0x07, 0xFF, 0x80, 0x0F, 0xFF, 0xC0, 0x00,
+ 0x1F, 0xC0, 0x00, 0x0F, 0xC0, 0x00, 0x07, 0x80, 0x00, 0x0F, 0x80, 0x00,
+ 0x0F, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x78, 0x00, 0x00,
+ 0xF0, 0x00, 0x03, 0xE0, 0x00, 0x0F, 0xA0, 0x00, 0x3F, 0x7C, 0x01, 0xFC,
+ 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xC1, 0xFF, 0xFE, 0x00, 0x3F, 0xE0, 0x00,
+ 0x00, 0x03, 0xF0, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFC, 0x00, 0x01, 0xFE,
+ 0x00, 0x01, 0xEF, 0x00, 0x00, 0xE7, 0x80, 0x00, 0xF3, 0xC0, 0x00, 0xF1,
+ 0xE0, 0x00, 0x78, 0xF0, 0x00, 0x78, 0x78, 0x00, 0x78, 0x3C, 0x00, 0x3C,
+ 0x1E, 0x00, 0x3C, 0x0F, 0x00, 0x3C, 0x07, 0x80, 0x1E, 0x03, 0xC0, 0x1E,
+ 0x01, 0xE0, 0x1E, 0x00, 0xF0, 0x0F, 0x00, 0x78, 0x0F, 0x00, 0x3C, 0x0F,
+ 0x80, 0x1E, 0x07, 0x80, 0x0F, 0x07, 0x80, 0x07, 0x83, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x3C,
+ 0x00, 0x00, 0x1E, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x07, 0x80, 0x00, 0x03,
+ 0xC0, 0x00, 0x01, 0xE0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x78, 0x00, 0x7F,
+ 0xFF, 0xE1, 0xFF, 0xFF, 0x87, 0xFF, 0xFE, 0x1F, 0xFF, 0xF8, 0x78, 0x00,
+ 0x01, 0xE0, 0x00, 0x07, 0x80, 0x00, 0x1E, 0x00, 0x00, 0x78, 0x00, 0x01,
+ 0xE0, 0x00, 0x07, 0x80, 0x00, 0x1E, 0xFE, 0x00, 0x7F, 0xFF, 0x01, 0xFF,
+ 0xFF, 0x07, 0xFF, 0xFE, 0x1E, 0x03, 0xFC, 0x40, 0x01, 0xF0, 0x00, 0x03,
+ 0xE0, 0x00, 0x07, 0x80, 0x00, 0x1F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0,
+ 0x00, 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00,
+ 0x07, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xFA, 0x00, 0x07, 0xCF, 0x00, 0x7F,
+ 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xC3, 0xFF, 0xFC, 0x01, 0xFF, 0xC0, 0x00,
+ 0x00, 0x1F, 0xF0, 0x00, 0xFF, 0xFC, 0x03, 0xFF, 0xFC, 0x07, 0xFF, 0xFC,
+ 0x0F, 0xE0, 0x0C, 0x1F, 0x80, 0x00, 0x1E, 0x00, 0x00, 0x3E, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0x78, 0x00, 0x00, 0x78, 0x00, 0x00, 0x78, 0x00, 0x00,
+ 0xF8, 0x7F, 0x00, 0xF1, 0xFF, 0xE0, 0xF3, 0xFF, 0xF0, 0xF7, 0xFF, 0xF8,
+ 0xFF, 0xC1, 0xFC, 0xFF, 0x00, 0x7E, 0xFE, 0x00, 0x3E, 0xFC, 0x00, 0x1E,
+ 0xFC, 0x00, 0x1F, 0xF8, 0x00, 0x0F, 0xF8, 0x00, 0x0F, 0xF8, 0x00, 0x0F,
+ 0x78, 0x00, 0x0F, 0x78, 0x00, 0x0F, 0x78, 0x00, 0x0F, 0x7C, 0x00, 0x1F,
+ 0x3C, 0x00, 0x1E, 0x3E, 0x00, 0x3E, 0x1F, 0x00, 0x7C, 0x1F, 0xC1, 0xFC,
+ 0x0F, 0xFF, 0xF8, 0x07, 0xFF, 0xF0, 0x03, 0xFF, 0xE0, 0x00, 0x7F, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00,
+ 0x00, 0x78, 0x00, 0x03, 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x7C, 0x00, 0x01,
+ 0xE0, 0x00, 0x07, 0x80, 0x00, 0x3E, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0,
+ 0x00, 0x1E, 0x00, 0x00, 0x78, 0x00, 0x03, 0xE0, 0x00, 0x0F, 0x00, 0x00,
+ 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x07, 0x80, 0x00, 0x3E, 0x00, 0x00, 0xF0,
+ 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0x78, 0x00, 0x03, 0xE0, 0x00,
+ 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x07, 0x80, 0x00, 0x3E,
+ 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x07, 0xFF, 0xE0, 0x0F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x3F, 0x81,
+ 0xFC, 0x3E, 0x00, 0x7C, 0x7C, 0x00, 0x3E, 0x78, 0x00, 0x1E, 0x78, 0x00,
+ 0x1E, 0x78, 0x00, 0x1E, 0x78, 0x00, 0x1E, 0x78, 0x00, 0x1E, 0x3C, 0x00,
+ 0x3C, 0x3E, 0x00, 0x7C, 0x1F, 0x81, 0xF8, 0x0F, 0xFF, 0xF0, 0x03, 0xFF,
+ 0xC0, 0x07, 0xFF, 0xE0, 0x1F, 0xFF, 0xF8, 0x3F, 0x81, 0xFC, 0x7C, 0x00,
+ 0x3E, 0x78, 0x00, 0x1E, 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x0F, 0xF0, 0x00,
+ 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF8, 0x00,
+ 0x1F, 0x78, 0x00, 0x1E, 0x7C, 0x00, 0x3E, 0x3F, 0x00, 0xFC, 0x3F, 0xFF,
+ 0xFC, 0x1F, 0xFF, 0xF8, 0x07, 0xFF, 0xE0, 0x00, 0xFF, 0x00, 0x00, 0xFE,
+ 0x00, 0x07, 0xFF, 0xC0, 0x0F, 0xFF, 0xE0, 0x1F, 0xFF, 0xF0, 0x3F, 0x83,
+ 0xF8, 0x7E, 0x00, 0xF8, 0x7C, 0x00, 0x7C, 0x78, 0x00, 0x3C, 0xF8, 0x00,
+ 0x3E, 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x1E, 0xF0, 0x00,
+ 0x1F, 0xF0, 0x00, 0x1F, 0xF0, 0x00, 0x1F, 0xF8, 0x00, 0x3F, 0x78, 0x00,
+ 0x3F, 0x7C, 0x00, 0x7F, 0x7E, 0x00, 0xFF, 0x3F, 0x83, 0xFF, 0x1F, 0xFF,
+ 0xEF, 0x0F, 0xFF, 0xCF, 0x07, 0xFF, 0x8F, 0x00, 0xFE, 0x1E, 0x00, 0x00,
+ 0x1E, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x7C, 0x00, 0x00, 0xF8, 0x00, 0x01, 0xF8, 0x30, 0x07, 0xF0, 0x3F, 0xFF,
+ 0xE0, 0x3F, 0xFF, 0xC0, 0x3F, 0xFF, 0x00, 0x0F, 0xF8, 0x00, 0xFF, 0xFF,
+ 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF,
+ 0xFF, 0x3C, 0xF3, 0xCF, 0x3C, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0xCF, 0x3C, 0xF3, 0xCE, 0x79, 0xC7, 0x3C, 0xE0, 0x00,
+ 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0xFE, 0x00, 0x00,
+ 0x3F, 0xF0, 0x00, 0x07, 0xFF, 0x00, 0x01, 0xFF, 0xE0, 0x00, 0x7F, 0xF8,
+ 0x00, 0x1F, 0xFE, 0x00, 0x03, 0xFF, 0x80, 0x00, 0xFF, 0xF0, 0x00, 0x3F,
+ 0xFC, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0x7F, 0xC0,
+ 0x00, 0x03, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0x80,
+ 0x00, 0x07, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0x80,
+ 0x00, 0x07, 0xFF, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x00, 0x0F, 0xE0, 0x00,
+ 0x00, 0x0F, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x80, 0x00, 0x00,
+ 0x07, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x07,
+ 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x0F,
+ 0xFF, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x1F, 0xFE, 0x00, 0x00, 0x1F,
+ 0xFE, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x07, 0xFC,
+ 0x00, 0x01, 0xFF, 0xE0, 0x00, 0x7F, 0xF8, 0x00, 0x0F, 0xFE, 0x00, 0x03,
+ 0xFF, 0xC0, 0x00, 0xFF, 0xF0, 0x00, 0x3F, 0xFC, 0x00, 0x07, 0xFF, 0x00,
+ 0x00, 0x7F, 0xE0, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x07, 0xF0, 0x0F, 0xFF, 0x07, 0xFF, 0xF3, 0xFF,
+ 0xFE, 0xF8, 0x1F, 0xB8, 0x01, 0xF8, 0x00, 0x7C, 0x00, 0x0F, 0x00, 0x03,
+ 0xC0, 0x00, 0xF0, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x80, 0x07, 0xC0,
+ 0x03, 0xE0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0x1F, 0x00, 0x0F,
+ 0x80, 0x03, 0xC0, 0x00, 0xF0, 0x00, 0x3C, 0x00, 0x0F, 0x00, 0x03, 0xC0,
+ 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00,
+ 0x3E, 0x00, 0x0F, 0x80, 0x03, 0xE0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x00,
+ 0x00, 0x7F, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF,
+ 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xF8, 0x03, 0xFE,
+ 0x00, 0x0F, 0xE0, 0x00, 0x3F, 0x80, 0x0F, 0xC0, 0x00, 0x07, 0xE0, 0x0F,
+ 0x80, 0x00, 0x01, 0xF8, 0x0F, 0x80, 0x00, 0x00, 0x7C, 0x0F, 0x80, 0x00,
+ 0x00, 0x1F, 0x07, 0x80, 0x00, 0x00, 0x07, 0x87, 0xC0, 0x1F, 0x87, 0x81,
+ 0xE3, 0xC0, 0x1F, 0xF3, 0xC0, 0xF3, 0xE0, 0x3F, 0xFD, 0xE0, 0x7D, 0xE0,
+ 0x1F, 0xFF, 0xF0, 0x1E, 0xF0, 0x1F, 0x83, 0xF8, 0x0F, 0xF0, 0x0F, 0x00,
+ 0x7C, 0x07, 0xF8, 0x0F, 0x80, 0x3E, 0x03, 0xFC, 0x07, 0x80, 0x0F, 0x01,
+ 0xFE, 0x03, 0xC0, 0x07, 0x80, 0xFF, 0x01, 0xE0, 0x03, 0xC0, 0x7F, 0x80,
+ 0xF0, 0x01, 0xE0, 0x7B, 0xC0, 0x78, 0x00, 0xF0, 0x3D, 0xE0, 0x3E, 0x00,
+ 0xF8, 0x3E, 0xF0, 0x0F, 0x00, 0x7C, 0x3E, 0x3C, 0x07, 0xE0, 0xFE, 0x7E,
+ 0x1E, 0x01, 0xFF, 0xFF, 0xFE, 0x0F, 0x00, 0xFF, 0xF7, 0xFE, 0x07, 0xC0,
+ 0x3F, 0xF3, 0xFC, 0x01, 0xF0, 0x07, 0xE1, 0xF0, 0x00, 0xF8, 0x00, 0x00,
+ 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x00,
+ 0x07, 0xE0, 0x00, 0x00, 0x80, 0x01, 0xFC, 0x00, 0x01, 0xE0, 0x00, 0x7F,
+ 0x80, 0x01, 0xF0, 0x00, 0x1F, 0xF8, 0x07, 0xF8, 0x00, 0x03, 0xFF, 0xFF,
+ 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00,
+ 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x1F,
+ 0xC0, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x01, 0xFF,
+ 0x00, 0x00, 0x03, 0xDE, 0x00, 0x00, 0x0F, 0xBE, 0x00, 0x00, 0x1E, 0x3C,
+ 0x00, 0x00, 0x3C, 0x78, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x01, 0xE0, 0xF0,
+ 0x00, 0x03, 0xC1, 0xE0, 0x00, 0x0F, 0x01, 0xE0, 0x00, 0x1E, 0x03, 0xC0,
+ 0x00, 0x7C, 0x07, 0xC0, 0x00, 0xF0, 0x07, 0x80, 0x01, 0xE0, 0x0F, 0x00,
+ 0x07, 0xC0, 0x1F, 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x3E, 0x00, 0x3E, 0x00,
+ 0x78, 0x00, 0x3C, 0x00, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xF8, 0x07,
+ 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xE0, 0x3E, 0x00, 0x03, 0xE0, 0x78,
+ 0x00, 0x03, 0xC1, 0xF0, 0x00, 0x07, 0xC3, 0xC0, 0x00, 0x07, 0x87, 0x80,
+ 0x00, 0x0F, 0x1F, 0x00, 0x00, 0x1F, 0x3C, 0x00, 0x00, 0x1E, 0x78, 0x00,
+ 0x00, 0x3D, 0xE0, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xE0,
+ 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xF8, 0xF0, 0x01, 0xFC, 0xF0, 0x00, 0x7E,
+ 0xF0, 0x00, 0x3E, 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x1E,
+ 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x3E, 0xF0, 0x00, 0x7C, 0xF0, 0x01, 0xFC,
+ 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF8,
+ 0xF0, 0x00, 0xFC, 0xF0, 0x00, 0x3E, 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x0F,
+ 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F,
+ 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x1F, 0xF0, 0x00, 0x3E, 0xF0, 0x00, 0xFE,
+ 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0x80,
+ 0x00, 0x0F, 0xFC, 0x00, 0x07, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xE0, 0x3F,
+ 0xFF, 0xFF, 0x07, 0xF0, 0x07, 0xF0, 0xFC, 0x00, 0x0F, 0x1F, 0x00, 0x00,
+ 0x31, 0xE0, 0x00, 0x01, 0x3E, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x78,
+ 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x78, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F,
+ 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00,
+ 0x78, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x03, 0xE0,
+ 0x00, 0x00, 0x1E, 0x00, 0x00, 0x11, 0xF0, 0x00, 0x03, 0x0F, 0xC0, 0x00,
+ 0xF0, 0x7F, 0x80, 0x7F, 0x03, 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFE, 0x00,
+ 0x7F, 0xFF, 0x80, 0x00, 0xFF, 0xC0, 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFF,
+ 0x80, 0x3F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFC, 0x0F, 0x00, 0x1F, 0xF8,
+ 0x78, 0x00, 0x0F, 0xE3, 0xC0, 0x00, 0x1F, 0x1E, 0x00, 0x00, 0x7C, 0xF0,
+ 0x00, 0x01, 0xE7, 0x80, 0x00, 0x0F, 0xBC, 0x00, 0x00, 0x3D, 0xE0, 0x00,
+ 0x01, 0xEF, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x01,
+ 0xFE, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x03, 0xFC,
+ 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x07, 0xF8, 0x00,
+ 0x00, 0x7F, 0xC0, 0x00, 0x03, 0xDE, 0x00, 0x00, 0x1E, 0xF0, 0x00, 0x01,
+ 0xF7, 0x80, 0x00, 0x0F, 0x3C, 0x00, 0x00, 0xF9, 0xE0, 0x00, 0x0F, 0x8F,
+ 0x00, 0x01, 0xFC, 0x78, 0x00, 0x7F, 0xC3, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF,
+ 0xFF, 0x80, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03,
+ 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0,
+ 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00,
+ 0x0F, 0xFF, 0xFF, 0xBF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF,
+ 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0x00,
+ 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0,
+ 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F,
+ 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE,
+ 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00,
+ 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00,
+ 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0,
+ 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0xFC,
+ 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFE,
+ 0x07, 0xF8, 0x07, 0xF8, 0x3F, 0x00, 0x01, 0xE1, 0xF0, 0x00, 0x01, 0x8F,
+ 0x80, 0x00, 0x02, 0x3E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x07, 0x80,
+ 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x03, 0xC0, 0x00,
+ 0x00, 0x0F, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00,
+ 0x03, 0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x0F, 0xFF,
+ 0xF0, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x03, 0xDE,
+ 0x00, 0x00, 0x0F, 0x78, 0x00, 0x00, 0x3D, 0xE0, 0x00, 0x00, 0xF7, 0xC0,
+ 0x00, 0x03, 0xCF, 0x80, 0x00, 0x0F, 0x3E, 0x00, 0x00, 0x3C, 0x7E, 0x00,
+ 0x00, 0xF0, 0xFC, 0x00, 0x07, 0xC1, 0xFE, 0x00, 0x7F, 0x03, 0xFF, 0xFF,
+ 0xF8, 0x07, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0x00,
+ 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00,
+ 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F,
+ 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF,
+ 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00,
+ 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00,
+ 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC,
+ 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xE0, 0x3C, 0x07,
+ 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80,
+ 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0,
+ 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E,
+ 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x3C, 0x07,
+ 0x83, 0xF7, 0xFC, 0xFF, 0x9F, 0xC3, 0xE0, 0x00, 0xF0, 0x00, 0x1F, 0x9E,
+ 0x00, 0x07, 0xE3, 0xC0, 0x01, 0xF8, 0x78, 0x00, 0x7E, 0x0F, 0x00, 0x1F,
+ 0x81, 0xE0, 0x07, 0xE0, 0x3C, 0x01, 0xF8, 0x07, 0x80, 0x7E, 0x00, 0xF0,
+ 0x1F, 0x80, 0x1E, 0x07, 0xE0, 0x03, 0xC3, 0xF0, 0x00, 0x78, 0xFC, 0x00,
+ 0x0F, 0x3F, 0x00, 0x01, 0xEF, 0xC0, 0x00, 0x3F, 0xF0, 0x00, 0x07, 0xFC,
+ 0x00, 0x00, 0xFF, 0x80, 0x00, 0x1F, 0xF8, 0x00, 0x03, 0xDF, 0x80, 0x00,
+ 0x79, 0xF8, 0x00, 0x0F, 0x1F, 0x80, 0x01, 0xE0, 0xF8, 0x00, 0x3C, 0x0F,
+ 0x80, 0x07, 0x80, 0xF8, 0x00, 0xF0, 0x0F, 0x80, 0x1E, 0x00, 0xF8, 0x03,
+ 0xC0, 0x0F, 0x80, 0x78, 0x00, 0xF8, 0x0F, 0x00, 0x0F, 0x81, 0xE0, 0x00,
+ 0xF8, 0x3C, 0x00, 0x0F, 0x87, 0x80, 0x00, 0xF8, 0xF0, 0x00, 0x0F, 0x9E,
+ 0x00, 0x00, 0xFC, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0,
+ 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00,
+ 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00,
+ 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07,
+ 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00,
+ 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C,
+ 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFE, 0x00, 0x00,
+ 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x0F,
+ 0xFF, 0xF0, 0x00, 0x1F, 0xFE, 0xF0, 0x00, 0x7B, 0xFD, 0xE0, 0x00, 0xF7,
+ 0xFB, 0xE0, 0x03, 0xEF, 0xF3, 0xC0, 0x07, 0x9F, 0xE7, 0x80, 0x0F, 0x3F,
+ 0xC7, 0x80, 0x3C, 0x7F, 0x8F, 0x00, 0x78, 0xFF, 0x1F, 0x01, 0xF1, 0xFE,
+ 0x1E, 0x03, 0xC3, 0xFC, 0x3C, 0x07, 0x87, 0xF8, 0x3C, 0x1E, 0x0F, 0xF0,
+ 0x78, 0x3C, 0x1F, 0xE0, 0xF8, 0xF8, 0x3F, 0xC0, 0xF1, 0xE0, 0x7F, 0x81,
+ 0xE3, 0xC0, 0xFF, 0x01, 0xEF, 0x01, 0xFE, 0x03, 0xDE, 0x03, 0xFC, 0x07,
+ 0xFC, 0x07, 0xF8, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xE0, 0x1F, 0xE0, 0x1F,
+ 0xC0, 0x3F, 0xC0, 0x1F, 0x00, 0x7F, 0x80, 0x00, 0x00, 0xFF, 0x00, 0x00,
+ 0x01, 0xFE, 0x00, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00,
+ 0x0F, 0xF0, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x3C, 0xFC, 0x00, 0x03,
+ 0xFF, 0x80, 0x00, 0xFF, 0xE0, 0x00, 0x3F, 0xFC, 0x00, 0x0F, 0xFF, 0x00,
+ 0x03, 0xFF, 0xE0, 0x00, 0xFF, 0x78, 0x00, 0x3F, 0xDF, 0x00, 0x0F, 0xF3,
+ 0xE0, 0x03, 0xFC, 0x78, 0x00, 0xFF, 0x1F, 0x00, 0x3F, 0xC3, 0xC0, 0x0F,
+ 0xF0, 0xF8, 0x03, 0xFC, 0x1E, 0x00, 0xFF, 0x07, 0xC0, 0x3F, 0xC0, 0xF0,
+ 0x0F, 0xF0, 0x3E, 0x03, 0xFC, 0x07, 0xC0, 0xFF, 0x00, 0xF0, 0x3F, 0xC0,
+ 0x3E, 0x0F, 0xF0, 0x07, 0x83, 0xFC, 0x01, 0xF0, 0xFF, 0x00, 0x3C, 0x3F,
+ 0xC0, 0x0F, 0x8F, 0xF0, 0x01, 0xE3, 0xFC, 0x00, 0x7C, 0xFF, 0x00, 0x0F,
+ 0xBF, 0xC0, 0x01, 0xEF, 0xF0, 0x00, 0x7F, 0xFC, 0x00, 0x0F, 0xFF, 0x00,
+ 0x03, 0xFF, 0xC0, 0x00, 0x7F, 0xF0, 0x00, 0x1F, 0xFC, 0x00, 0x03, 0xF0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0x80,
+ 0x03, 0xFF, 0xFF, 0xC0, 0x07, 0xF0, 0x0F, 0xE0, 0x0F, 0xC0, 0x03, 0xF0,
+ 0x1F, 0x80, 0x01, 0xF8, 0x3E, 0x00, 0x00, 0x7C, 0x3E, 0x00, 0x00, 0x7C,
+ 0x7C, 0x00, 0x00, 0x3C, 0x78, 0x00, 0x00, 0x3E, 0x78, 0x00, 0x00, 0x1E,
+ 0x78, 0x00, 0x00, 0x1E, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F,
+ 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F,
+ 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F,
+ 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0x78, 0x00, 0x00, 0x1E,
+ 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x7C, 0x00, 0x00, 0x3C,
+ 0x3E, 0x00, 0x00, 0x7C, 0x3E, 0x00, 0x00, 0x7C, 0x1F, 0x80, 0x01, 0xF8,
+ 0x0F, 0xC0, 0x03, 0xF0, 0x07, 0xF0, 0x0F, 0xE0, 0x03, 0xFF, 0xFF, 0xC0,
+ 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x0F, 0xF0, 0x00,
+ 0xFF, 0xFE, 0x03, 0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0xF0,
+ 0x03, 0xFB, 0xC0, 0x03, 0xEF, 0x00, 0x07, 0xBC, 0x00, 0x1F, 0xF0, 0x00,
+ 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F,
+ 0xC0, 0x01, 0xFF, 0x00, 0x07, 0xBC, 0x00, 0x3E, 0xF0, 0x03, 0xFB, 0xFF,
+ 0xFF, 0xCF, 0xFF, 0xFE, 0x3F, 0xFF, 0xE0, 0xFF, 0xFE, 0x03, 0xC0, 0x00,
+ 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F,
+ 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0x00,
+ 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x0F,
+ 0xF0, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x07, 0xF0, 0x0F, 0xE0, 0x0F, 0xC0, 0x03, 0xF0, 0x1F, 0x80,
+ 0x01, 0xF8, 0x3E, 0x00, 0x00, 0x7C, 0x3E, 0x00, 0x00, 0x7C, 0x7C, 0x00,
+ 0x00, 0x3C, 0x78, 0x00, 0x00, 0x3E, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00,
+ 0x00, 0x1E, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00,
+ 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00,
+ 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00,
+ 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00,
+ 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x7C, 0x00, 0x00, 0x3E, 0x3E, 0x00,
+ 0x00, 0x7C, 0x3E, 0x00, 0x00, 0x7C, 0x1F, 0x80, 0x01, 0xF8, 0x0F, 0xC0,
+ 0x03, 0xF0, 0x07, 0xF0, 0x0F, 0xE0, 0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF,
+ 0xFF, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00,
+ 0x1F, 0x80, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00,
+ 0x03, 0xF0, 0x00, 0x00, 0x01, 0xF8, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xF8,
+ 0x03, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xF8, 0x0F, 0x00, 0x3F, 0x81, 0xE0,
+ 0x01, 0xF0, 0x3C, 0x00, 0x1F, 0x07, 0x80, 0x01, 0xE0, 0xF0, 0x00, 0x3C,
+ 0x1E, 0x00, 0x07, 0x83, 0xC0, 0x00, 0xF0, 0x78, 0x00, 0x1E, 0x0F, 0x00,
+ 0x03, 0xC1, 0xE0, 0x00, 0xF8, 0x3C, 0x00, 0x3E, 0x07, 0x80, 0x1F, 0xC0,
+ 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFC, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF,
+ 0xF0, 0x0F, 0x00, 0x7E, 0x01, 0xE0, 0x03, 0xE0, 0x3C, 0x00, 0x3E, 0x07,
+ 0x80, 0x07, 0xC0, 0xF0, 0x00, 0x7C, 0x1E, 0x00, 0x07, 0x83, 0xC0, 0x00,
+ 0xF8, 0x78, 0x00, 0x0F, 0x0F, 0x00, 0x01, 0xF1, 0xE0, 0x00, 0x1E, 0x3C,
+ 0x00, 0x03, 0xE7, 0x80, 0x00, 0x3E, 0xF0, 0x00, 0x03, 0xDE, 0x00, 0x00,
+ 0x7C, 0x00, 0xFF, 0x80, 0x07, 0xFF, 0xF8, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF,
+ 0xFC, 0x3F, 0x00, 0xFC, 0x7C, 0x00, 0x1C, 0x78, 0x00, 0x04, 0xF0, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0xF8, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x3F, 0xFE,
+ 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xF8, 0x00, 0x7F,
+ 0xFC, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x3F, 0x00, 0x00,
+ 0x1F, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x1E, 0xE0, 0x00, 0x3E, 0xFE, 0x01,
+ 0xFC, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xF8, 0x3F, 0xFF, 0xE0, 0x03, 0xFF,
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F,
+ 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00,
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F,
+ 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00,
+ 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00,
+ 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F,
+ 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00,
+ 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00,
+ 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF,
+ 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFE, 0x00, 0x01,
+ 0xF7, 0x80, 0x00, 0x79, 0xE0, 0x00, 0x1E, 0x7C, 0x00, 0x0F, 0x8F, 0x80,
+ 0x07, 0xC3, 0xF8, 0x07, 0xF0, 0x7F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFC, 0x00,
+ 0xFF, 0xFC, 0x00, 0x0F, 0xFC, 0x00, 0xF0, 0x00, 0x00, 0x1E, 0xF0, 0x00,
+ 0x00, 0x79, 0xE0, 0x00, 0x00, 0xF3, 0xE0, 0x00, 0x03, 0xE3, 0xC0, 0x00,
+ 0x07, 0x87, 0x80, 0x00, 0x0F, 0x0F, 0x80, 0x00, 0x3E, 0x0F, 0x00, 0x00,
+ 0x78, 0x1F, 0x00, 0x01, 0xF0, 0x1E, 0x00, 0x03, 0xC0, 0x3C, 0x00, 0x07,
+ 0x80, 0x7C, 0x00, 0x1F, 0x00, 0x78, 0x00, 0x3C, 0x00, 0xF0, 0x00, 0x78,
+ 0x00, 0xF0, 0x01, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x03, 0xE0, 0x0F, 0x80,
+ 0x03, 0xC0, 0x1E, 0x00, 0x07, 0x80, 0x3C, 0x00, 0x0F, 0x80, 0xF8, 0x00,
+ 0x0F, 0x01, 0xE0, 0x00, 0x1E, 0x03, 0xC0, 0x00, 0x1E, 0x0F, 0x00, 0x00,
+ 0x3C, 0x1E, 0x00, 0x00, 0x7C, 0x7C, 0x00, 0x00, 0x78, 0xF0, 0x00, 0x00,
+ 0xF1, 0xE0, 0x00, 0x01, 0xF7, 0xC0, 0x00, 0x01, 0xEF, 0x00, 0x00, 0x03,
+ 0xFE, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x07, 0xF0, 0x00, 0x00, 0x0F,
+ 0xE0, 0x00, 0x00, 0x0F, 0x80, 0x00, 0xF0, 0x00, 0x3F, 0x80, 0x01, 0xFF,
+ 0x00, 0x07, 0xF0, 0x00, 0x7D, 0xE0, 0x00, 0xFE, 0x00, 0x0F, 0x3C, 0x00,
+ 0x1F, 0xC0, 0x01, 0xE7, 0x80, 0x07, 0xFC, 0x00, 0x3C, 0xF8, 0x00, 0xF7,
+ 0x80, 0x0F, 0x8F, 0x00, 0x1E, 0xF0, 0x01, 0xE1, 0xE0, 0x03, 0xDE, 0x00,
+ 0x3C, 0x3C, 0x00, 0xFB, 0xE0, 0x07, 0x87, 0xC0, 0x1E, 0x3C, 0x01, 0xF0,
+ 0x78, 0x03, 0xC7, 0x80, 0x3C, 0x0F, 0x00, 0x78, 0xF0, 0x07, 0x81, 0xE0,
+ 0x1F, 0x1F, 0x00, 0xF0, 0x3E, 0x03, 0xC1, 0xE0, 0x3E, 0x03, 0xC0, 0x78,
+ 0x3C, 0x07, 0x80, 0x78, 0x0F, 0x07, 0x80, 0xF0, 0x0F, 0x03, 0xE0, 0xF8,
+ 0x1E, 0x01, 0xF0, 0x78, 0x0F, 0x07, 0xC0, 0x1E, 0x0F, 0x01, 0xE0, 0xF0,
+ 0x03, 0xC1, 0xE0, 0x3C, 0x1E, 0x00, 0x78, 0x7C, 0x07, 0xC3, 0xC0, 0x0F,
+ 0x8F, 0x00, 0x78, 0xF8, 0x00, 0xF1, 0xE0, 0x0F, 0x1E, 0x00, 0x1E, 0x3C,
+ 0x01, 0xE3, 0xC0, 0x03, 0xCF, 0x80, 0x3E, 0x78, 0x00, 0x7D, 0xE0, 0x03,
+ 0xDF, 0x00, 0x07, 0xBC, 0x00, 0x7B, 0xC0, 0x00, 0xF7, 0x80, 0x0F, 0x78,
+ 0x00, 0x1E, 0xF0, 0x01, 0xEF, 0x00, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x00,
+ 0x3F, 0x80, 0x03, 0xF8, 0x00, 0x07, 0xF0, 0x00, 0x7F, 0x00, 0x00, 0xFE,
+ 0x00, 0x0F, 0xE0, 0x00, 0x1F, 0x80, 0x00, 0xFC, 0x00, 0x3E, 0x00, 0x01,
+ 0xF0, 0xF8, 0x00, 0x1F, 0x03, 0xC0, 0x01, 0xF0, 0x1F, 0x00, 0x0F, 0x80,
+ 0x7C, 0x00, 0xF8, 0x01, 0xE0, 0x0F, 0x80, 0x0F, 0x80, 0x7C, 0x00, 0x3E,
+ 0x07, 0xC0, 0x00, 0xF0, 0x7C, 0x00, 0x07, 0xC3, 0xE0, 0x00, 0x1F, 0x3E,
+ 0x00, 0x00, 0xFB, 0xE0, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x0F, 0xF0, 0x00,
+ 0x00, 0x7F, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00,
+ 0xFE, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x07, 0xDF,
+ 0x00, 0x00, 0x3C, 0x7C, 0x00, 0x03, 0xE1, 0xF0, 0x00, 0x3E, 0x0F, 0x80,
+ 0x03, 0xE0, 0x3E, 0x00, 0x1F, 0x00, 0xF0, 0x01, 0xF0, 0x07, 0xC0, 0x1F,
+ 0x00, 0x1F, 0x00, 0xF8, 0x00, 0x78, 0x0F, 0x80, 0x03, 0xE0, 0xF8, 0x00,
+ 0x0F, 0x87, 0xC0, 0x00, 0x3C, 0x7C, 0x00, 0x01, 0xF7, 0xC0, 0x00, 0x07,
+ 0xC0, 0xF8, 0x00, 0x01, 0xF7, 0xC0, 0x00, 0x3E, 0x3E, 0x00, 0x07, 0xC3,
+ 0xE0, 0x00, 0x7C, 0x1F, 0x00, 0x0F, 0x80, 0xF8, 0x01, 0xF0, 0x0F, 0x80,
+ 0x1F, 0x00, 0x7C, 0x03, 0xE0, 0x03, 0xE0, 0x7C, 0x00, 0x3E, 0x07, 0xC0,
+ 0x01, 0xF0, 0xF8, 0x00, 0x0F, 0x9F, 0x00, 0x00, 0xF9, 0xF0, 0x00, 0x07,
+ 0xFE, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x1F, 0x80,
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F,
+ 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00,
+ 0x7F, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xE7, 0xFF,
+ 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x1F,
+ 0x80, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x07, 0xC0, 0x00,
+ 0x00, 0xF8, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x3E,
+ 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x1F, 0x00, 0x00,
+ 0x01, 0xF0, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0xF8,
+ 0x00, 0x00, 0x1F, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x3E, 0x00, 0x00,
+ 0x07, 0xC0, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x03, 0xF0,
+ 0x00, 0x00, 0x3E, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F,
+ 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0,
+ 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C,
+ 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0x00, 0xF8, 0x00, 0x78, 0x00, 0x78, 0x00,
+ 0x7C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x1E, 0x00,
+ 0x1E, 0x00, 0x1F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x80, 0x07, 0x80,
+ 0x07, 0x80, 0x07, 0x80, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x01, 0xE0,
+ 0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF8,
+ 0x00, 0x78, 0x00, 0x78, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C,
+ 0x00, 0x3E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x0F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0,
+ 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1E,
+ 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83,
+ 0xC1, 0xE0, 0xF0, 0x78, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F,
+ 0x80, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0xE0,
+ 0x00, 0x0F, 0xDF, 0x80, 0x00, 0xFC, 0x7E, 0x00, 0x0F, 0xC1, 0xF8, 0x00,
+ 0xF8, 0x07, 0xE0, 0x0F, 0x80, 0x0F, 0x80, 0xF8, 0x00, 0x3E, 0x0F, 0x80,
+ 0x00, 0xF8, 0xF8, 0x00, 0x03, 0xEF, 0x80, 0x00, 0x0F, 0x80, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F,
+ 0x80, 0xF0, 0x0F, 0x00, 0xF0, 0x0E, 0x01, 0xE0, 0x1E, 0x01, 0xE0, 0x01,
+ 0xFE, 0x00, 0x7F, 0xFE, 0x03, 0xFF, 0xFE, 0x0F, 0xFF, 0xF8, 0x3C, 0x03,
+ 0xF0, 0x80, 0x03, 0xE0, 0x00, 0x07, 0x80, 0x00, 0x1E, 0x00, 0x00, 0x3C,
+ 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x7F, 0xFF, 0x0F, 0xFF, 0xFC, 0x7F,
+ 0xFF, 0xF3, 0xFF, 0xFF, 0xDF, 0xC0, 0x0F, 0x78, 0x00, 0x3F, 0xE0, 0x00,
+ 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x1F, 0xF0, 0x00, 0x7F, 0xC0, 0x03, 0xFF,
+ 0x80, 0x1F, 0xDF, 0x81, 0xFF, 0x7F, 0xFF, 0xBC, 0xFF, 0xFC, 0xF1, 0xFF,
+ 0xE3, 0xC1, 0xFC, 0x00, 0xF0, 0x00, 0x01, 0xE0, 0x00, 0x03, 0xC0, 0x00,
+ 0x07, 0x80, 0x00, 0x0F, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x78, 0x00, 0x00, 0xF0, 0x00, 0x01, 0xE0, 0xFE, 0x03, 0xC7, 0xFF, 0x07,
+ 0x9F, 0xFF, 0x0F, 0x7F, 0xFF, 0x1F, 0xF0, 0x7E, 0x3F, 0x80, 0x3E, 0x7E,
+ 0x00, 0x3E, 0xF8, 0x00, 0x3D, 0xF0, 0x00, 0x7B, 0xE0, 0x00, 0xFF, 0x80,
+ 0x00, 0xFF, 0x00, 0x01, 0xFE, 0x00, 0x03, 0xFC, 0x00, 0x07, 0xF8, 0x00,
+ 0x0F, 0xF0, 0x00, 0x1F, 0xE0, 0x00, 0x3F, 0xC0, 0x00, 0x7F, 0xC0, 0x01,
+ 0xFF, 0x80, 0x03, 0xDF, 0x00, 0x07, 0xBF, 0x00, 0x1F, 0x7F, 0x00, 0x7C,
+ 0xFF, 0x83, 0xF1, 0xEF, 0xFF, 0xE3, 0xCF, 0xFF, 0x87, 0x8F, 0xFE, 0x00,
+ 0x07, 0xE0, 0x00, 0x00, 0x7F, 0x80, 0x3F, 0xFE, 0x07, 0xFF, 0xF0, 0xFF,
+ 0xFF, 0x1F, 0xC0, 0xF3, 0xF0, 0x01, 0x7C, 0x00, 0x07, 0xC0, 0x00, 0x78,
+ 0x00, 0x0F, 0x80, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F,
+ 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00,
+ 0xF8, 0x00, 0x07, 0x80, 0x00, 0x7C, 0x00, 0x03, 0xC0, 0x00, 0x3F, 0x00,
+ 0x11, 0xFC, 0x0F, 0x0F, 0xFF, 0xF0, 0x7F, 0xFF, 0x03, 0xFF, 0xE0, 0x07,
+ 0xF0, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x78, 0x00, 0x00,
+ 0xF0, 0x00, 0x01, 0xE0, 0x00, 0x03, 0xC0, 0x00, 0x07, 0x80, 0x00, 0x0F,
+ 0x00, 0x00, 0x1E, 0x01, 0xF8, 0x3C, 0x1F, 0xFC, 0x78, 0x7F, 0xFC, 0xF1,
+ 0xFF, 0xFD, 0xE7, 0xF0, 0x7F, 0xCF, 0x80, 0x3F, 0xBE, 0x00, 0x3F, 0x78,
+ 0x00, 0x3E, 0xF0, 0x00, 0x7F, 0xE0, 0x00, 0xFF, 0x80, 0x00, 0xFF, 0x00,
+ 0x01, 0xFE, 0x00, 0x03, 0xFC, 0x00, 0x07, 0xF8, 0x00, 0x0F, 0xF0, 0x00,
+ 0x1F, 0xE0, 0x00, 0x3F, 0xC0, 0x00, 0x7F, 0xC0, 0x01, 0xF7, 0x80, 0x03,
+ 0xEF, 0x00, 0x07, 0xDF, 0x00, 0x1F, 0x9F, 0x00, 0x7F, 0x3F, 0x83, 0xFE,
+ 0x3F, 0xFF, 0xBC, 0x3F, 0xFE, 0x78, 0x3F, 0xF8, 0xF0, 0x1F, 0xC0, 0x00,
+ 0x00, 0x7F, 0x80, 0x03, 0xFF, 0xE0, 0x07, 0xFF, 0xF0, 0x0F, 0xFF, 0xF8,
+ 0x1F, 0x80, 0xFC, 0x3E, 0x00, 0x3E, 0x3C, 0x00, 0x1E, 0x78, 0x00, 0x1E,
+ 0x78, 0x00, 0x0F, 0x70, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x78, 0x00, 0x00,
+ 0x7C, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x3F, 0x00, 0x02, 0x1F, 0xC0, 0x3E,
+ 0x0F, 0xFF, 0xFE, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFC, 0x00, 0x7F, 0xC0,
+ 0x00, 0xFF, 0x03, 0xFF, 0x07, 0xFF, 0x07, 0xFF, 0x0F, 0x80, 0x0F, 0x00,
+ 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0xFF, 0xFE, 0xFF, 0xFE,
+ 0xFF, 0xFE, 0xFF, 0xFE, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00,
+ 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00,
+ 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00,
+ 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00,
+ 0x01, 0xFC, 0x00, 0x0F, 0xFE, 0x3C, 0x3F, 0xFE, 0x78, 0xFF, 0xFE, 0xF3,
+ 0xF8, 0x3F, 0xE7, 0xC0, 0x1F, 0xDF, 0x00, 0x1F, 0xBC, 0x00, 0x1F, 0x78,
+ 0x00, 0x3F, 0xF0, 0x00, 0x7F, 0xC0, 0x00, 0x7F, 0x80, 0x00, 0xFF, 0x00,
+ 0x01, 0xFE, 0x00, 0x03, 0xFC, 0x00, 0x07, 0xF8, 0x00, 0x0F, 0xF0, 0x00,
+ 0x1F, 0xE0, 0x00, 0x7D, 0xE0, 0x00, 0xFB, 0xC0, 0x01, 0xF7, 0xC0, 0x07,
+ 0xE7, 0xC0, 0x1F, 0xCF, 0xE0, 0xFF, 0x8F, 0xFF, 0xEF, 0x0F, 0xFF, 0x9E,
+ 0x0F, 0xFE, 0x3C, 0x07, 0xF0, 0x78, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0,
+ 0x00, 0x07, 0x80, 0x00, 0x1F, 0x08, 0x00, 0x7C, 0x1E, 0x03, 0xF8, 0x3F,
+ 0xFF, 0xE0, 0x7F, 0xFF, 0x80, 0x7F, 0xFE, 0x00, 0x1F, 0xE0, 0x00, 0xF0,
+ 0x00, 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00,
+ 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03,
+ 0xC1, 0xFC, 0x0F, 0x1F, 0xFC, 0x3C, 0xFF, 0xF8, 0xF7, 0xFF, 0xF3, 0xFE,
+ 0x07, 0xEF, 0xE0, 0x0F, 0xBF, 0x00, 0x1E, 0xF8, 0x00, 0x7F, 0xE0, 0x00,
+ 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF,
+ 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00,
+ 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03,
+ 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC,
+ 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x83, 0xC1, 0xE0,
+ 0xF0, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0xF0, 0x78, 0x3C,
+ 0x1E, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07,
+ 0x83, 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xC1, 0xE0,
+ 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x0F, 0x8F, 0xBF, 0xDF, 0xCF, 0xE7, 0xC0,
+ 0xF0, 0x00, 0x01, 0xE0, 0x00, 0x03, 0xC0, 0x00, 0x07, 0x80, 0x00, 0x0F,
+ 0x00, 0x00, 0x1E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x78, 0x00, 0x00, 0xF0,
+ 0x00, 0x01, 0xE0, 0x00, 0x03, 0xC0, 0x07, 0xE7, 0x80, 0x1F, 0x8F, 0x00,
+ 0x7E, 0x1E, 0x01, 0xF8, 0x3C, 0x07, 0xE0, 0x78, 0x1F, 0x80, 0xF0, 0x7E,
+ 0x01, 0xE1, 0xF8, 0x03, 0xCF, 0xC0, 0x07, 0xBF, 0x00, 0x0F, 0xFC, 0x00,
+ 0x1F, 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0x7F, 0xE0, 0x00, 0xF7, 0xE0, 0x01,
+ 0xE7, 0xE0, 0x03, 0xC7, 0xE0, 0x07, 0x87, 0xE0, 0x0F, 0x07, 0xE0, 0x1E,
+ 0x07, 0xE0, 0x3C, 0x07, 0xE0, 0x78, 0x07, 0xE0, 0xF0, 0x07, 0xE1, 0xE0,
+ 0x03, 0xE3, 0xC0, 0x03, 0xE7, 0x80, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x00, 0x7E, 0x00, 0x3F, 0x83, 0xC7, 0xFE, 0x03, 0xFF, 0x0F,
+ 0x3F, 0xFC, 0x1F, 0xFE, 0x3D, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0x83, 0xF7,
+ 0xC1, 0xFB, 0xF8, 0x07, 0xDC, 0x03, 0xEF, 0xC0, 0x0F, 0xE0, 0x07, 0xBE,
+ 0x00, 0x3F, 0x00, 0x1F, 0xF8, 0x00, 0x7C, 0x00, 0x3F, 0xC0, 0x01, 0xE0,
+ 0x00, 0xFF, 0x00, 0x07, 0x80, 0x03, 0xFC, 0x00, 0x1E, 0x00, 0x0F, 0xF0,
+ 0x00, 0x78, 0x00, 0x3F, 0xC0, 0x01, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0x80,
+ 0x03, 0xFC, 0x00, 0x1E, 0x00, 0x0F, 0xF0, 0x00, 0x78, 0x00, 0x3F, 0xC0,
+ 0x01, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0x80, 0x03, 0xFC, 0x00, 0x1E, 0x00,
+ 0x0F, 0xF0, 0x00, 0x78, 0x00, 0x3F, 0xC0, 0x01, 0xE0, 0x00, 0xFF, 0x00,
+ 0x07, 0x80, 0x03, 0xFC, 0x00, 0x1E, 0x00, 0x0F, 0xF0, 0x00, 0x78, 0x00,
+ 0x3F, 0xC0, 0x01, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x00,
+ 0x7F, 0x03, 0xC7, 0xFF, 0x0F, 0x3F, 0xFE, 0x3D, 0xFF, 0xFC, 0xFF, 0x81,
+ 0xFB, 0xF8, 0x03, 0xEF, 0xC0, 0x07, 0xBE, 0x00, 0x1F, 0xF8, 0x00, 0x3F,
+ 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0,
+ 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00,
+ 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF,
+ 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00,
+ 0x03, 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x1F,
+ 0xFF, 0xF8, 0x1F, 0x81, 0xF8, 0x3E, 0x00, 0x7C, 0x7C, 0x00, 0x3E, 0x7C,
+ 0x00, 0x3E, 0x78, 0x00, 0x1E, 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x0F, 0xF0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF8, 0x00, 0x1F, 0x78,
+ 0x00, 0x1E, 0x7C, 0x00, 0x3E, 0x7C, 0x00, 0x3E, 0x3E, 0x00, 0x7C, 0x1F,
+ 0x81, 0xF8, 0x1F, 0xFF, 0xF8, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xC0, 0x00,
+ 0xFF, 0x00, 0x00, 0x7F, 0x01, 0xE3, 0xFF, 0x83, 0xCF, 0xFF, 0x87, 0xBF,
+ 0xFF, 0x8F, 0xF8, 0x3F, 0x1F, 0xC0, 0x1F, 0x3F, 0x00, 0x1F, 0x7C, 0x00,
+ 0x1E, 0xF8, 0x00, 0x3D, 0xF0, 0x00, 0x7F, 0xC0, 0x00, 0x7F, 0x80, 0x00,
+ 0xFF, 0x00, 0x01, 0xFE, 0x00, 0x03, 0xFC, 0x00, 0x07, 0xF8, 0x00, 0x0F,
+ 0xF0, 0x00, 0x1F, 0xE0, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0xC0, 0x01, 0xEF,
+ 0x80, 0x03, 0xDF, 0x80, 0x0F, 0xBF, 0x80, 0x3E, 0x7F, 0xC1, 0xF8, 0xF7,
+ 0xFF, 0xF1, 0xE7, 0xFF, 0xC3, 0xC7, 0xFF, 0x07, 0x83, 0xF0, 0x0F, 0x00,
+ 0x00, 0x1E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x78, 0x00, 0x00, 0xF0, 0x00,
+ 0x01, 0xE0, 0x00, 0x03, 0xC0, 0x00, 0x07, 0x80, 0x00, 0x0F, 0x00, 0x00,
+ 0x00, 0x00, 0xFC, 0x00, 0x0F, 0xFE, 0x3C, 0x3F, 0xFE, 0x78, 0xFF, 0xFE,
+ 0xF3, 0xF8, 0x3F, 0xE7, 0xC0, 0x1F, 0xDF, 0x00, 0x1F, 0xBC, 0x00, 0x1F,
+ 0x78, 0x00, 0x3F, 0xF0, 0x00, 0x7F, 0xC0, 0x00, 0x7F, 0x80, 0x00, 0xFF,
+ 0x00, 0x01, 0xFE, 0x00, 0x03, 0xFC, 0x00, 0x07, 0xF8, 0x00, 0x0F, 0xF0,
+ 0x00, 0x1F, 0xE0, 0x00, 0x3F, 0xE0, 0x00, 0xFB, 0xC0, 0x01, 0xF7, 0x80,
+ 0x03, 0xEF, 0x80, 0x0F, 0xCF, 0x80, 0x3F, 0x9F, 0xC1, 0xFF, 0x1F, 0xFF,
+ 0xDE, 0x1F, 0xFF, 0x3C, 0x1F, 0xFC, 0x78, 0x0F, 0xE0, 0xF0, 0x00, 0x01,
+ 0xE0, 0x00, 0x03, 0xC0, 0x00, 0x07, 0x80, 0x00, 0x0F, 0x00, 0x00, 0x1E,
+ 0x00, 0x00, 0x3C, 0x00, 0x00, 0x78, 0x00, 0x00, 0xF0, 0x00, 0x01, 0xE0,
+ 0x00, 0x7F, 0xE3, 0xFF, 0xCF, 0xFF, 0xBF, 0xFF, 0xF8, 0x1F, 0xC0, 0x3F,
+ 0x00, 0x7C, 0x00, 0xF8, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80, 0x0F, 0x00,
+ 0x1E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07,
+ 0x80, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0,
+ 0x03, 0xC0, 0x00, 0x03, 0xFC, 0x03, 0xFF, 0xF0, 0xFF, 0xFF, 0x3F, 0xFF,
+ 0xE7, 0xE0, 0x3D, 0xF0, 0x00, 0xBC, 0x00, 0x07, 0x80, 0x00, 0xF0, 0x00,
+ 0x1E, 0x00, 0x03, 0xE0, 0x00, 0x3F, 0x00, 0x07, 0xFE, 0x00, 0x7F, 0xFC,
+ 0x03, 0xFF, 0xC0, 0x0F, 0xFE, 0x00, 0x1F, 0xC0, 0x00, 0xFC, 0x00, 0x07,
+ 0x80, 0x00, 0xF0, 0x00, 0x1E, 0x00, 0x03, 0xE0, 0x00, 0xFF, 0xC0, 0x7E,
+ 0xFF, 0xFF, 0xDF, 0xFF, 0xF1, 0xFF, 0xF8, 0x07, 0xFC, 0x00, 0x1E, 0x00,
+ 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1E, 0x00, 0x1E, 0x00,
+ 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00,
+ 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00,
+ 0x1E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x0F, 0xFF, 0x0F, 0xFF,
+ 0x07, 0xFF, 0x01, 0xFF, 0x00, 0x00, 0x03, 0xC0, 0x00, 0xFF, 0x00, 0x03,
+ 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC,
+ 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00,
+ 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F,
+ 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x1F, 0xF8,
+ 0x00, 0x7D, 0xE0, 0x03, 0xF7, 0xC0, 0x1F, 0xDF, 0x81, 0xFF, 0x3F, 0xFF,
+ 0xBC, 0x7F, 0xFC, 0xF0, 0xFF, 0xE3, 0xC0, 0xFE, 0x00, 0xF0, 0x00, 0x07,
+ 0xBC, 0x00, 0x07, 0x9E, 0x00, 0x03, 0xCF, 0x80, 0x03, 0xE3, 0xC0, 0x01,
+ 0xE1, 0xE0, 0x00, 0xF0, 0xF8, 0x00, 0xF8, 0x3C, 0x00, 0x78, 0x1E, 0x00,
+ 0x3C, 0x07, 0x80, 0x3C, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x1F, 0x00, 0x78,
+ 0x0F, 0x00, 0x3C, 0x07, 0x80, 0x1F, 0x07, 0xC0, 0x07, 0x83, 0xC0, 0x03,
+ 0xC1, 0xE0, 0x01, 0xF1, 0xF0, 0x00, 0x78, 0xF0, 0x00, 0x3E, 0xF8, 0x00,
+ 0x0F, 0x78, 0x00, 0x07, 0xBC, 0x00, 0x03, 0xFE, 0x00, 0x00, 0xFE, 0x00,
+ 0x00, 0x7F, 0x00, 0x00, 0x3F, 0x80, 0x00, 0xF0, 0x03, 0xF8, 0x01, 0xFF,
+ 0x00, 0x7F, 0x00, 0x7D, 0xE0, 0x0F, 0xE0, 0x0F, 0x3C, 0x01, 0xFC, 0x01,
+ 0xE7, 0x80, 0x7F, 0xC0, 0x3C, 0xF8, 0x0F, 0x78, 0x0F, 0x8F, 0x01, 0xEF,
+ 0x01, 0xE1, 0xE0, 0x3D, 0xE0, 0x3C, 0x3C, 0x0F, 0x9E, 0x07, 0x83, 0xC1,
+ 0xE3, 0xC1, 0xF0, 0x78, 0x3C, 0x78, 0x3C, 0x0F, 0x07, 0x8F, 0x07, 0x81,
+ 0xE1, 0xE0, 0xF0, 0xF0, 0x1E, 0x3C, 0x1E, 0x3C, 0x03, 0xC7, 0x83, 0xC7,
+ 0x80, 0x78, 0xF0, 0x78, 0xF0, 0x0F, 0x3C, 0x07, 0x9E, 0x00, 0xF7, 0x80,
+ 0xF7, 0x80, 0x1E, 0xF0, 0x1E, 0xF0, 0x03, 0xFE, 0x03, 0xFE, 0x00, 0x7F,
+ 0x80, 0x3F, 0xC0, 0x07, 0xF0, 0x07, 0xF0, 0x00, 0xFE, 0x00, 0xFE, 0x00,
+ 0x1F, 0xC0, 0x1F, 0xC0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0x3E, 0x00, 0x3E,
+ 0x00, 0x7C, 0x00, 0x0F, 0x9F, 0x00, 0x0F, 0x87, 0xC0, 0x0F, 0x81, 0xF0,
+ 0x0F, 0x80, 0xF8, 0x07, 0xC0, 0x3E, 0x07, 0xC0, 0x0F, 0x87, 0xC0, 0x03,
+ 0xE7, 0xC0, 0x01, 0xF3, 0xE0, 0x00, 0x7F, 0xE0, 0x00, 0x1F, 0xE0, 0x00,
+ 0x07, 0xE0, 0x00, 0x03, 0xF0, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFE, 0x00,
+ 0x01, 0xFF, 0x80, 0x01, 0xF7, 0xC0, 0x01, 0xF1, 0xF0, 0x00, 0xF8, 0x7C,
+ 0x00, 0xF8, 0x3E, 0x00, 0xF8, 0x0F, 0x80, 0xF8, 0x03, 0xE0, 0x7C, 0x00,
+ 0xF8, 0x7C, 0x00, 0x7C, 0x7C, 0x00, 0x1F, 0x7C, 0x00, 0x07, 0xC0, 0xF8,
+ 0x00, 0x0F, 0xBC, 0x00, 0x07, 0x9E, 0x00, 0x03, 0xCF, 0x80, 0x03, 0xE3,
+ 0xC0, 0x01, 0xE1, 0xF0, 0x01, 0xF0, 0x78, 0x00, 0xF0, 0x3C, 0x00, 0x78,
+ 0x1F, 0x00, 0x7C, 0x07, 0x80, 0x3C, 0x03, 0xE0, 0x3E, 0x00, 0xF0, 0x1E,
+ 0x00, 0x78, 0x0F, 0x00, 0x3E, 0x0F, 0x80, 0x0F, 0x07, 0x80, 0x07, 0xC7,
+ 0xC0, 0x01, 0xE3, 0xC0, 0x00, 0xF1, 0xE0, 0x00, 0x7D, 0xE0, 0x00, 0x1E,
+ 0xF0, 0x00, 0x0F, 0xF8, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFC, 0x00, 0x00,
+ 0xFC, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0x0F, 0x80, 0x00, 0x07, 0x80, 0x00, 0x07, 0xC0, 0x00, 0x07, 0xE0, 0x00,
+ 0x07, 0xE0, 0x00, 0x3F, 0xF0, 0x00, 0x1F, 0xF0, 0x00, 0x0F, 0xF0, 0x00,
+ 0x07, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xDF, 0xFF, 0xFE,
+ 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF8, 0x00, 0x1F, 0x80, 0x00,
+ 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00,
+ 0x0F, 0xC0, 0x00, 0xFC, 0x00, 0x07, 0xC0, 0x00, 0x7C, 0x00, 0x07, 0xC0,
+ 0x00, 0x7C, 0x00, 0x07, 0xE0, 0x00, 0x7E, 0x00, 0x07, 0xE0, 0x00, 0x3E,
+ 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xC0, 0x00, 0x0F, 0xC0, 0x1F, 0xF0, 0x0F, 0xFC, 0x03, 0xFF, 0x01, 0xF8,
+ 0x00, 0x7C, 0x00, 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00,
+ 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07,
+ 0x80, 0x01, 0xE0, 0x00, 0xF8, 0x00, 0x7C, 0x03, 0xFF, 0x00, 0xFF, 0x00,
+ 0x3F, 0xC0, 0x0F, 0xF8, 0x00, 0x3F, 0x00, 0x03, 0xE0, 0x00, 0xF8, 0x00,
+ 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07,
+ 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0,
+ 0x00, 0x7C, 0x00, 0x1F, 0x80, 0x03, 0xFF, 0x00, 0xFF, 0xC0, 0x1F, 0xF0,
+ 0x01, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xF0, 0xFE, 0x00, 0x3F, 0xE0, 0x0F, 0xFC, 0x03, 0xFF, 0x00, 0x07,
+ 0xE0, 0x00, 0xF8, 0x00, 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x78,
+ 0x00, 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00,
+ 0x07, 0x80, 0x01, 0xE0, 0x00, 0x7C, 0x00, 0x0F, 0x80, 0x03, 0xFF, 0x00,
+ 0x3F, 0xC0, 0x0F, 0xF0, 0x07, 0xFC, 0x03, 0xF0, 0x01, 0xF0, 0x00, 0x7C,
+ 0x00, 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00,
+ 0x07, 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x80, 0x01,
+ 0xE0, 0x00, 0xF8, 0x00, 0x7E, 0x03, 0xFF, 0x00, 0xFF, 0xC0, 0x3F, 0xE0,
+ 0x0F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x47, 0xFF,
+ 0x80, 0x0E, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF,
+ 0xFB, 0x80, 0x0F, 0xFF, 0x18, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0xF8, 0x00, 0x07, 0xFF, 0xC0, 0x03, 0xFF, 0xF8, 0x03, 0xFF,
+ 0xFF, 0x00, 0xFC, 0x07, 0xC0, 0x7C, 0x00, 0x70, 0x3E, 0x00, 0x0C, 0x0F,
+ 0x00, 0x01, 0x07, 0xC0, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00, 0x00,
+ 0x3E, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x1F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF,
+ 0x80, 0x3C, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0x3C, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xE0, 0x0F,
+ 0xFF, 0xF8, 0x00, 0x3E, 0x00, 0x00, 0x07, 0x80, 0x00, 0x01, 0xE0, 0x00,
+ 0x00, 0x78, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x40, 0xF8,
+ 0x00, 0x30, 0x1F, 0x80, 0x1C, 0x03, 0xF8, 0x1F, 0x00, 0x7F, 0xFF, 0xC0,
+ 0x0F, 0xFF, 0xE0, 0x01, 0xFF, 0xF0, 0x00, 0x0F, 0xE0, 0x3C, 0xF3, 0xCF,
+ 0x3C, 0xE7, 0x9C, 0x73, 0xCE, 0x00, 0x00, 0x0F, 0xF0, 0x03, 0xFF, 0x00,
+ 0x7F, 0xF0, 0x07, 0xFF, 0x00, 0xF8, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00,
+ 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x0F, 0xFF, 0xE0, 0xFF, 0xFE,
+ 0x0F, 0xFF, 0xE0, 0xFF, 0xFE, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0,
+ 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F,
+ 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00,
+ 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00,
+ 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00,
+ 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x01, 0xF0,
+ 0x00, 0x3E, 0x00, 0xFF, 0xE0, 0x0F, 0xFC, 0x00, 0xFF, 0x80, 0x0F, 0xF0,
+ 0x00, 0x3C, 0x1E, 0x78, 0x3C, 0xF0, 0x79, 0xE0, 0xF3, 0xC1, 0xE7, 0x03,
+ 0x9E, 0x0F, 0x38, 0x1C, 0x70, 0x39, 0xE0, 0xF3, 0x81, 0xC0, 0xF0, 0x00,
+ 0xF0, 0x00, 0xFF, 0x00, 0x0F, 0x00, 0x0F, 0xF0, 0x00, 0xF0, 0x00, 0xFF,
+ 0x00, 0x0F, 0x00, 0x0F, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0x00, 0x0F, 0x00,
+ 0x0F, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00,
+ 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00,
+ 0x0F, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0,
+ 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F,
+ 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00,
+ 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00,
+ 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F,
+ 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00,
+ 0xF0, 0x00, 0x0F, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00,
+ 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0,
+ 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00,
+ 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00,
+ 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x07, 0xE0, 0x00, 0x0E, 0x00, 0x00, 0x00,
+ 0x07, 0xFE, 0x00, 0x07, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xC0, 0x01, 0xC0,
+ 0x00, 0x00, 0x01, 0xF0, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x78, 0x1E,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x1E, 0x07, 0x80, 0x1E, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x3C, 0x03, 0x80,
+ 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x3C, 0x03,
+ 0xC0, 0x70, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xF0, 0x3C, 0x00, 0x00, 0x00,
+ 0x03, 0xC0, 0x3C, 0x0E, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x1E, 0x07, 0x83, 0xC0, 0x00, 0x00, 0x00, 0x07, 0x81,
+ 0xE0, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xF0, 0xF8, 0x78, 0x00, 0x00, 0x00,
+ 0x00, 0x3F, 0xFC, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFE, 0x0F, 0x03,
+ 0xF0, 0x00, 0x7E, 0x00, 0x7E, 0x07, 0x83, 0xFF, 0x00, 0x7F, 0xE0, 0x00,
+ 0x01, 0xC1, 0xFF, 0xE0, 0x3F, 0xFC, 0x00, 0x00, 0xF0, 0xF8, 0x7C, 0x1F,
+ 0x0F, 0x80, 0x00, 0x38, 0x3C, 0x0F, 0x07, 0x81, 0xE0, 0x00, 0x1E, 0x0F,
+ 0x03, 0xC1, 0xE0, 0x78, 0x00, 0x07, 0x07, 0x80, 0x78, 0xF0, 0x0F, 0x00,
+ 0x03, 0x81, 0xE0, 0x1E, 0x3C, 0x03, 0xC0, 0x01, 0xE0, 0x78, 0x07, 0x8F,
+ 0x00, 0xF0, 0x00, 0x70, 0x1E, 0x01, 0xE3, 0xC0, 0x3C, 0x00, 0x3C, 0x07,
+ 0x80, 0x78, 0xF0, 0x0F, 0x00, 0x0E, 0x01, 0xE0, 0x1E, 0x3C, 0x03, 0xC0,
+ 0x07, 0x80, 0x78, 0x07, 0x8F, 0x00, 0xF0, 0x01, 0xC0, 0x0F, 0x03, 0xC1,
+ 0xE0, 0x78, 0x00, 0xE0, 0x03, 0xC0, 0xF0, 0x78, 0x1E, 0x00, 0x78, 0x00,
+ 0xF8, 0x78, 0x1F, 0x0F, 0x00, 0x1C, 0x00, 0x1F, 0xFE, 0x03, 0xFF, 0xC0,
+ 0x0F, 0x00, 0x03, 0xFF, 0x00, 0x7F, 0xE0, 0x03, 0x80, 0x00, 0x3F, 0x00,
+ 0x07, 0xE0, 0x00, 0x20, 0x0C, 0x03, 0x80, 0xF0, 0x3C, 0x0F, 0x03, 0xC1,
+ 0xF0, 0x7C, 0x1F, 0x03, 0xC0, 0x7C, 0x07, 0xC0, 0x7C, 0x03, 0xC0, 0x3C,
+ 0x03, 0xC0, 0x3C, 0x03, 0x80, 0x30, 0x02, 0x1C, 0xF3, 0x8E, 0x79, 0xCF,
+ 0x3C, 0xF3, 0xCF, 0x00, 0x3C, 0xF3, 0xCF, 0x3D, 0xE7, 0x9C, 0x73, 0xCE,
+ 0x00, 0x1C, 0x0E, 0x78, 0x3C, 0xE0, 0x71, 0xC0, 0xE7, 0x83, 0xCE, 0x07,
+ 0x3C, 0x1E, 0x78, 0x3C, 0xF0, 0x79, 0xE0, 0xF3, 0xC1, 0xE0, 0x3C, 0x1E,
+ 0x78, 0x3C, 0xF0, 0x79, 0xE0, 0xF3, 0xC1, 0xE7, 0x03, 0x9E, 0x0F, 0x38,
+ 0x1C, 0x70, 0x39, 0xE0, 0xF3, 0x81, 0xC0, 0x0F, 0xC0, 0x7F, 0x83, 0xFF,
+ 0x1F, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF7, 0xFF, 0x8F, 0xFC, 0x1F, 0xE0, 0x3F, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xE0, 0x1F, 0xFF, 0xF3, 0xE0, 0x7C, 0x1C,
+ 0x07, 0xE1, 0xF8, 0x38, 0x0E, 0xE3, 0x70, 0x70, 0x1C, 0xCC, 0xE0, 0xE0,
+ 0x39, 0xF9, 0xC1, 0xC0, 0x71, 0xE3, 0x83, 0x80, 0xE1, 0xC7, 0x07, 0x01,
+ 0xC3, 0x0E, 0x0E, 0x03, 0x80, 0x1C, 0x1C, 0x07, 0x00, 0x38, 0x38, 0x0E,
+ 0x00, 0x70, 0x70, 0x1C, 0x00, 0xE0, 0x80, 0x18, 0x03, 0x80, 0x78, 0x07,
+ 0x80, 0x78, 0x07, 0x80, 0x7C, 0x07, 0xC0, 0x7C, 0x07, 0x81, 0xF0, 0x7C,
+ 0x1F, 0x07, 0x81, 0xE0, 0x78, 0x1E, 0x03, 0x80, 0x60, 0x08, 0x00, 0x00,
+ 0x00, 0x3E, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x00,
+ 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x00, 0x03, 0xE1, 0xF7, 0xC3, 0xEF, 0x87,
+ 0xDF, 0x0F, 0xBE, 0x1F, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x01, 0xE0, 0x00,
+ 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x7C, 0x7C, 0x00,
+ 0x00, 0xF1, 0xFC, 0x00, 0x03, 0xC3, 0xF8, 0x00, 0x0F, 0x07, 0xF0, 0x00,
+ 0x1C, 0x1F, 0xF0, 0x00, 0x00, 0x3D, 0xE0, 0x00, 0x00, 0xFB, 0xE0, 0x00,
+ 0x01, 0xE3, 0xC0, 0x00, 0x03, 0xC7, 0x80, 0x00, 0x0F, 0x8F, 0x80, 0x00,
+ 0x1E, 0x0F, 0x00, 0x00, 0x3C, 0x1E, 0x00, 0x00, 0xF0, 0x1E, 0x00, 0x01,
+ 0xE0, 0x3C, 0x00, 0x07, 0xC0, 0x7C, 0x00, 0x0F, 0x00, 0x78, 0x00, 0x1E,
+ 0x00, 0xF0, 0x00, 0x7C, 0x01, 0xF0, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xE0,
+ 0x03, 0xE0, 0x07, 0x80, 0x03, 0xC0, 0x0F, 0xFF, 0xFF, 0x80, 0x3F, 0xFF,
+ 0xFF, 0x80, 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFE, 0x03, 0xE0, 0x00,
+ 0x3E, 0x07, 0x80, 0x00, 0x3C, 0x1F, 0x00, 0x00, 0x7C, 0x3C, 0x00, 0x00,
+ 0x78, 0x78, 0x00, 0x00, 0xF1, 0xF0, 0x00, 0x01, 0xF3, 0xC0, 0x00, 0x01,
+ 0xE7, 0x80, 0x00, 0x03, 0xDE, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x1F, 0xF0,
+ 0x01, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x7F, 0xFF, 0x03, 0xF0, 0x0C, 0x0F,
+ 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x07, 0x80, 0x00, 0x1E, 0x00,
+ 0x00, 0x78, 0x00, 0x01, 0xE0, 0x00, 0x07, 0x80, 0x00, 0x1E, 0x00, 0x00,
+ 0x78, 0x00, 0x01, 0xE0, 0x00, 0x07, 0x80, 0x03, 0xFF, 0xFF, 0x0F, 0xFF,
+ 0xFC, 0x3F, 0xFF, 0xF0, 0xFF, 0xFF, 0xC0, 0x1E, 0x00, 0x00, 0x78, 0x00,
+ 0x01, 0xE0, 0x00, 0x07, 0x80, 0x00, 0x1E, 0x00, 0x00, 0x78, 0x00, 0x01,
+ 0xE0, 0x00, 0x07, 0x80, 0x00, 0x1E, 0x00, 0x00, 0x78, 0x00, 0x3F, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x20, 0x00,
+ 0x01, 0x1C, 0x00, 0x00, 0xEF, 0x80, 0x00, 0x7D, 0xF0, 0xFC, 0x3E, 0x3E,
+ 0xFF, 0xDF, 0x07, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xC0, 0x1F, 0x87, 0xE0,
+ 0x0F, 0x80, 0x7C, 0x03, 0xC0, 0x0F, 0x01, 0xF0, 0x03, 0xE0, 0x78, 0x00,
+ 0x78, 0x1E, 0x00, 0x1E, 0x07, 0x80, 0x07, 0x81, 0xE0, 0x01, 0xE0, 0x7C,
+ 0x00, 0xF8, 0x0F, 0x00, 0x3C, 0x03, 0xE0, 0x1F, 0x00, 0x7E, 0x1F, 0x80,
+ 0x3F, 0xFF, 0xF0, 0x1F, 0xFF, 0xFE, 0x0F, 0xBF, 0xF7, 0xC7, 0xC3, 0xF0,
+ 0xFB, 0xE0, 0x00, 0x1F, 0x70, 0x00, 0x03, 0x88, 0x00, 0x00, 0x40, 0xF8,
+ 0x00, 0x07, 0xDE, 0x00, 0x01, 0xE7, 0xC0, 0x00, 0xF8, 0xF8, 0x00, 0x7C,
+ 0x1E, 0x00, 0x1E, 0x07, 0xC0, 0x0F, 0x80, 0xF0, 0x03, 0xC0, 0x3E, 0x01,
+ 0xF0, 0x07, 0x80, 0x78, 0x01, 0xF0, 0x3E, 0x00, 0x3C, 0x0F, 0x00, 0x0F,
+ 0x87, 0x80, 0x01, 0xF3, 0xE0, 0x1F, 0xFC, 0xFF, 0xE7, 0xFF, 0xFF, 0xF9,
+ 0xFF, 0xFF, 0xFE, 0x00, 0x7F, 0x80, 0x00, 0x0F, 0xC0, 0x00, 0x03, 0xF0,
+ 0x00, 0x00, 0x78, 0x00, 0x00, 0x1E, 0x00, 0x1F, 0xFF, 0xFF, 0xE7, 0xFF,
+ 0xFF, 0xF9, 0xFF, 0xFF, 0xFE, 0x00, 0x1E, 0x00, 0x00, 0x07, 0x80, 0x00,
+ 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x07, 0x80,
+ 0x00, 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x07,
+ 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
+ 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFE,
+ 0x00, 0xFF, 0xF0, 0x7F, 0xFE, 0x0F, 0x83, 0xC3, 0xE0, 0x08, 0x78, 0x00,
+ 0x0F, 0x00, 0x01, 0xE0, 0x00, 0x3E, 0x00, 0x03, 0xE0, 0x00, 0x7E, 0x00,
+ 0x07, 0xF0, 0x01, 0xFF, 0x00, 0x7B, 0xF8, 0x1E, 0x1F, 0x83, 0xC1, 0xFC,
+ 0xF0, 0x0F, 0xDE, 0x00, 0xFB, 0xC0, 0x0F, 0xFC, 0x00, 0xFF, 0x80, 0x1E,
+ 0xF8, 0x03, 0xCF, 0xC0, 0x78, 0xFC, 0x1E, 0x0F, 0xE7, 0xC0, 0x7F, 0xF0,
+ 0x03, 0xF8, 0x00, 0x3F, 0x00, 0x03, 0xF0, 0x00, 0x1E, 0x00, 0x03, 0xE0,
+ 0x00, 0x3C, 0x00, 0x07, 0x80, 0x00, 0xF0, 0x80, 0x3E, 0x1E, 0x0F, 0x83,
+ 0xFF, 0xE0, 0x7F, 0xF8, 0x03, 0xFC, 0x00, 0xF8, 0x7F, 0xE1, 0xFF, 0x87,
+ 0xFE, 0x1F, 0xF8, 0x7C, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xC0,
+ 0x00, 0x0F, 0x80, 0x7C, 0x00, 0x07, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00,
+ 0x70, 0x01, 0x80, 0x00, 0x06, 0x00, 0xC0, 0x3F, 0xC0, 0xC0, 0x60, 0x3F,
+ 0xFC, 0x18, 0x38, 0x3F, 0xFF, 0x07, 0x0C, 0x1F, 0x80, 0xC0, 0xC6, 0x07,
+ 0xC0, 0x00, 0x19, 0x83, 0xE0, 0x00, 0x06, 0x60, 0xF0, 0x00, 0x01, 0xB0,
+ 0x7C, 0x00, 0x00, 0x3C, 0x1E, 0x00, 0x00, 0x0F, 0x07, 0x80, 0x00, 0x03,
+ 0xC1, 0xE0, 0x00, 0x00, 0xF0, 0x78, 0x00, 0x00, 0x3C, 0x1E, 0x00, 0x00,
+ 0x0F, 0x07, 0x80, 0x00, 0x03, 0xC1, 0xF0, 0x00, 0x00, 0xD8, 0x3C, 0x00,
+ 0x00, 0x66, 0x0F, 0x80, 0x00, 0x19, 0x81, 0xF0, 0x00, 0x06, 0x30, 0x7E,
+ 0x03, 0x03, 0x0E, 0x0F, 0xFF, 0xC1, 0xC1, 0x80, 0xFF, 0xF0, 0x60, 0x30,
+ 0x0F, 0xF0, 0x30, 0x06, 0x00, 0x00, 0x18, 0x00, 0xE0, 0x00, 0x1C, 0x00,
+ 0x1C, 0x00, 0x0E, 0x00, 0x03, 0xE0, 0x1F, 0x00, 0x00, 0x3F, 0xFF, 0x00,
+ 0x00, 0x01, 0xFE, 0x00, 0x00, 0x00, 0x20, 0x08, 0x03, 0x00, 0xC0, 0x38,
+ 0x0E, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x07,
+ 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x3C, 0x0F, 0x01, 0xF0, 0x7C,
+ 0x07, 0xC1, 0xF0, 0x1F, 0x07, 0xC0, 0x3C, 0x0F, 0x00, 0xF0, 0x3C, 0x03,
+ 0xC0, 0xF0, 0x0F, 0x03, 0xC0, 0x38, 0x0E, 0x00, 0xC0, 0x30, 0x02, 0x00,
+ 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x3C, 0x00,
+ 0x00, 0x01, 0xE0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00,
+ 0x03, 0xC0, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x07,
+ 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x00,
+ 0x0F, 0xFF, 0xC0, 0x00, 0x0F, 0x80, 0x7C, 0x00, 0x07, 0x00, 0x03, 0x80,
+ 0x03, 0x80, 0x00, 0x70, 0x01, 0x80, 0x00, 0x06, 0x00, 0xC3, 0xFF, 0x80,
+ 0xC0, 0x60, 0xFF, 0xF8, 0x18, 0x38, 0x3C, 0x0F, 0x07, 0x0C, 0x0F, 0x01,
+ 0xE0, 0xC6, 0x03, 0xC0, 0x78, 0x19, 0x80, 0xF0, 0x1E, 0x06, 0x60, 0x3C,
+ 0x07, 0x81, 0xB0, 0x0F, 0x01, 0xE0, 0x3C, 0x03, 0xC0, 0xF0, 0x0F, 0x00,
+ 0xFF, 0xF8, 0x03, 0xC0, 0x3F, 0xF0, 0x00, 0xF0, 0x0F, 0x1E, 0x00, 0x3C,
+ 0x03, 0xC3, 0xC0, 0x0F, 0x00, 0xF0, 0x78, 0x03, 0xC0, 0x3C, 0x1E, 0x00,
+ 0xD8, 0x0F, 0x07, 0xC0, 0x66, 0x03, 0xC0, 0xF0, 0x19, 0x80, 0xF0, 0x3E,
+ 0x06, 0x30, 0x3C, 0x07, 0x83, 0x0E, 0x0F, 0x01, 0xF1, 0xC1, 0x83, 0xC0,
+ 0x3C, 0x60, 0x30, 0xF0, 0x0F, 0xB0, 0x06, 0x00, 0x00, 0x18, 0x00, 0xE0,
+ 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x03, 0xE0, 0x1F, 0x00, 0x00,
+ 0x3F, 0xFF, 0x00, 0x00, 0x01, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0xC0, 0x3F, 0xE0,
+ 0xFF, 0xE3, 0xC1, 0xE7, 0x01, 0xDC, 0x01, 0xF8, 0x03, 0xF0, 0x07, 0xE0,
+ 0x0F, 0xC0, 0x1D, 0xC0, 0x73, 0xC1, 0xE3, 0xFF, 0x83, 0xFE, 0x01, 0xF0,
+ 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x78, 0x00,
+ 0x00, 0x01, 0xE0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00,
+ 0x00, 0x78, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x07, 0x80, 0x03, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFC, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x01, 0xE0,
+ 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x78, 0x00,
+ 0x00, 0x01, 0xE0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0xC3, 0xFF, 0xCF, 0xFF, 0xB0,
+ 0x1F, 0x00, 0x3C, 0x00, 0x70, 0x01, 0xC0, 0x0E, 0x00, 0x78, 0x03, 0xC0,
+ 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x03, 0xE0, 0x1E, 0x00, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xC0, 0x1F, 0xC1, 0xFF, 0xC7, 0xFF, 0x98, 0x0F, 0x00,
+ 0x1C, 0x00, 0x70, 0x03, 0x83, 0xFE, 0x0F, 0xE0, 0x3F, 0xE0, 0x07, 0x80,
+ 0x07, 0x00, 0x1C, 0x00, 0x70, 0x03, 0xF0, 0x1F, 0xFF, 0xFB, 0xFF, 0xC3,
+ 0xFC, 0x00, 0x03, 0xE0, 0xF8, 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x0F, 0x03,
+ 0xC0, 0xF0, 0x00, 0x00, 0x3E, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1E,
+ 0x00, 0x78, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x00, 0x03, 0xE1, 0xF7,
+ 0xC3, 0xEF, 0x87, 0xDF, 0x0F, 0xBE, 0x1F, 0x00, 0x00, 0xF8, 0x00, 0x00,
+ 0x01, 0xE0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x7C, 0x7C, 0x00, 0x00, 0xF1, 0xFC, 0x00, 0x03, 0xC3, 0xF8, 0x00, 0x0F,
+ 0x07, 0xF0, 0x00, 0x1C, 0x1F, 0xF0, 0x00, 0x00, 0x3D, 0xE0, 0x00, 0x00,
+ 0xFB, 0xE0, 0x00, 0x01, 0xE3, 0xC0, 0x00, 0x03, 0xC7, 0x80, 0x00, 0x0F,
+ 0x8F, 0x80, 0x00, 0x1E, 0x0F, 0x00, 0x00, 0x3C, 0x1E, 0x00, 0x00, 0xF0,
+ 0x1E, 0x00, 0x01, 0xE0, 0x3C, 0x00, 0x07, 0xC0, 0x7C, 0x00, 0x0F, 0x00,
+ 0x78, 0x00, 0x1E, 0x00, 0xF0, 0x00, 0x7C, 0x01, 0xF0, 0x00, 0xF0, 0x01,
+ 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x07, 0x80, 0x03, 0xC0, 0x0F, 0xFF, 0xFF,
+ 0x80, 0x3F, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFE,
+ 0x03, 0xE0, 0x00, 0x3E, 0x07, 0x80, 0x00, 0x3C, 0x1F, 0x00, 0x00, 0x7C,
+ 0x3C, 0x00, 0x00, 0x78, 0x78, 0x00, 0x00, 0xF1, 0xF0, 0x00, 0x01, 0xF3,
+ 0xC0, 0x00, 0x01, 0xE7, 0x80, 0x00, 0x03, 0xDE, 0x00, 0x00, 0x03, 0xC0,
+ 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x1E,
+ 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x01, 0xE7, 0xFF, 0xFF, 0xE7, 0x8F,
+ 0xFF, 0xFF, 0xCF, 0x1F, 0xFF, 0xFF, 0xBC, 0x3F, 0xFF, 0xFF, 0xF0, 0x78,
+ 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x1E, 0x00,
+ 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF,
+ 0xC0, 0x0F, 0xFF, 0xFF, 0x80, 0x1E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x00, 0x78, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00,
+ 0x03, 0xC0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xF0, 0x07,
+ 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, 0xFF, 0xC0, 0x03, 0xE0, 0x00, 0x00, 0x00,
+ 0x78, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00,
+ 0x01, 0xE7, 0x80, 0x00, 0x1E, 0x3C, 0xF0, 0x00, 0x03, 0xCF, 0x1E, 0x00,
+ 0x00, 0x7B, 0xC3, 0xC0, 0x00, 0x0F, 0x70, 0x78, 0x00, 0x01, 0xE0, 0x0F,
+ 0x00, 0x00, 0x3C, 0x01, 0xE0, 0x00, 0x07, 0x80, 0x3C, 0x00, 0x00, 0xF0,
+ 0x07, 0x80, 0x00, 0x1E, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x1E, 0x00, 0x00,
+ 0x78, 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x78, 0x00, 0x01, 0xE0, 0x0F, 0x00,
+ 0x00, 0x3C, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xFF, 0xF0, 0x07,
+ 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x1E, 0x00, 0x00, 0x78,
+ 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x78, 0x00, 0x01, 0xE0, 0x0F, 0x00, 0x00,
+ 0x3C, 0x01, 0xE0, 0x00, 0x07, 0x80, 0x3C, 0x00, 0x00, 0xF0, 0x07, 0x80,
+ 0x00, 0x1E, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x1E, 0x00, 0x00, 0x78, 0x03,
+ 0xC0, 0x00, 0x0F, 0x00, 0x78, 0x00, 0x01, 0xE0, 0x0F, 0x00, 0x00, 0x3C,
+ 0x01, 0xE0, 0x00, 0x07, 0x80, 0x3C, 0x00, 0x00, 0xF0, 0x07, 0x80, 0x00,
+ 0x1E, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x03, 0xE0, 0x3E, 0x01, 0xE0, 0x1E,
+ 0x01, 0xE7, 0x8E, 0x3C, 0xF1, 0xEF, 0x0F, 0xF0, 0x78, 0x03, 0xC0, 0x1E,
+ 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0,
+ 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03,
+ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78,
+ 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x80, 0x20, 0x06, 0x01,
+ 0x80, 0x38, 0x0E, 0x01, 0xE0, 0x78, 0x07, 0x81, 0xE0, 0x1E, 0x07, 0x80,
+ 0x78, 0x1E, 0x01, 0xF0, 0x7C, 0x07, 0xC1, 0xF0, 0x1F, 0x07, 0xC0, 0x78,
+ 0x1E, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x78, 0x1E, 0x07,
+ 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0x38, 0x0E, 0x01, 0x80, 0x60,
+ 0x08, 0x02, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0xF0, 0x00, 0x1E, 0x07,
+ 0xFF, 0xE0, 0x01, 0xE1, 0xFF, 0xFF, 0x80, 0x3C, 0x3F, 0xFF, 0xFC, 0x07,
+ 0x87, 0xF0, 0x0F, 0xE0, 0x70, 0xFC, 0x00, 0x3F, 0x00, 0x1F, 0x00, 0x01,
+ 0xF8, 0x03, 0xE0, 0x00, 0x0F, 0x80, 0x3E, 0x00, 0x00, 0x7C, 0x07, 0xC0,
+ 0x00, 0x03, 0xC0, 0x78, 0x00, 0x00, 0x3E, 0x07, 0x80, 0x00, 0x01, 0xE0,
+ 0xF8, 0x00, 0x00, 0x1E, 0x0F, 0x00, 0x00, 0x01, 0xF0, 0xF0, 0x00, 0x00,
+ 0x0F, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x0F, 0x0F, 0x00,
+ 0x00, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0xF0,
+ 0xF0, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x00, 0x00,
+ 0x1F, 0x0F, 0x80, 0x00, 0x01, 0xE0, 0x78, 0x00, 0x00, 0x1E, 0x07, 0x80,
+ 0x00, 0x03, 0xE0, 0x7C, 0x00, 0x00, 0x3C, 0x03, 0xE0, 0x00, 0x07, 0xC0,
+ 0x3E, 0x00, 0x00, 0xF8, 0x01, 0xF0, 0x00, 0x1F, 0x80, 0x0F, 0xC0, 0x03,
+ 0xF0, 0x00, 0x7F, 0x00, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x1F,
+ 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00,
+ 0x1F, 0x00, 0x00, 0x1E, 0x01, 0xFE, 0x00, 0x00, 0x78, 0x03, 0xFC, 0x00,
+ 0x00, 0xE0, 0x07, 0x38, 0x00, 0x03, 0xC0, 0x00, 0x70, 0x00, 0x07, 0x00,
+ 0x00, 0xE0, 0x00, 0x1E, 0x00, 0x01, 0xC0, 0x00, 0x78, 0x00, 0x03, 0x80,
+ 0x00, 0xE0, 0x00, 0x07, 0x00, 0x03, 0xC0, 0x00, 0x0E, 0x00, 0x07, 0x00,
+ 0x00, 0x1C, 0x00, 0x1E, 0x00, 0x00, 0x38, 0x00, 0x78, 0x00, 0x00, 0x70,
+ 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x00, 0x01, 0xC0, 0x07, 0x00,
+ 0x00, 0x03, 0x80, 0x1E, 0x0F, 0xF0, 0xFF, 0xF8, 0x78, 0x7F, 0xF9, 0xFF,
+ 0xF0, 0xE0, 0xFF, 0xFB, 0xFF, 0xE3, 0xC1, 0x80, 0xF0, 0x00, 0x07, 0x00,
+ 0x00, 0xF0, 0x00, 0x1E, 0x00, 0x00, 0xE0, 0x00, 0x78, 0x00, 0x01, 0x80,
+ 0x00, 0xE0, 0x00, 0x07, 0x00, 0x03, 0xC0, 0x00, 0x1C, 0x00, 0x07, 0x00,
+ 0x00, 0x78, 0x00, 0x1E, 0x00, 0x01, 0xE0, 0x00, 0x38, 0x00, 0x07, 0x80,
+ 0x00, 0xE0, 0x00, 0x1E, 0x00, 0x03, 0xC0, 0x00, 0xF8, 0x00, 0x07, 0x00,
+ 0x03, 0xE0, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x00, 0x38, 0x00, 0x3F, 0xFF,
+ 0x00, 0xE0, 0x00, 0x7F, 0xFE, 0x03, 0xC0, 0x00, 0xFF, 0xFC, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00,
+ 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x00, 0x1E, 0x3E, 0x00, 0x00, 0x78, 0xF0, 0x7C, 0x00, 0x03,
+ 0xE3, 0x81, 0xF0, 0x00, 0x1F, 0x1E, 0x03, 0xE0, 0x00, 0x78, 0xF0, 0x07,
+ 0xC0, 0x03, 0xE0, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x00, 0x3E, 0x00, 0x78,
+ 0x00, 0x00, 0x7C, 0x03, 0xE0, 0x00, 0x00, 0xF0, 0x1F, 0x00, 0x00, 0x03,
+ 0xE0, 0x78, 0x00, 0x00, 0x07, 0xC3, 0xE0, 0x00, 0x00, 0x0F, 0x1F, 0x00,
+ 0x00, 0x00, 0x3E, 0x78, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x00, 0x00,
+ 0xFF, 0x00, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x00,
+ 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x03,
+ 0xC0, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x0F,
+ 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00,
+ 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x03, 0xC0, 0x00,
+ 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x00, 0xF0,
+ 0x0F, 0xF8, 0x00, 0x1E, 0x07, 0xFF, 0xE0, 0x03, 0xC1, 0xFF, 0xFF, 0x80,
+ 0x78, 0x3F, 0xFF, 0xFE, 0x07, 0x07, 0xF8, 0x0F, 0xF0, 0xF0, 0xFC, 0x00,
+ 0x3F, 0x80, 0x1F, 0x80, 0x00, 0xF8, 0x03, 0xE0, 0x00, 0x07, 0xC0, 0x3E,
+ 0x00, 0x00, 0x3E, 0x07, 0xC0, 0x00, 0x01, 0xE0, 0x78, 0x00, 0x00, 0x1E,
+ 0x07, 0x80, 0x00, 0x01, 0xF0, 0x78, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00,
+ 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0xF0,
+ 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x0F,
+ 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x78, 0x00, 0x00, 0x0F, 0x07, 0x80, 0x00,
+ 0x01, 0xF0, 0x78, 0x00, 0x00, 0x1E, 0x03, 0xC0, 0x00, 0x01, 0xE0, 0x3C,
+ 0x00, 0x00, 0x3E, 0x01, 0xE0, 0x00, 0x07, 0xC0, 0x1F, 0x00, 0x00, 0x78,
+ 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x01, 0xF0, 0x00, 0x3E, 0x00,
+ 0x3E, 0x00, 0x01, 0xF0, 0x0F, 0xC0, 0x0F, 0xFF, 0x81, 0xFF, 0xF0, 0xFF,
+ 0xF8, 0x1F, 0xFF, 0x0F, 0xFF, 0x81, 0xFF, 0xF0, 0xFF, 0xF8, 0x1F, 0xFF,
+ 0x00, 0x3E, 0x00, 0x78, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x3C, 0x00,
+ 0xF0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0x03, 0xE1, 0xF7, 0xC3, 0xEF, 0x87,
+ 0xDF, 0x0F, 0xBE, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x1E,
+ 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80,
+ 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, 0x03,
+ 0xC0, 0x03, 0xC0, 0x07, 0x80, 0x0F, 0x80, 0x1F, 0xF0, 0x1F, 0xE0, 0x1F,
+ 0xC0, 0x0F, 0x80, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x1F, 0xC0, 0x00, 0x00,
+ 0x3F, 0x80, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x03,
+ 0xDE, 0x00, 0x00, 0x0F, 0xBE, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x3C,
+ 0x78, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x01, 0xE0, 0xF0, 0x00, 0x03, 0xC1,
+ 0xE0, 0x00, 0x0F, 0x01, 0xE0, 0x00, 0x1E, 0x03, 0xC0, 0x00, 0x7C, 0x07,
+ 0xC0, 0x00, 0xF0, 0x07, 0x80, 0x01, 0xE0, 0x0F, 0x00, 0x07, 0xC0, 0x1F,
+ 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x78, 0x00, 0x3C,
+ 0x00, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0xF0,
+ 0x0F, 0xFF, 0xFF, 0xE0, 0x3E, 0x00, 0x03, 0xE0, 0x78, 0x00, 0x03, 0xC1,
+ 0xF0, 0x00, 0x07, 0xC3, 0xC0, 0x00, 0x07, 0x87, 0x80, 0x00, 0x0F, 0x1F,
+ 0x00, 0x00, 0x1F, 0x3C, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x3D, 0xE0,
+ 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF8,
+ 0xFF, 0xFF, 0xF8, 0xF0, 0x01, 0xFC, 0xF0, 0x00, 0x7E, 0xF0, 0x00, 0x3E,
+ 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x1E,
+ 0xF0, 0x00, 0x3E, 0xF0, 0x00, 0x7C, 0xF0, 0x01, 0xFC, 0xFF, 0xFF, 0xF8,
+ 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF8, 0xF0, 0x00, 0xFC,
+ 0xF0, 0x00, 0x3E, 0xF0, 0x00, 0x1E, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F,
+ 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F,
+ 0xF0, 0x00, 0x1F, 0xF0, 0x00, 0x3E, 0xF0, 0x00, 0xFE, 0xFF, 0xFF, 0xFC,
+ 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x78, 0x00,
+ 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C,
+ 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00,
+ 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0,
+ 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00,
+ 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00,
+ 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07,
+ 0x80, 0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x1F, 0xC0, 0x00, 0x00,
+ 0x3F, 0x80, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x01, 0xEF, 0x00, 0x00, 0x03,
+ 0xDE, 0x00, 0x00, 0x0F, 0xBE, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x3C,
+ 0x78, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x01, 0xE0, 0xF0, 0x00, 0x03, 0xC1,
+ 0xE0, 0x00, 0x0F, 0x01, 0xE0, 0x00, 0x1E, 0x03, 0xC0, 0x00, 0x7C, 0x07,
+ 0xC0, 0x00, 0xF0, 0x07, 0x80, 0x01, 0xE0, 0x0F, 0x00, 0x07, 0xC0, 0x1F,
+ 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x78, 0x00, 0x3C,
+ 0x00, 0xF0, 0x00, 0x78, 0x03, 0xE0, 0x00, 0xF8, 0x07, 0x80, 0x00, 0xF0,
+ 0x0F, 0x00, 0x01, 0xE0, 0x3E, 0x00, 0x01, 0xE0, 0x78, 0x00, 0x03, 0xC1,
+ 0xF0, 0x00, 0x07, 0xC3, 0xC0, 0x00, 0x07, 0x87, 0x80, 0x00, 0x0F, 0x1F,
+ 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xFD, 0xFF,
+ 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00,
+ 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xBF, 0xFF, 0xFE, 0xFF,
+ 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00,
+ 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03,
+ 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0,
+ 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x7F, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xE7,
+ 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0xF8, 0x00, 0x00,
+ 0x1F, 0x80, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x07, 0xC0,
+ 0x00, 0x00, 0xF8, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x01, 0xF0, 0x00, 0x00,
+ 0x3E, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x1F, 0x00,
+ 0x00, 0x01, 0xF0, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00,
+ 0xF8, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x3E, 0x00,
+ 0x00, 0x07, 0xC0, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x03,
+ 0xF0, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00,
+ 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F,
+ 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF,
+ 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00,
+ 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00,
+ 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC,
+ 0x00, 0x00, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01,
+ 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x07, 0xF0, 0x0F, 0xE0, 0x0F,
+ 0xC0, 0x03, 0xF0, 0x1F, 0x80, 0x01, 0xF8, 0x3F, 0x00, 0x00, 0x7C, 0x3E,
+ 0x00, 0x00, 0x7C, 0x7C, 0x00, 0x00, 0x3C, 0x78, 0x00, 0x00, 0x1E, 0x78,
+ 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0xF0, 0x00, 0x00, 0x0F, 0xF0,
+ 0x00, 0x00, 0x0F, 0xF0, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0x0F, 0xF0,
+ 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0,
+ 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0x78,
+ 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x7C,
+ 0x00, 0x00, 0x3C, 0x3E, 0x00, 0x00, 0x7C, 0x3E, 0x00, 0x00, 0x7C, 0x1F,
+ 0x80, 0x01, 0xF8, 0x0F, 0xC0, 0x03, 0xF0, 0x07, 0xF0, 0x0F, 0xE0, 0x03,
+ 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00,
+ 0x0F, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0x9E,
+ 0x00, 0x07, 0xE3, 0xC0, 0x01, 0xF8, 0x78, 0x00, 0x7E, 0x0F, 0x00, 0x1F,
+ 0x81, 0xE0, 0x07, 0xE0, 0x3C, 0x01, 0xF8, 0x07, 0x80, 0x7E, 0x00, 0xF0,
+ 0x1F, 0x80, 0x1E, 0x07, 0xE0, 0x03, 0xC3, 0xF0, 0x00, 0x78, 0xFC, 0x00,
+ 0x0F, 0x3F, 0x00, 0x01, 0xEF, 0xC0, 0x00, 0x3F, 0xF0, 0x00, 0x07, 0xFC,
+ 0x00, 0x00, 0xFF, 0x80, 0x00, 0x1F, 0xF8, 0x00, 0x03, 0xDF, 0x80, 0x00,
+ 0x79, 0xF8, 0x00, 0x0F, 0x1F, 0x80, 0x01, 0xE0, 0xF8, 0x00, 0x3C, 0x0F,
+ 0x80, 0x07, 0x80, 0xF8, 0x00, 0xF0, 0x0F, 0x80, 0x1E, 0x00, 0xF8, 0x03,
+ 0xC0, 0x0F, 0x80, 0x78, 0x00, 0xF8, 0x0F, 0x00, 0x0F, 0x81, 0xE0, 0x00,
+ 0xF8, 0x3C, 0x00, 0x0F, 0x87, 0x80, 0x00, 0xF8, 0xF0, 0x00, 0x0F, 0x9E,
+ 0x00, 0x00, 0xFC, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x1F, 0xC0, 0x00, 0x00,
+ 0x3F, 0x80, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x01, 0xEF, 0x00, 0x00, 0x03,
+ 0xDE, 0x00, 0x00, 0x0F, 0xBE, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x3C,
+ 0x78, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x01, 0xE0, 0xF0, 0x00, 0x03, 0xC1,
+ 0xE0, 0x00, 0x0F, 0x01, 0xE0, 0x00, 0x1E, 0x03, 0xC0, 0x00, 0x7C, 0x07,
+ 0xC0, 0x00, 0xF0, 0x07, 0x80, 0x01, 0xE0, 0x0F, 0x00, 0x07, 0xC0, 0x1F,
+ 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x78, 0x00, 0x3C,
+ 0x00, 0xF0, 0x00, 0x78, 0x03, 0xE0, 0x00, 0xF8, 0x07, 0x80, 0x00, 0xF0,
+ 0x0F, 0x00, 0x01, 0xE0, 0x3E, 0x00, 0x03, 0xE0, 0x78, 0x00, 0x03, 0xC1,
+ 0xF0, 0x00, 0x07, 0xC3, 0xC0, 0x00, 0x07, 0x87, 0x80, 0x00, 0x0F, 0x1F,
+ 0x00, 0x00, 0x1F, 0x3C, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x3D, 0xE0,
+ 0x00, 0x00, 0x3C, 0xFE, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFC,
+ 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x1F, 0xFE, 0xF0,
+ 0x00, 0x7B, 0xFD, 0xE0, 0x00, 0xF7, 0xFB, 0xE0, 0x03, 0xEF, 0xF3, 0xC0,
+ 0x07, 0x9F, 0xE7, 0x80, 0x0F, 0x3F, 0xC7, 0x80, 0x3C, 0x7F, 0x8F, 0x00,
+ 0x78, 0xFF, 0x1F, 0x01, 0xF1, 0xFE, 0x1E, 0x03, 0xC3, 0xFC, 0x3C, 0x07,
+ 0x87, 0xF8, 0x3C, 0x1E, 0x0F, 0xF0, 0x78, 0x3C, 0x1F, 0xE0, 0xF8, 0xF8,
+ 0x3F, 0xC0, 0xF1, 0xE0, 0x7F, 0x81, 0xE3, 0xC0, 0xFF, 0x01, 0xEF, 0x01,
+ 0xFE, 0x03, 0xDE, 0x03, 0xFC, 0x07, 0xFC, 0x07, 0xF8, 0x07, 0xF0, 0x0F,
+ 0xF0, 0x0F, 0xE0, 0x1F, 0xE0, 0x1F, 0xC0, 0x3F, 0xC0, 0x1F, 0x00, 0x7F,
+ 0x80, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0xFE, 0x00, 0x00, 0x03, 0xFC,
+ 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x1F, 0xE0,
+ 0x00, 0x00, 0x3C, 0xFC, 0x00, 0x03, 0xFF, 0x80, 0x00, 0xFF, 0xE0, 0x00,
+ 0x3F, 0xFC, 0x00, 0x0F, 0xFF, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0xFF, 0x78,
+ 0x00, 0x3F, 0xDF, 0x00, 0x0F, 0xF3, 0xE0, 0x03, 0xFC, 0x78, 0x00, 0xFF,
+ 0x1F, 0x00, 0x3F, 0xC3, 0xC0, 0x0F, 0xF0, 0xF8, 0x03, 0xFC, 0x1E, 0x00,
+ 0xFF, 0x07, 0xC0, 0x3F, 0xC0, 0xF0, 0x0F, 0xF0, 0x3E, 0x03, 0xFC, 0x07,
+ 0xC0, 0xFF, 0x00, 0xF0, 0x3F, 0xC0, 0x3E, 0x0F, 0xF0, 0x07, 0x83, 0xFC,
+ 0x01, 0xF0, 0xFF, 0x00, 0x3C, 0x3F, 0xC0, 0x0F, 0x8F, 0xF0, 0x01, 0xE3,
+ 0xFC, 0x00, 0x7C, 0xFF, 0x00, 0x0F, 0xBF, 0xC0, 0x01, 0xEF, 0xF0, 0x00,
+ 0x7F, 0xFC, 0x00, 0x0F, 0xFF, 0x00, 0x03, 0xFF, 0xC0, 0x00, 0x7F, 0xF0,
+ 0x00, 0x1F, 0xFC, 0x00, 0x03, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x87, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xE1, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0x80,
+ 0x03, 0xFF, 0xFF, 0xC0, 0x07, 0xF0, 0x0F, 0xE0, 0x0F, 0xC0, 0x03, 0xF0,
+ 0x1F, 0x80, 0x01, 0xF8, 0x3E, 0x00, 0x00, 0x7C, 0x3E, 0x00, 0x00, 0x7C,
+ 0x7C, 0x00, 0x00, 0x3C, 0x78, 0x00, 0x00, 0x3E, 0x78, 0x00, 0x00, 0x1E,
+ 0x78, 0x00, 0x00, 0x1E, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F,
+ 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F,
+ 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F,
+ 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0x78, 0x00, 0x00, 0x1E,
+ 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x7C, 0x00, 0x00, 0x3C,
+ 0x3E, 0x00, 0x00, 0x7C, 0x3E, 0x00, 0x00, 0x7C, 0x1F, 0x80, 0x01, 0xF8,
+ 0x0F, 0xC0, 0x03, 0xF0, 0x07, 0xF0, 0x0F, 0xE0, 0x03, 0xFF, 0xFF, 0xC0,
+ 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x0F, 0xF0, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F,
+ 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00,
+ 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00,
+ 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF,
+ 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00,
+ 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00,
+ 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC,
+ 0x00, 0x00, 0xF0, 0xFF, 0xFE, 0x03, 0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x3F,
+ 0xFF, 0xFC, 0xF0, 0x03, 0xFB, 0xC0, 0x03, 0xEF, 0x00, 0x07, 0xBC, 0x00,
+ 0x1F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F,
+ 0xF0, 0x00, 0x3F, 0xC0, 0x01, 0xFF, 0x00, 0x07, 0xBC, 0x00, 0x3E, 0xF0,
+ 0x03, 0xFB, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x3F, 0xFF, 0xE0, 0xFF, 0xFE,
+ 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03,
+ 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0,
+ 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00,
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFC, 0x00, 0x01, 0xF8, 0x00, 0x03, 0xF0, 0x00, 0x07, 0xE0, 0x00, 0x0F,
+ 0xC0, 0x00, 0x1F, 0x80, 0x00, 0x3E, 0x00, 0x00, 0x7C, 0x00, 0x00, 0xF8,
+ 0x00, 0x01, 0xF0, 0x00, 0x03, 0xE0, 0x00, 0x07, 0xC0, 0x00, 0x1F, 0x00,
+ 0x00, 0xF8, 0x00, 0x07, 0xC0, 0x00, 0x3E, 0x00, 0x01, 0xF8, 0x00, 0x07,
+ 0xC0, 0x00, 0x3E, 0x00, 0x01, 0xF0, 0x00, 0x0F, 0x80, 0x00, 0x3E, 0x00,
+ 0x01, 0xF0, 0x00, 0x0F, 0x80, 0x00, 0x7C, 0x00, 0x03, 0xE0, 0x00, 0x0F,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F,
+ 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00,
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xF8, 0x00,
+ 0x01, 0xF7, 0xC0, 0x00, 0x3E, 0x3E, 0x00, 0x07, 0xC3, 0xE0, 0x00, 0x7C,
+ 0x1F, 0x00, 0x0F, 0x80, 0xF8, 0x01, 0xF0, 0x0F, 0x80, 0x1F, 0x00, 0x7C,
+ 0x03, 0xE0, 0x03, 0xE0, 0x7C, 0x00, 0x3E, 0x07, 0xC0, 0x01, 0xF0, 0xF8,
+ 0x00, 0x0F, 0x9F, 0x00, 0x00, 0xF9, 0xF0, 0x00, 0x07, 0xFE, 0x00, 0x00,
+ 0x3F, 0xC0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F,
+ 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF,
+ 0xC0, 0x0F, 0xFF, 0xFF, 0xF0, 0x1F, 0xE3, 0xC7, 0xF8, 0x3F, 0x83, 0xC1,
+ 0xFC, 0x3E, 0x03, 0xC0, 0x7C, 0x7C, 0x03, 0xC0, 0x3E, 0x78, 0x03, 0xC0,
+ 0x1E, 0xF8, 0x03, 0xC0, 0x1F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0,
+ 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0,
+ 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF8, 0x03, 0xC0, 0x1F, 0x78, 0x03, 0xC0,
+ 0x1E, 0x7C, 0x03, 0xC0, 0x3E, 0x3E, 0x03, 0xC0, 0x7C, 0x3F, 0x83, 0xC1,
+ 0xFC, 0x1F, 0xE3, 0xC7, 0xF8, 0x0F, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF,
+ 0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x3E, 0x00, 0x01, 0xF0, 0xF8, 0x00, 0x1F, 0x03, 0xC0, 0x01, 0xF0,
+ 0x1F, 0x00, 0x0F, 0x80, 0x7C, 0x00, 0xF8, 0x01, 0xE0, 0x0F, 0x80, 0x0F,
+ 0x80, 0x7C, 0x00, 0x3E, 0x07, 0xC0, 0x00, 0xF0, 0x7C, 0x00, 0x07, 0xC3,
+ 0xE0, 0x00, 0x1F, 0x3E, 0x00, 0x00, 0xFB, 0xE0, 0x00, 0x03, 0xFF, 0x00,
+ 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00,
+ 0x0F, 0xC0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x7F,
+ 0xE0, 0x00, 0x07, 0xDF, 0x00, 0x00, 0x3C, 0x7C, 0x00, 0x03, 0xE1, 0xF0,
+ 0x00, 0x3E, 0x0F, 0x80, 0x03, 0xE0, 0x3E, 0x00, 0x1F, 0x00, 0xF0, 0x01,
+ 0xF0, 0x07, 0xC0, 0x1F, 0x00, 0x1F, 0x00, 0xF8, 0x00, 0x78, 0x0F, 0x80,
+ 0x03, 0xE0, 0xF8, 0x00, 0x0F, 0x87, 0xC0, 0x00, 0x3C, 0x7C, 0x00, 0x01,
+ 0xF7, 0xC0, 0x00, 0x07, 0xC0, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0,
+ 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0,
+ 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0,
+ 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0,
+ 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0,
+ 0x0F, 0x78, 0x03, 0xC0, 0x1E, 0x78, 0x03, 0xC0, 0x1E, 0x78, 0x03, 0xC0,
+ 0x1E, 0x7C, 0x03, 0xC0, 0x3C, 0x3E, 0x03, 0xC0, 0x7C, 0x3E, 0x03, 0xC0,
+ 0x7C, 0x1F, 0x03, 0xC0, 0xF8, 0x0F, 0xC3, 0xC3, 0xF0, 0x07, 0xF3, 0xCF,
+ 0xE0, 0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF,
+ 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0,
+ 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x0F, 0xF0,
+ 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF,
+ 0xC0, 0x07, 0xF0, 0x0F, 0xE0, 0x0F, 0xC0, 0x03, 0xF0, 0x1F, 0x00, 0x00,
+ 0xF8, 0x3E, 0x00, 0x00, 0x7C, 0x3C, 0x00, 0x00, 0x3C, 0x7C, 0x00, 0x00,
+ 0x3E, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0xF0, 0x00, 0x00,
+ 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00,
+ 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00,
+ 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x1F, 0x78, 0x00, 0x00,
+ 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x7C, 0x00, 0x00, 0x3E, 0x3C, 0x00, 0x00,
+ 0x3C, 0x3E, 0x00, 0x00, 0x7C, 0x1E, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00,
+ 0xF0, 0x0F, 0x80, 0x01, 0xF0, 0x07, 0xE0, 0x07, 0xE0, 0x03, 0xF0, 0x0F,
+ 0xC0, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xF8, 0x1F,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F,
+ 0xF8, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78,
+ 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x01, 0xE0, 0x07, 0x80, 0x1E,
+ 0x00, 0x78, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x01, 0xE0, 0x07,
+ 0x80, 0x1E, 0x00, 0x78, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x01,
+ 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00,
+ 0x78, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x01, 0xE0, 0x01, 0xF0,
+ 0xF8, 0x00, 0x1F, 0x0F, 0x80, 0x01, 0xF0, 0xF8, 0x00, 0x1F, 0x0F, 0x80,
+ 0x01, 0xF0, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x01,
+ 0xF7, 0xC0, 0x00, 0x3E, 0x3E, 0x00, 0x07, 0xC3, 0xE0, 0x00, 0x7C, 0x1F,
+ 0x00, 0x0F, 0x80, 0xF8, 0x01, 0xF0, 0x0F, 0x80, 0x1F, 0x00, 0x7C, 0x03,
+ 0xE0, 0x03, 0xE0, 0x7C, 0x00, 0x3E, 0x07, 0xC0, 0x01, 0xF0, 0xF8, 0x00,
+ 0x0F, 0x9F, 0x00, 0x00, 0xF9, 0xF0, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x3F,
+ 0xC0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x1F, 0x80, 0x00, 0x00, 0xF0, 0x00,
+ 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00,
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x01, 0xE0, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x0F, 0x00,
+ 0x00, 0x07, 0x80, 0x00, 0x03, 0xC0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x70,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xF0, 0x00, 0x07,
+ 0xFF, 0x07, 0x83, 0xFF, 0xE3, 0xE1, 0xFF, 0xFC, 0xF0, 0xFC, 0x3F, 0x3C,
+ 0x3C, 0x03, 0xDF, 0x1F, 0x00, 0xFF, 0x87, 0x80, 0x1F, 0xE1, 0xE0, 0x07,
+ 0xF8, 0xF8, 0x01, 0xFC, 0x3C, 0x00, 0x7F, 0x0F, 0x00, 0x0F, 0x83, 0xC0,
+ 0x03, 0xE0, 0xF0, 0x00, 0xF8, 0x3C, 0x00, 0x3C, 0x0F, 0x00, 0x0F, 0x03,
+ 0xC0, 0x03, 0xE0, 0xF0, 0x01, 0xF8, 0x3C, 0x00, 0x7E, 0x07, 0x80, 0x1F,
+ 0x81, 0xE0, 0x0F, 0xE0, 0x7C, 0x03, 0xFC, 0x0F, 0x81, 0xFF, 0x83, 0xF0,
+ 0xFB, 0xFC, 0x7F, 0xFE, 0xFF, 0x0F, 0xFF, 0x1F, 0xC1, 0xFF, 0x81, 0xF0,
+ 0x1F, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x07,
+ 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x0E, 0x00, 0x03, 0xC0, 0x00, 0xF0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x80, 0xFF, 0xFC, 0x3F, 0xFF,
+ 0x8F, 0xFF, 0xF3, 0xF8, 0x06, 0x7C, 0x00, 0x0F, 0x00, 0x01, 0xE0, 0x00,
+ 0x3C, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0xC0, 0x00, 0x3F, 0xE0,
+ 0x0F, 0xFC, 0x03, 0xFF, 0x81, 0xFF, 0xF0, 0x3F, 0x00, 0x0F, 0x80, 0x01,
+ 0xE0, 0x00, 0x3C, 0x00, 0x07, 0x80, 0x00, 0xF0, 0x00, 0x1F, 0x00, 0x01,
+ 0xF8, 0x01, 0x9F, 0xFF, 0xF3, 0xFF, 0xFE, 0x1F, 0xFF, 0xC0, 0x7F, 0xE0,
+ 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xF0, 0x00,
+ 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x07, 0x00, 0x00, 0x38,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xC0, 0xF1, 0xFF, 0xC3,
+ 0xCF, 0xFF, 0x8F, 0x7F, 0xFF, 0x3F, 0xE0, 0x7E, 0xFE, 0x00, 0xFB, 0xF0,
+ 0x01, 0xEF, 0x80, 0x07, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00,
+ 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF,
+ 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00,
+ 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03,
+ 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xF0, 0x00, 0x03, 0xC0,
+ 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00,
+ 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0x00, 0x0F,
+ 0x01, 0xE0, 0x3C, 0x07, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x1C, 0x03, 0xC0,
+ 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00,
+ 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00,
+ 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00,
+ 0xF8, 0x07, 0xC0, 0x7F, 0xC3, 0xFC, 0x1F, 0xC0, 0xFC, 0x00, 0x0F, 0x00,
+ 0x00, 0xF0, 0x00, 0x0F, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78,
+ 0x00, 0x07, 0x80, 0x00, 0x38, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x0F,
+ 0x87, 0xC0, 0x7C, 0x3E, 0x03, 0xE1, 0xF0, 0x1F, 0x0F, 0x80, 0xF8, 0x7C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x78, 0x01, 0xE3, 0xC0, 0x07, 0x9E, 0x00, 0x3C,
+ 0xF0, 0x00, 0xF7, 0x80, 0x07, 0xBC, 0x00, 0x3D, 0xE0, 0x00, 0xFF, 0x00,
+ 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F,
+ 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x01, 0xFF, 0x00, 0x0F, 0x78, 0x00,
+ 0x7B, 0xC0, 0x07, 0xDE, 0x00, 0x3C, 0xF8, 0x03, 0xE3, 0xC0, 0x3E, 0x1F,
+ 0x87, 0xF0, 0x7F, 0xFF, 0x03, 0xFF, 0xF0, 0x07, 0xFE, 0x00, 0x1F, 0xC0,
+ 0x00, 0x01, 0xFC, 0x00, 0x01, 0xFF, 0xC1, 0xE0, 0xFF, 0xF8, 0xF8, 0x7F,
+ 0xFF, 0x3C, 0x3F, 0x0F, 0xCF, 0x0F, 0x00, 0xF7, 0xC7, 0xC0, 0x3F, 0xE1,
+ 0xE0, 0x07, 0xF8, 0x78, 0x01, 0xFE, 0x3E, 0x00, 0x7F, 0x0F, 0x00, 0x1F,
+ 0xC3, 0xC0, 0x03, 0xE0, 0xF0, 0x00, 0xF8, 0x3C, 0x00, 0x3E, 0x0F, 0x00,
+ 0x0F, 0x03, 0xC0, 0x03, 0xC0, 0xF0, 0x00, 0xF8, 0x3C, 0x00, 0x7E, 0x0F,
+ 0x00, 0x1F, 0x81, 0xE0, 0x07, 0xE0, 0x78, 0x03, 0xF8, 0x1F, 0x00, 0xFF,
+ 0x03, 0xE0, 0x7F, 0xE0, 0xFC, 0x3E, 0xFF, 0x1F, 0xFF, 0xBF, 0xC3, 0xFF,
+ 0xC7, 0xF0, 0x7F, 0xE0, 0x7C, 0x07, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x3F,
+ 0xFC, 0x01, 0xFF, 0xFC, 0x0F, 0xFF, 0xF8, 0x7E, 0x07, 0xE1, 0xF0, 0x07,
+ 0xCF, 0x80, 0x0F, 0x3E, 0x00, 0x3C, 0xF0, 0x00, 0xF3, 0xC0, 0x03, 0xCF,
+ 0x00, 0x0F, 0x3C, 0x00, 0x3C, 0xF0, 0x01, 0xF3, 0xC0, 0x0F, 0x8F, 0x00,
+ 0x7E, 0x3C, 0x07, 0xF0, 0xF1, 0xFF, 0xC3, 0xC7, 0xFC, 0x0F, 0x1F, 0xFC,
+ 0x3C, 0x7F, 0xF8, 0xF0, 0x07, 0xF3, 0xC0, 0x03, 0xEF, 0x00, 0x07, 0xBC,
+ 0x00, 0x1F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00,
+ 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x01, 0xFF, 0x80, 0x07, 0xBF, 0x00, 0x3E,
+ 0xFF, 0x03, 0xFB, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x3D, 0xFF, 0xF0, 0xF1,
+ 0xFE, 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00,
+ 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03,
+ 0xC0, 0x00, 0x00, 0xF0, 0x00, 0x07, 0xFF, 0x00, 0x01, 0xEF, 0xE0, 0x00,
+ 0x7B, 0xF8, 0x00, 0x3E, 0x1F, 0x00, 0x0F, 0x03, 0xC0, 0x07, 0xC0, 0xF0,
+ 0x01, 0xE0, 0x1E, 0x00, 0xF8, 0x07, 0x80, 0x3C, 0x01, 0xF0, 0x0F, 0x00,
+ 0x3C, 0x07, 0xC0, 0x0F, 0x01, 0xE0, 0x03, 0xE0, 0xF8, 0x00, 0x78, 0x3C,
+ 0x00, 0x1E, 0x0F, 0x00, 0x07, 0xC7, 0x80, 0x00, 0xF1, 0xE0, 0x00, 0x3C,
+ 0xF8, 0x00, 0x0F, 0xBC, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x7F, 0x80, 0x00,
+ 0x1F, 0xE0, 0x00, 0x03, 0xF0, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x3F, 0x00,
+ 0x00, 0x07, 0x80, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1E,
+ 0x00, 0x00, 0x07, 0x80, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00, 0x00,
+ 0x1E, 0x00, 0x00, 0x07, 0x80, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00,
+ 0x00, 0xFF, 0xC0, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0xF8, 0x0F, 0xFF, 0xF8,
+ 0x1F, 0x80, 0x38, 0x1E, 0x00, 0x08, 0x1E, 0x00, 0x00, 0x1E, 0x00, 0x00,
+ 0x1F, 0x80, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x07, 0xFF, 0xC0,
+ 0x07, 0xFF, 0xF0, 0x1F, 0x1F, 0xF8, 0x1E, 0x01, 0xFC, 0x3C, 0x00, 0x7C,
+ 0x78, 0x00, 0x3E, 0x78, 0x00, 0x1E, 0x70, 0x00, 0x1F, 0xF0, 0x00, 0x0F,
+ 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F,
+ 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF8, 0x00, 0x1F, 0x78, 0x00, 0x1E,
+ 0x78, 0x00, 0x3E, 0x7C, 0x00, 0x3E, 0x3E, 0x00, 0x7C, 0x3F, 0x81, 0xFC,
+ 0x1F, 0xFF, 0xF8, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xC0, 0x00, 0xFF, 0x00,
+ 0x03, 0xFF, 0x01, 0xFF, 0xF8, 0x7F, 0xFF, 0x1F, 0xFF, 0xE7, 0xF0, 0x0C,
+ 0xF8, 0x00, 0x1E, 0x00, 0x03, 0xC0, 0x00, 0x78, 0x00, 0x0F, 0x00, 0x00,
+ 0xF0, 0x00, 0x0F, 0x80, 0x00, 0x7F, 0xC0, 0x1F, 0xF8, 0x07, 0xFF, 0x03,
+ 0xFF, 0xE0, 0x7E, 0x00, 0x1F, 0x00, 0x03, 0xC0, 0x00, 0x78, 0x00, 0x0F,
+ 0x00, 0x01, 0xE0, 0x00, 0x3E, 0x00, 0x03, 0xF0, 0x03, 0x3F, 0xFF, 0xE7,
+ 0xFF, 0xFC, 0x3F, 0xFF, 0x80, 0xFF, 0xC0, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF,
+ 0xDF, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0x00, 0x0F, 0xE0, 0x00,
+ 0xFC, 0x00, 0x1F, 0xC0, 0x01, 0xF8, 0x00, 0x1F, 0x80, 0x01, 0xF8, 0x00,
+ 0x1F, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x07, 0x80,
+ 0x00, 0x7C, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x01, 0xF0, 0x00, 0x0F,
+ 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, 0x00,
+ 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3E,
+ 0x00, 0x01, 0xF0, 0x00, 0x07, 0xE0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0xFC,
+ 0x01, 0xFF, 0xF8, 0x07, 0xFF, 0xE0, 0x07, 0xFF, 0x00, 0x00, 0x7C, 0x00,
+ 0x01, 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x3E,
+ 0x00, 0x07, 0xE0, 0x00, 0x3F, 0x00, 0x01, 0xF0, 0x00, 0x0E, 0x00, 0x00,
+ 0x7F, 0x03, 0xC7, 0xFF, 0x0F, 0x3F, 0xFE, 0x3D, 0xFF, 0xFC, 0xFF, 0x81,
+ 0xFB, 0xF8, 0x03, 0xEF, 0xC0, 0x07, 0xBE, 0x00, 0x1F, 0xF8, 0x00, 0x3F,
+ 0xC0, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0,
+ 0x00, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00,
+ 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF,
+ 0x00, 0x03, 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00,
+ 0x03, 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03,
+ 0xC0, 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xF0, 0x00, 0x03, 0xC0,
+ 0x00, 0x0F, 0x00, 0x00, 0x3C, 0x00, 0x7E, 0x00, 0x01, 0xFF, 0x80, 0x07,
+ 0xFF, 0xE0, 0x0F, 0xFF, 0xF0, 0x1F, 0x81, 0xF8, 0x1F, 0x00, 0xF8, 0x3E,
+ 0x00, 0x7C, 0x3C, 0x00, 0x3C, 0x7C, 0x00, 0x3E, 0x78, 0x00, 0x1E, 0x78,
+ 0x00, 0x1E, 0x78, 0x00, 0x1E, 0xF0, 0x00, 0x1F, 0xF0, 0x00, 0x0F, 0xF0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0x78, 0x00, 0x1E, 0x78,
+ 0x00, 0x1E, 0x78, 0x00, 0x1E, 0x7C, 0x00, 0x3E, 0x3C, 0x00, 0x3C, 0x3E,
+ 0x00, 0x7C, 0x1F, 0x00, 0xF8, 0x1F, 0x81, 0xF0, 0x0F, 0xFF, 0xF0, 0x07,
+ 0xFF, 0xE0, 0x01, 0xFF, 0x80, 0x00, 0x7E, 0x00, 0xF0, 0x3C, 0x0F, 0x03,
+ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0,
+ 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF8, 0x1F, 0x07,
+ 0xFC, 0xFF, 0x1F, 0xC3, 0xF0, 0xF0, 0x01, 0xF3, 0xC0, 0x1F, 0x8F, 0x00,
+ 0xFC, 0x3C, 0x07, 0xE0, 0xF0, 0x3F, 0x03, 0xC1, 0xF8, 0x0F, 0x0F, 0xC0,
+ 0x3C, 0x7E, 0x00, 0xF3, 0xF0, 0x03, 0xDF, 0x80, 0x0F, 0xFE, 0x00, 0x3F,
+ 0xFC, 0x00, 0xFE, 0xF8, 0x03, 0xF1, 0xE0, 0x0F, 0x87, 0xC0, 0x3C, 0x0F,
+ 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x0F, 0x00, 0xF8, 0x3C, 0x01, 0xF0,
+ 0xF0, 0x07, 0xC3, 0xC0, 0x0F, 0x8F, 0x00, 0x1F, 0x3C, 0x00, 0x7C, 0xF0,
+ 0x00, 0xFB, 0xC0, 0x01, 0xF0, 0x0F, 0xC0, 0x00, 0x07, 0xF0, 0x00, 0x03,
+ 0xFC, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x03, 0xE0, 0x00,
+ 0x00, 0xF0, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x0F, 0x00,
+ 0x00, 0x07, 0xC0, 0x00, 0x01, 0xE0, 0x00, 0x01, 0xF0, 0x00, 0x00, 0xFC,
+ 0x00, 0x00, 0xFE, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x3D,
+ 0xE0, 0x00, 0x3E, 0xF8, 0x00, 0x1E, 0x3C, 0x00, 0x1F, 0x1E, 0x00, 0x0F,
+ 0x0F, 0x80, 0x0F, 0x83, 0xC0, 0x07, 0x81, 0xE0, 0x07, 0xC0, 0xF8, 0x03,
+ 0xC0, 0x3C, 0x03, 0xE0, 0x1F, 0x01, 0xE0, 0x07, 0x80, 0xF0, 0x03, 0xC0,
+ 0xF8, 0x01, 0xF0, 0x78, 0x00, 0x78, 0x7C, 0x00, 0x3C, 0x3C, 0x00, 0x1F,
+ 0x3E, 0x00, 0x07, 0x9E, 0x00, 0x03, 0xDF, 0x00, 0x00, 0xF0, 0xF0, 0x00,
+ 0x3C, 0x78, 0x00, 0x1E, 0x3C, 0x00, 0x0F, 0x1E, 0x00, 0x07, 0x8F, 0x00,
+ 0x03, 0xC7, 0x80, 0x01, 0xE3, 0xC0, 0x00, 0xF1, 0xE0, 0x00, 0x78, 0xF0,
+ 0x00, 0x3C, 0x78, 0x00, 0x1E, 0x3C, 0x00, 0x0F, 0x1E, 0x00, 0x07, 0x8F,
+ 0x00, 0x03, 0xC7, 0x80, 0x01, 0xE3, 0xC0, 0x00, 0xF1, 0xE0, 0x00, 0x78,
+ 0xF0, 0x00, 0x3C, 0x78, 0x00, 0x1E, 0x3C, 0x00, 0x0F, 0x1F, 0x00, 0x0F,
+ 0x8F, 0x80, 0x07, 0xC7, 0xE0, 0x07, 0xE3, 0xFC, 0x0F, 0xFB, 0xFF, 0xFF,
+ 0xFF, 0xF7, 0xFF, 0x9F, 0xF9, 0xFF, 0x8F, 0xFC, 0x3F, 0x03, 0xDE, 0x00,
+ 0x00, 0x0F, 0x00, 0x00, 0x07, 0x80, 0x00, 0x03, 0xC0, 0x00, 0x01, 0xE0,
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x1E,
+ 0x00, 0x00, 0x00, 0xF0, 0x01, 0xE3, 0xE0, 0x03, 0xC7, 0x80, 0x0F, 0x1E,
+ 0x00, 0x1E, 0x7C, 0x00, 0x78, 0xF0, 0x01, 0xE3, 0xC0, 0x03, 0xCF, 0x80,
+ 0x0F, 0x1E, 0x00, 0x3C, 0x78, 0x00, 0xF1, 0xE0, 0x03, 0xC3, 0xC0, 0x0F,
+ 0x0F, 0x00, 0x7C, 0x3C, 0x01, 0xE0, 0xF8, 0x0F, 0x81, 0xE0, 0x3E, 0x07,
+ 0x81, 0xF0, 0x1F, 0x0F, 0x80, 0x3C, 0x3E, 0x00, 0xF1, 0xF0, 0x03, 0xEF,
+ 0x80, 0x07, 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0x7F, 0x80, 0x00, 0xFC, 0x00,
+ 0x03, 0xE0, 0x00, 0x7F, 0xFF, 0xE7, 0xFF, 0xFE, 0x7F, 0xFF, 0xE7, 0xFF,
+ 0xFE, 0x07, 0xF0, 0x01, 0xF8, 0x00, 0x3E, 0x00, 0x03, 0xC0, 0x00, 0x78,
+ 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0x7C, 0x00, 0x03,
+ 0xE0, 0x00, 0x3F, 0xE0, 0x01, 0xFF, 0xF8, 0x07, 0xFF, 0x80, 0x3F, 0xF8,
+ 0x0F, 0xFF, 0x81, 0xFC, 0x00, 0x3E, 0x00, 0x07, 0xC0, 0x00, 0x78, 0x00,
+ 0x0F, 0x80, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00,
+ 0x00, 0xF0, 0x00, 0x0F, 0x80, 0x00, 0x7C, 0x00, 0x07, 0xE0, 0x00, 0x3F,
+ 0x80, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x7F, 0xFE, 0x00, 0xFF, 0xE0,
+ 0x00, 0x1F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F,
+ 0x00, 0x01, 0xF0, 0x00, 0x7E, 0x00, 0x07, 0xE0, 0x00, 0x7C, 0x00, 0x07,
+ 0x00, 0x00, 0xFF, 0x00, 0x03, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x1F, 0xFF,
+ 0xF8, 0x1F, 0x81, 0xF8, 0x3E, 0x00, 0x7C, 0x7C, 0x00, 0x3E, 0x7C, 0x00,
+ 0x3E, 0x78, 0x00, 0x1E, 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x0F, 0xF0, 0x00,
+ 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00,
+ 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF8, 0x00, 0x1F, 0x78, 0x00,
+ 0x1E, 0x7C, 0x00, 0x3E, 0x7C, 0x00, 0x3E, 0x3E, 0x00, 0x7C, 0x1F, 0x81,
+ 0xF8, 0x1F, 0xFF, 0xF8, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xC0, 0x00, 0xFF,
+ 0x00, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xDF, 0xFF,
+ 0xFF, 0xE1, 0xE0, 0x07, 0x80, 0xF0, 0x03, 0xC0, 0x78, 0x01, 0xE0, 0x3C,
+ 0x00, 0xF0, 0x1E, 0x00, 0x78, 0x0F, 0x00, 0x3C, 0x07, 0x80, 0x1E, 0x03,
+ 0xC0, 0x0F, 0x01, 0xE0, 0x07, 0x80, 0xF0, 0x03, 0xC0, 0x78, 0x01, 0xE0,
+ 0x3C, 0x00, 0xF0, 0x1E, 0x00, 0x78, 0x0F, 0x00, 0x3C, 0x07, 0x80, 0x1E,
+ 0x03, 0xC0, 0x0F, 0x01, 0xE0, 0x07, 0x80, 0xF0, 0x03, 0xC0, 0x78, 0x01,
+ 0xF0, 0x3C, 0x00, 0xFF, 0x1E, 0x00, 0x3F, 0x8F, 0x00, 0x1F, 0xC0, 0x00,
+ 0x07, 0xE0, 0x00, 0xFF, 0x00, 0x03, 0xFF, 0xC0, 0x07, 0xFF, 0xF0, 0x0F,
+ 0xFF, 0xF8, 0x1F, 0x81, 0xF8, 0x3E, 0x00, 0x7C, 0x7C, 0x00, 0x3E, 0x7C,
+ 0x00, 0x3E, 0x78, 0x00, 0x1E, 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x0F, 0xF0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0,
+ 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF8, 0x00, 0x1F, 0xF8,
+ 0x00, 0x1E, 0xFC, 0x00, 0x3E, 0xFC, 0x00, 0x3E, 0xFE, 0x00, 0x7C, 0xFF,
+ 0x81, 0xF8, 0xF7, 0xFF, 0xF8, 0xF3, 0xFF, 0xF0, 0xF1, 0xFF, 0xC0, 0xF0,
+ 0x7F, 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF0,
+ 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x3F, 0xFE, 0x07, 0xFF,
+ 0xF0, 0xFF, 0xFF, 0x1F, 0xC0, 0xF3, 0xF0, 0x01, 0x7C, 0x00, 0x07, 0xC0,
+ 0x00, 0x78, 0x00, 0x0F, 0x80, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0,
+ 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F,
+ 0x00, 0x00, 0xF8, 0x00, 0x07, 0x80, 0x00, 0x7C, 0x00, 0x07, 0xC0, 0x00,
+ 0x3E, 0x00, 0x01, 0xF8, 0x00, 0x1F, 0xFE, 0x00, 0x7F, 0xF8, 0x03, 0xFF,
+ 0xC0, 0x0F, 0xFC, 0x00, 0x03, 0xE0, 0x00, 0x1E, 0x00, 0x01, 0xE0, 0x00,
+ 0x1E, 0x00, 0x01, 0xE0, 0x00, 0x3E, 0x00, 0x0F, 0xC0, 0x00, 0xFC, 0x00,
+ 0x0F, 0x80, 0x00, 0xE0, 0x00, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xF0, 0xFF,
+ 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0x3F, 0x81, 0xFC, 0x0F, 0x80, 0x1F, 0x07,
+ 0xC0, 0x03, 0xE1, 0xE0, 0x00, 0x78, 0xF8, 0x00, 0x1E, 0x3E, 0x00, 0x07,
+ 0xCF, 0x00, 0x00, 0xF3, 0xC0, 0x00, 0x3C, 0xF0, 0x00, 0x0F, 0x3C, 0x00,
+ 0x03, 0xCF, 0x00, 0x00, 0xF3, 0xC0, 0x00, 0x3C, 0xF0, 0x00, 0x0F, 0x3E,
+ 0x00, 0x07, 0xC7, 0x80, 0x01, 0xE1, 0xE0, 0x00, 0xF8, 0x7C, 0x00, 0x3E,
+ 0x0F, 0x80, 0x1F, 0x01, 0xF8, 0x1F, 0x80, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF,
+ 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3C, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3E, 0x00, 0x00,
+ 0x1F, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x07, 0xF0, 0x00,
+ 0x03, 0xF0, 0xF0, 0x03, 0xC7, 0x80, 0x0F, 0x3C, 0x00, 0x79, 0xE0, 0x01,
+ 0xEF, 0x00, 0x0F, 0x78, 0x00, 0x7B, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0,
+ 0x00, 0x7F, 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07,
+ 0xF8, 0x00, 0x3F, 0xC0, 0x03, 0xFE, 0x00, 0x1E, 0xF0, 0x00, 0xF7, 0x80,
+ 0x0F, 0xBC, 0x00, 0x79, 0xF0, 0x07, 0xC7, 0x80, 0x7C, 0x3F, 0x0F, 0xE0,
+ 0xFF, 0xFE, 0x07, 0xFF, 0xE0, 0x0F, 0xFC, 0x00, 0x3F, 0x80, 0x00, 0x00,
+ 0xC3, 0xE0, 0x00, 0xF1, 0xFE, 0x00, 0xFC, 0xFF, 0xC0, 0x7F, 0x3F, 0xF8,
+ 0x3F, 0x9F, 0x3F, 0x0F, 0x87, 0x87, 0xC7, 0xC1, 0xE0, 0xF9, 0xF0, 0x78,
+ 0x1E, 0x78, 0x1E, 0x07, 0xBE, 0x07, 0x81, 0xFF, 0x01, 0xE0, 0x3F, 0xC0,
+ 0x78, 0x0F, 0xF0, 0x1E, 0x03, 0xFC, 0x07, 0x80, 0xFF, 0x01, 0xE0, 0x3F,
+ 0xC0, 0x78, 0x0F, 0xF0, 0x1E, 0x03, 0xFC, 0x07, 0x80, 0xFF, 0x81, 0xE0,
+ 0x7D, 0xE0, 0x78, 0x1E, 0x7C, 0x1E, 0x07, 0x9F, 0x07, 0x83, 0xE3, 0xE1,
+ 0xE1, 0xF0, 0x7E, 0x79, 0xF8, 0x1F, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0x00,
+ 0x3F, 0xFF, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x07, 0x80,
+ 0x00, 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x07,
+ 0x80, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1E, 0x00, 0x00,
+ 0xF8, 0x00, 0x1F, 0xFE, 0x00, 0x3E, 0xFF, 0x00, 0x3E, 0xFF, 0x80, 0x7C,
+ 0x0F, 0x80, 0x7C, 0x07, 0x80, 0xF8, 0x07, 0xC0, 0xF8, 0x03, 0xC1, 0xF0,
+ 0x03, 0xE1, 0xF0, 0x01, 0xE3, 0xE0, 0x01, 0xE3, 0xC0, 0x01, 0xF7, 0xC0,
+ 0x00, 0xFF, 0x80, 0x00, 0xFF, 0x80, 0x00, 0x7F, 0x00, 0x00, 0x7F, 0x00,
+ 0x00, 0x7E, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x7E, 0x00,
+ 0x00, 0xFE, 0x00, 0x00, 0xFE, 0x00, 0x01, 0xFF, 0x00, 0x01, 0xFF, 0x00,
+ 0x03, 0xEF, 0x80, 0x03, 0xC7, 0x80, 0x07, 0xC7, 0x80, 0x07, 0x83, 0xC0,
+ 0x0F, 0x83, 0xC0, 0x1F, 0x03, 0xC0, 0x1F, 0x01, 0xE0, 0x3E, 0x01, 0xF0,
+ 0x3E, 0x01, 0xFF, 0x7C, 0x00, 0xFF, 0x7C, 0x00, 0x7F, 0xF8, 0x00, 0x1F,
+ 0xF0, 0x1E, 0x03, 0xFC, 0x07, 0x80, 0xFF, 0x01, 0xE0, 0x3F, 0xC0, 0x78,
+ 0x0F, 0xF0, 0x1E, 0x03, 0xFC, 0x07, 0x80, 0xFF, 0x01, 0xE0, 0x3F, 0xC0,
+ 0x78, 0x0F, 0xF0, 0x1E, 0x03, 0xFC, 0x07, 0x80, 0xFF, 0x01, 0xE0, 0x3F,
+ 0xC0, 0x78, 0x0F, 0xF0, 0x1E, 0x03, 0xFC, 0x07, 0x80, 0xFF, 0x01, 0xE0,
+ 0x3F, 0xC0, 0x78, 0x0F, 0xF0, 0x1E, 0x03, 0xFC, 0x07, 0x80, 0xFF, 0x01,
+ 0xE0, 0x7F, 0xE0, 0x78, 0x1E, 0x7C, 0x1E, 0x0F, 0x9F, 0x87, 0x87, 0xE3,
+ 0xF9, 0xE7, 0xF0, 0x7F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFC, 0x00, 0xFF, 0xFC,
+ 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x07,
+ 0x80, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1E, 0x00, 0x00,
+ 0x07, 0x80, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x78, 0x00, 0x0F, 0x00, 0x00,
+ 0xF0, 0x1E, 0x00, 0x00, 0x78, 0x1E, 0x00, 0x00, 0x78, 0x3C, 0x00, 0x00,
+ 0x3C, 0x3C, 0x00, 0x00, 0x3C, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00,
+ 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x70, 0x00, 0x00, 0x0E, 0xF0, 0x00, 0x00,
+ 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0,
+ 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0,
+ 0x0F, 0xF0, 0x07, 0xC0, 0x0F, 0xF8, 0x07, 0xE0, 0x1F, 0x78, 0x07, 0xE0,
+ 0x1E, 0x78, 0x07, 0xE0, 0x1E, 0x7C, 0x0F, 0xF0, 0x3E, 0x3E, 0x1E, 0xF8,
+ 0x7C, 0x3F, 0xFE, 0x7F, 0xFC, 0x1F, 0xFC, 0x7F, 0xF8, 0x0F, 0xFC, 0x3F,
+ 0xF0, 0x03, 0xF0, 0x0F, 0xC0, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F,
+ 0xF8, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C,
+ 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F,
+ 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x1E, 0x00, 0x78, 0x01,
+ 0xF0, 0x07, 0xFC, 0x0F, 0xF0, 0x1F, 0xC0, 0x1F, 0x3E, 0x1F, 0x01, 0xF0,
+ 0xF8, 0x0F, 0x87, 0xC0, 0x7C, 0x3E, 0x03, 0xE1, 0xF0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0xE0, 0x07, 0x8F, 0x00, 0x1E, 0x78, 0x00, 0xF3, 0xC0, 0x03, 0xDE,
+ 0x00, 0x1E, 0xF0, 0x00, 0xF7, 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x00,
+ 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0,
+ 0x00, 0x7F, 0x80, 0x07, 0xFC, 0x00, 0x3D, 0xE0, 0x01, 0xEF, 0x00, 0x1F,
+ 0x78, 0x00, 0xF3, 0xE0, 0x0F, 0x8F, 0x00, 0xF8, 0x7E, 0x1F, 0xC1, 0xFF,
+ 0xFC, 0x0F, 0xFF, 0xC0, 0x1F, 0xF8, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x01,
+ 0xE0, 0x00, 0x03, 0xE0, 0x00, 0x03, 0xC0, 0x00, 0x07, 0x80, 0x00, 0x0F,
+ 0x00, 0x00, 0x1E, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x78,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x03, 0xFF,
+ 0xC0, 0x0F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x1F, 0x81, 0xF8, 0x3E, 0x00,
+ 0x7C, 0x7C, 0x00, 0x3E, 0x7C, 0x00, 0x3E, 0x78, 0x00, 0x1E, 0xF8, 0x00,
+ 0x1F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00,
+ 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00,
+ 0x0F, 0xF8, 0x00, 0x1F, 0x78, 0x00, 0x1E, 0x7C, 0x00, 0x3E, 0x7C, 0x00,
+ 0x3E, 0x3E, 0x00, 0x7C, 0x1F, 0x81, 0xF8, 0x1F, 0xFF, 0xF8, 0x0F, 0xFF,
+ 0xF0, 0x03, 0xFF, 0xC0, 0x00, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xF8,
+ 0x00, 0x0F, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x03,
+ 0x80, 0x00, 0x3C, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x01, 0xE3, 0xC0, 0x07, 0x9E, 0x00,
+ 0x3C, 0xF0, 0x00, 0xF7, 0x80, 0x07, 0xBC, 0x00, 0x3D, 0xE0, 0x00, 0xFF,
+ 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00,
+ 0x7F, 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x01, 0xFF, 0x00, 0x0F, 0x78,
+ 0x00, 0x7B, 0xC0, 0x07, 0xDE, 0x00, 0x3C, 0xF8, 0x03, 0xE3, 0xC0, 0x3E,
+ 0x1F, 0x87, 0xF0, 0x7F, 0xFF, 0x03, 0xFF, 0xF0, 0x07, 0xFE, 0x00, 0x1F,
+ 0xC0, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x03,
+ 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x1E, 0x00,
+ 0x00, 0x78, 0x1E, 0x00, 0x00, 0x78, 0x3C, 0x00, 0x00, 0x3C, 0x3C, 0x00,
+ 0x00, 0x3C, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00, 0x00, 0x1E, 0x78, 0x00,
+ 0x00, 0x1E, 0x70, 0x00, 0x00, 0x0E, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x03,
+ 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03,
+ 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x03, 0xC0, 0x0F, 0xF0, 0x07,
+ 0xC0, 0x0F, 0xF8, 0x07, 0xE0, 0x1F, 0x78, 0x07, 0xE0, 0x1E, 0x78, 0x07,
+ 0xE0, 0x1E, 0x7C, 0x0F, 0xF0, 0x3E, 0x3E, 0x1E, 0xF8, 0x7C, 0x3F, 0xFE,
+ 0x7F, 0xFC, 0x1F, 0xFC, 0x7F, 0xF8, 0x0F, 0xFC, 0x3F, 0xF0, 0x03, 0xF0,
+ 0x0F, 0xC0,
+};
+
+const GFXglyph FreeSans24pt_Win1253Glyphs[] PROGMEM = {
+/* 0x01 */ { 0, 43, 48, 60, 8, -39 },
+/* 0x02 */ { 258, 43, 48, 60, 8, -39 },
+/* 0x03 */ { 516, 48, 48, 60, 6, -39 },
+/* 0x04 */ { 804, 57, 48, 60, 1, -39 },
+/* 0x05 */ { 1146, 47, 48, 60, 6, -39 },
+/* 0x06 */ { 1428, 47, 48, 60, 6, -39 },
+/* 0x07 */ { 1710, 0, 0, 0, 0, 0 },
+/* 0x08 */ { 1710, 49, 48, 60, 5, -39 },
+/* 0x09 */ { 2004, 54, 38, 60, 3, -34 },
+/* 0x0A */ { 2261, 0, 0, 0, 0, 0 },
+/* 0x0B */ { 2261, 52, 48, 60, 4, -39 },
+/* 0x0C */ { 2573, 47, 48, 60, 6, -39 },
+/* 0x0D */ { 2855, 0, 0, 0, 0, 0 },
+/* 0x0E */ { 2855, 47, 48, 60, 6, -39 },
+/* 0x0F */ { 3137, 48, 49, 60, 6, -39 },
+/* 0x10 */ { 3431, 46, 48, 60, 7, -39 },
+/* 0x11 */ { 3707, 48, 48, 60, 6, -39 },
+/* 0x12 */ { 3995, 46, 48, 60, 7, -39 },
+/* 0x13 */ { 4271, 48, 46, 60, 6, -38 },
+/* 0x14 */ { 4547, 48, 48, 60, 6, -39 },
+/* 0x15 */ { 4835, 51, 48, 60, 4, -39 },
+/* 0x16 */ { 5141, 37, 47, 60, 11, -38 },
+/* 0x17 */ { 5359, 50, 40, 60, 5, -35 },
+/* 0x18 */ { 5609, 56, 40, 60, 2, -35 },
+/* 0x19 */ { 5889, 48, 48, 60, 6, -39 },
+/* 0x1A */ { 6177, 0, 0, 0, 0, 0 },
+/* 0x1B */ { 6177, 56, 49, 60, 2, -39 },
+/* 0x1C */ { 6520, 48, 48, 60, 6, -39 },
+/* 0x1D */ { 6808, 49, 49, 60, 5, -39 },
+/* 0x1E */ { 7109, 48, 47, 60, 5, -38 },
+/* 0x1F */ { 7391, 34, 48, 60, 13, -39 },
+/* 0x20 */ { 7595, 1, 1, 15, 0, 0 },
+/* 0x21 */ { 7596, 5, 34, 19, 7, -33 },
+/* 0x22 */ { 7618, 13, 13, 22, 4, -33 },
+/* 0x23 */ { 7640, 32, 35, 39, 4, -34 },
+/* 0x24 */ { 7780, 22, 42, 30, 4, -34 },
+/* 0x25 */ { 7896, 39, 36, 45, 3, -34 },
+/* 0x26 */ { 8072, 32, 36, 37, 3, -34 },
+/* 0x27 */ { 8216, 4, 13, 13, 4, -33 },
+/* 0x28 */ { 8223, 11, 42, 18, 4, -35 },
+/* 0x29 */ { 8281, 11, 42, 18, 4, -35 },
+/* 0x2A */ { 8339, 21, 22, 24, 1, -34 },
+/* 0x2B */ { 8397, 30, 30, 39, 5, -29 },
+/* 0x2C */ { 8510, 6, 11, 15, 4, -5 },
+/* 0x2D */ { 8519, 12, 4, 17, 2, -14 },
+/* 0x2E */ { 8525, 4, 6, 15, 6, -5 },
+/* 0x2F */ { 8528, 16, 39, 16, 0, -33 },
+/* 0x30 */ { 8606, 24, 36, 30, 3, -34 },
+/* 0x31 */ { 8714, 20, 34, 30, 5, -33 },
+/* 0x32 */ { 8799, 22, 35, 30, 4, -34 },
+/* 0x33 */ { 8896, 23, 36, 30, 4, -34 },
+/* 0x34 */ { 9000, 25, 34, 30, 2, -33 },
+/* 0x35 */ { 9107, 22, 35, 30, 4, -33 },
+/* 0x36 */ { 9204, 24, 36, 30, 3, -34 },
+/* 0x37 */ { 9312, 22, 34, 30, 4, -33 },
+/* 0x38 */ { 9406, 24, 36, 30, 3, -34 },
+/* 0x39 */ { 9514, 24, 36, 30, 3, -34 },
+/* 0x3A */ { 9622, 5, 24, 16, 6, -23 },
+/* 0x3B */ { 9637, 6, 29, 16, 4, -23 },
+/* 0x3C */ { 9659, 29, 25, 39, 5, -26 },
+/* 0x3D */ { 9750, 29, 14, 39, 5, -21 },
+/* 0x3E */ { 9801, 29, 25, 39, 5, -26 },
+/* 0x3F */ { 9892, 18, 35, 25, 3, -34 },
+/* 0x40 */ { 9971, 41, 41, 47, 3, -32 },
+/* 0x41 */ { 10182, 31, 34, 32, 0, -33 },
+/* 0x42 */ { 10314, 24, 34, 32, 4, -33 },
+/* 0x43 */ { 10416, 28, 36, 33, 3, -34 },
+/* 0x44 */ { 10542, 29, 34, 36, 4, -33 },
+/* 0x45 */ { 10666, 22, 34, 30, 4, -33 },
+/* 0x46 */ { 10760, 20, 34, 27, 4, -33 },
+/* 0x47 */ { 10845, 30, 36, 36, 3, -34 },
+/* 0x48 */ { 10980, 26, 34, 35, 4, -33 },
+/* 0x49 */ { 11091, 4, 34, 14, 4, -33 },
+/* 0x4A */ { 11108, 11, 43, 14, -3, -33 },
+/* 0x4B */ { 11168, 27, 34, 31, 4, -33 },
+/* 0x4C */ { 11283, 21, 34, 26, 4, -33 },
+/* 0x4D */ { 11373, 31, 34, 41, 4, -33 },
+/* 0x4E */ { 11505, 26, 34, 35, 4, -33 },
+/* 0x4F */ { 11616, 32, 36, 37, 3, -34 },
+/* 0x50 */ { 11760, 22, 34, 28, 4, -33 },
+/* 0x51 */ { 11854, 32, 41, 37, 3, -34 },
+/* 0x52 */ { 12018, 27, 34, 33, 4, -33 },
+/* 0x53 */ { 12133, 24, 36, 30, 3, -34 },
+/* 0x54 */ { 12241, 28, 34, 29, 0, -33 },
+/* 0x55 */ { 12360, 26, 35, 34, 5, -33 },
+/* 0x56 */ { 12474, 31, 34, 32, 0, -33 },
+/* 0x57 */ { 12606, 43, 34, 46, 2, -33 },
+/* 0x58 */ { 12789, 29, 34, 31, 1, -33 },
+/* 0x59 */ { 12913, 28, 34, 29, 0, -33 },
+/* 0x5A */ { 13032, 28, 34, 32, 2, -33 },
+/* 0x5B */ { 13151, 10, 42, 18, 4, -35 },
+/* 0x5C */ { 13204, 16, 39, 16, 0, -33 },
+/* 0x5D */ { 13282, 9, 42, 18, 4, -35 },
+/* 0x5E */ { 13330, 29, 13, 39, 5, -33 },
+/* 0x5F */ { 13378, 24, 4, 24, 0, 8 },
+/* 0x60 */ { 13390, 11, 9, 24, 4, -37 },
+/* 0x61 */ { 13403, 22, 28, 29, 3, -26 },
+/* 0x62 */ { 13480, 23, 37, 30, 4, -35 },
+/* 0x63 */ { 13587, 20, 28, 26, 3, -26 },
+/* 0x64 */ { 13657, 23, 37, 30, 3, -35 },
+/* 0x65 */ { 13764, 24, 28, 29, 3, -26 },
+/* 0x66 */ { 13848, 16, 36, 17, 1, -35 },
+/* 0x67 */ { 13920, 23, 37, 30, 3, -26 },
+/* 0x68 */ { 14027, 22, 36, 30, 4, -35 },
+/* 0x69 */ { 14126, 4, 36, 13, 4, -35 },
+/* 0x6A */ { 14144, 9, 46, 13, -1, -35 },
+/* 0x6B */ { 14196, 23, 36, 27, 4, -35 },
+/* 0x6C */ { 14300, 4, 36, 13, 4, -35 },
+/* 0x6D */ { 14318, 38, 27, 46, 4, -26 },
+/* 0x6E */ { 14447, 22, 27, 30, 4, -26 },
+/* 0x6F */ { 14522, 24, 28, 29, 3, -26 },
+/* 0x70 */ { 14606, 23, 37, 30, 4, -26 },
+/* 0x71 */ { 14713, 23, 37, 30, 3, -26 },
+/* 0x72 */ { 14820, 15, 27, 19, 4, -26 },
+/* 0x73 */ { 14871, 19, 28, 24, 3, -26 },
+/* 0x74 */ { 14938, 16, 33, 18, 1, -32 },
+/* 0x75 */ { 15004, 22, 28, 30, 4, -26 },
+/* 0x76 */ { 15081, 25, 26, 28, 1, -25 },
+/* 0x77 */ { 15163, 35, 26, 38, 2, -25 },
+/* 0x78 */ { 15277, 25, 26, 28, 1, -25 },
+/* 0x79 */ { 15359, 25, 36, 28, 1, -25 },
+/* 0x7A */ { 15472, 21, 26, 25, 2, -25 },
+/* 0x7B */ { 15541, 18, 43, 30, 6, -35 },
+/* 0x7C */ { 15638, 4, 47, 16, 6, -35 },
+/* 0x7D */ { 15662, 18, 43, 30, 6, -35 },
+/* 0x7E */ { 15759, 29, 9, 39, 5, -18 },
+/* 0x7F */ { 15792, 0, 0, 0, 0, 0 },
+/* 0x80 */ { 15792, 26, 36, 30, 0, -34 },
+/* 0x81 */ { 15909, 0, 0, 0, 0, 0 },
+/* 0x82 */ { 15909, 6, 11, 15, 4, -5 },
+/* 0x83 */ { 15918, 20, 46, 17, -3, -35 },
+/* 0x84 */ { 16033, 15, 11, 24, 4, -5 },
+/* 0x85 */ { 16054, 36, 6, 47, 5, -5 },
+/* 0x86 */ { 16081, 20, 39, 24, 2, -33 },
+/* 0x87 */ { 16179, 20, 39, 24, 2, -33 },
+/* 0x88 */ { 16277, 0, 0, 0, 0, 0 },
+/* 0x89 */ { 16277, 58, 36, 63, 3, -34 },
+/* 0x8A */ { 16538, 0, 0, 0, 0, 0 },
+/* 0x8B */ { 16538, 11, 21, 19, 4, -23 },
+/* 0x8C */ { 16567, 0, 0, 0, 0, 0 },
+/* 0x8D */ { 16567, 0, 0, 0, 0, 0 },
+/* 0x8E */ { 16567, 0, 0, 0, 0, 0 },
+/* 0x8F */ { 16567, 0, 0, 0, 0, 0 },
+/* 0x90 */ { 16567, 0, 0, 0, 0, 0 },
+/* 0x91 */ { 16567, 6, 11, 15, 4, -33 },
+/* 0x92 */ { 16576, 6, 11, 15, 4, -33 },
+/* 0x93 */ { 16585, 15, 11, 24, 4, -33 },
+/* 0x94 */ { 16606, 15, 11, 24, 4, -33 },
+/* 0x95 */ { 16627, 14, 14, 28, 7, -23 },
+/* 0x96 */ { 16652, 19, 4, 24, 2, -14 },
+/* 0x97 */ { 16662, 42, 4, 47, 2, -14 },
+/* 0x98 */ { 16683, 0, 0, 0, 0, 0 },
+/* 0x99 */ { 16683, 31, 13, 47, 6, -33 },
+/* 0x9A */ { 16734, 0, 0, 0, 0, 0 },
+/* 0x9B */ { 16734, 11, 21, 19, 4, -23 },
+/* 0x9C */ { 16763, 0, 0, 0, 0, 0 },
+/* 0x9D */ { 16763, 0, 0, 0, 0, 0 },
+/* 0x9E */ { 16763, 0, 0, 0, 0, 0 },
+/* 0x9F */ { 16763, 0, 0, 0, 0, 0 },
+/* 0xA0 */ { 16763, 1, 1, 15, 0, 0 },
+/* 0xA1 */ { 16764, 15, 15, 24, 5, -45 },
+/* 0xA2 */ { 16793, 31, 38, 33, 0, -37 },
+/* 0xA3 */ { 16941, 22, 35, 30, 3, -34 },
+/* 0xA4 */ { 17038, 26, 26, 30, 2, -26 },
+/* 0xA5 */ { 17123, 26, 34, 30, 2, -33 },
+/* 0xA6 */ { 17234, 4, 40, 16, 6, -31 },
+/* 0xA7 */ { 17254, 19, 39, 24, 2, -34 },
+/* 0xA8 */ { 17347, 14, 5, 24, 5, -35 },
+/* 0xA9 */ { 17356, 34, 34, 47, 7, -33 },
+/* 0xAA */ { 17501, 0, 0, 0, 0, 0 },
+/* 0xAB */ { 17501, 21, 21, 29, 4, -23 },
+/* 0xAC */ { 17557, 29, 13, 39, 5, -19 },
+/* 0xAD */ { 17605, 12, 4, 17, 2, -14 },
+/* 0xAE */ { 17611, 34, 34, 47, 7, -33 },
+/* 0xAF */ { 17756, 47, 4, 47, 0, -14 },
+/* 0xB0 */ { 17780, 15, 15, 24, 4, -34 },
+/* 0xB1 */ { 17809, 30, 30, 39, 5, -29 },
+/* 0xB2 */ { 17922, 14, 19, 19, 2, -34 },
+/* 0xB3 */ { 17956, 14, 19, 19, 2, -34 },
+/* 0xB4 */ { 17990, 11, 9, 24, 9, -37 },
+/* 0xB5 */ { 18003, 15, 15, 24, 5, -45 },
+/* 0xB6 */ { 18032, 31, 38, 33, 0, -37 },
+/* 0xB7 */ { 18180, 4, 6, 15, 5, -18 },
+/* 0xB8 */ { 18183, 31, 38, 35, 0, -37 },
+/* 0xB9 */ { 18331, 35, 38, 41, 0, -37 },
+/* 0xBA */ { 18498, 13, 38, 19, 0, -37 },
+/* 0xBB */ { 18560, 21, 21, 29, 4, -23 },
+/* 0xBC */ { 18616, 36, 39, 38, 0, -37 },
+/* 0xBD */ { 18792, 39, 36, 46, 4, -34 },
+/* 0xBE */ { 18968, 38, 38, 39, 0, -37 },
+/* 0xBF */ { 19149, 36, 38, 39, 0, -37 },
+/* 0xC0 */ { 19320, 15, 46, 16, 0, -45 },
+/* 0xC1 */ { 19407, 31, 34, 32, 0, -33 },
+/* 0xC2 */ { 19539, 24, 34, 32, 4, -33 },
+/* 0xC3 */ { 19641, 21, 34, 25, 4, -33 },
+/* 0xC4 */ { 19731, 31, 34, 32, 0, -33 },
+/* 0xC5 */ { 19863, 22, 34, 30, 4, -33 },
+/* 0xC6 */ { 19957, 28, 34, 32, 2, -33 },
+/* 0xC7 */ { 20076, 26, 34, 35, 4, -33 },
+/* 0xC8 */ { 20187, 32, 36, 38, 3, -34 },
+/* 0xC9 */ { 20331, 4, 34, 14, 4, -33 },
+/* 0xCA */ { 20348, 27, 34, 31, 4, -33 },
+/* 0xCB */ { 20463, 31, 34, 32, 0, -33 },
+/* 0xCC */ { 20595, 31, 34, 41, 4, -33 },
+/* 0xCD */ { 20727, 26, 34, 35, 4, -33 },
+/* 0xCE */ { 20838, 21, 34, 29, 4, -33 },
+/* 0xCF */ { 20928, 32, 36, 37, 3, -34 },
+/* 0xD0 */ { 21072, 26, 34, 34, 4, -33 },
+/* 0xD1 */ { 21183, 22, 34, 28, 4, -33 },
+/* 0xD2 */ { 21277, 0, 0, 0, 0, 0 },
+/* 0xD3 */ { 21277, 22, 34, 29, 4, -33 },
+/* 0xD4 */ { 21371, 28, 34, 29, 0, -33 },
+/* 0xD5 */ { 21490, 28, 34, 29, 0, -33 },
+/* 0xD6 */ { 21609, 32, 34, 38, 3, -33 },
+/* 0xD7 */ { 21745, 29, 34, 31, 1, -33 },
+/* 0xD8 */ { 21869, 32, 34, 38, 3, -33 },
+/* 0xD9 */ { 22005, 32, 35, 38, 3, -34 },
+/* 0xDA */ { 22145, 14, 44, 14, -1, -43 },
+/* 0xDB */ { 22222, 28, 44, 29, 0, -43 },
+/* 0xDC */ { 22376, 26, 39, 31, 3, -37 },
+/* 0xDD */ { 22503, 19, 39, 25, 3, -37 },
+/* 0xDE */ { 22596, 22, 48, 30, 4, -37 },
+/* 0xDF */ { 22728, 12, 38, 16, 4, -37 },
+/* 0xE0 */ { 22785, 21, 47, 28, 4, -45 },
+/* 0xE1 */ { 22909, 26, 28, 31, 3, -26 },
+/* 0xE2 */ { 23000, 22, 46, 29, 4, -35 },
+/* 0xE3 */ { 23127, 26, 36, 28, 1, -25 },
+/* 0xE4 */ { 23244, 24, 36, 30, 3, -34 },
+/* 0xE5 */ { 23352, 19, 28, 25, 3, -26 },
+/* 0xE6 */ { 23419, 21, 47, 25, 2, -35 },
+/* 0xE7 */ { 23543, 22, 37, 30, 4, -26 },
+/* 0xE8 */ { 23645, 24, 37, 30, 3, -35 },
+/* 0xE9 */ { 23756, 10, 26, 16, 4, -25 },
+/* 0xEA */ { 23789, 22, 26, 27, 4, -25 },
+/* 0xEB */ { 23861, 25, 36, 27, 1, -35 },
+/* 0xEC */ { 23974, 25, 36, 30, 4, -25 },
+/* 0xED */ { 24087, 22, 26, 26, 2, -25 },
+/* 0xEE */ { 24159, 20, 47, 25, 2, -35 },
+/* 0xEF */ { 24277, 24, 28, 29, 3, -26 },
+/* 0xF0 */ { 24361, 25, 27, 28, 2, -25 },
+/* 0xF1 */ { 24446, 24, 37, 31, 4, -26 },
+/* 0xF2 */ { 24557, 20, 38, 28, 3, -26 },
+/* 0xF3 */ { 24652, 26, 27, 30, 3, -25 },
+/* 0xF4 */ { 24740, 24, 26, 28, 2, -25 },
+/* 0xF5 */ { 24818, 21, 26, 28, 4, -24 },
+/* 0xF6 */ { 24887, 26, 37, 32, 3, -26 },
+/* 0xF7 */ { 25008, 24, 36, 26, 1, -25 },
+/* 0xF8 */ { 25116, 26, 36, 32, 3, -25 },
+/* 0xF9 */ { 25233, 32, 26, 38, 3, -24 },
+/* 0xFA */ { 25337, 14, 36, 16, 0, -35 },
+/* 0xFB */ { 25400, 21, 37, 28, 4, -35 },
+/* 0xFC */ { 25498, 24, 39, 29, 3, -37 },
+/* 0xFD */ { 25615, 21, 39, 28, 4, -37 },
+/* 0xFE */ { 25718, 32, 39, 38, 3, -37 },
+/* 0xFF */ { 25874, 0, 0, 0, 0, 0 },
+};
+
+const GFXfont FreeSans24pt_Win1253 PROGMEM = {
+(uint8_t*)FreeSans24pt_Win1253Bitmaps,
+(GFXglyph*)FreeSans24pt_Win1253Glyphs,
+0x01, 0xFF, 55
+};
diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h
index 3c14c2607..6b727f273 100644
--- a/src/graphics/niche/InkHUD/Applet.h
+++ b/src/graphics/niche/InkHUD/Applet.h
@@ -104,6 +104,15 @@ class Applet : public GFX
virtual void onFreeText(char c) {}
virtual void onFreeTextDone() {}
virtual void onFreeTextCancel() {}
+ // Absolute display-space touch point, for touch-friendly UI interactions.
+ // Return true if consumed.
+ virtual bool onTouchPoint(uint16_t x, uint16_t y, bool longPress)
+ {
+ (void)x;
+ (void)y;
+ (void)longPress;
+ return false;
+ }
// List of inputs which can be subscribed to
enum InputMask { // | No Joystick | With Joystick |
BUTTON_SHORT = 1, // | Button Click | Joystick Center Click |
diff --git a/src/graphics/niche/InkHUD/AppletFont.h b/src/graphics/niche/InkHUD/AppletFont.h
index 8374c7f61..c97c12c36 100644
--- a/src/graphics/niche/InkHUD/AppletFont.h
+++ b/src/graphics/niche/InkHUD/AppletFont.h
@@ -88,8 +88,12 @@ class AppletFont
// Greek
#include "graphics/niche/Fonts/FreeSans12pt_Win1253.h"
+#include "graphics/niche/Fonts/FreeSans18pt_Win1253.h"
+#include "graphics/niche/Fonts/FreeSans24pt_Win1253.h"
#include "graphics/niche/Fonts/FreeSans6pt_Win1253.h"
#include "graphics/niche/Fonts/FreeSans9pt_Win1253.h"
+#define FREESANS_24PT_WIN1253 InkHUD::AppletFont(FreeSans24pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -5, 3)
+#define FREESANS_18PT_WIN1253 InkHUD::AppletFont(FreeSans18pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -4, 2)
#define FREESANS_12PT_WIN1253 InkHUD::AppletFont(FreeSans12pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -3, 1)
#define FREESANS_9PT_WIN1253 InkHUD::AppletFont(FreeSans9pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -2, -1)
#define FREESANS_6PT_WIN1253 InkHUD::AppletFont(FreeSans6pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -1, -2)
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
index 06ddd5bb0..63ccaa216 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
@@ -43,8 +43,8 @@ void InkHUD::MapApplet::onRender(bool full)
// Add white halo outline first
constexpr int outlinePad = 1;
- int boxSize = 11;
- int radius = 2; // rounded corner radius
+ int boxSize = fontSmall.lineHeight() + 2; // scale with font so digit fits
+ int radius = max(2, boxSize / 6);
// White halo background
fillRoundedRect(x, y, boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), radius + 1, WHITE);
@@ -143,17 +143,19 @@ void InkHUD::MapApplet::onRender(bool full)
int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
+ int16_t r = fontSmall.lineHeight() / 2; // scale marker with font
+
// White fill background + halo
- fillCircle(centerX, centerY, 8, WHITE); // big white base
- drawCircle(centerX, centerY, 8, WHITE); // crisp edge
+ fillCircle(centerX, centerY, r + 2, WHITE);
+ drawCircle(centerX, centerY, r + 2, WHITE);
// Black bullseye on top
- drawCircle(centerX, centerY, 6, BLACK);
- fillCircle(centerX, centerY, 2, BLACK);
+ drawCircle(centerX, centerY, r, BLACK);
+ fillCircle(centerX, centerY, max(2, r / 4), BLACK);
// Crosshairs
- drawLine(centerX - 8, centerY, centerX + 8, centerY, BLACK);
- drawLine(centerX, centerY - 8, centerX, centerY + 8, BLACK);
+ drawLine(centerX - r - 2, centerY, centerX + r + 2, centerY, BLACK);
+ drawLine(centerX, centerY - r - 2, centerX, centerY + r + 2, BLACK);
}
}
@@ -382,9 +384,9 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
constexpr uint16_t paddingH = 2;
constexpr uint16_t paddingW = 4;
- uint16_t paddingInnerW = 2; // Zero'd out if no text
- constexpr uint16_t markerSizeMax = 12; // Size of cross (if marker uses a cross)
- constexpr uint16_t markerSizeMin = 5;
+ uint16_t paddingInnerW = 2; // Zero'd out if no text
+ uint16_t markerSizeMax = fontSmall.lineHeight(); // Scale cross with font
+ uint16_t markerSizeMin = max(5, fontSmall.lineHeight() / 3);
int16_t textX;
int16_t textY;
diff --git a/src/graphics/niche/InkHUD/Applets/System/AppSwitcher/AppSwitcherApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/AppSwitcher/AppSwitcherApplet.cpp
new file mode 100644
index 000000000..005327bae
--- /dev/null
+++ b/src/graphics/niche/InkHUD/Applets/System/AppSwitcher/AppSwitcherApplet.cpp
@@ -0,0 +1,545 @@
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+
+#include "./AppSwitcherApplet.h"
+
+#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/niche/InkHUD/Tile.h"
+
+#include
+#include
+
+using namespace NicheGraphics;
+
+namespace
+{
+static constexpr uint16_t BODY_MARGIN_X = 8;
+static constexpr uint16_t BODY_MARGIN_Y = 6;
+static constexpr uint16_t SLOT_GAP_X = 8;
+static constexpr uint16_t SLOT_GAP_Y = 8;
+static constexpr uint8_t ICON_RADIUS = 8;
+static constexpr uint16_t FOOTER_PAD = 4;
+static constexpr uint16_t LABEL_BOTTOM_PAD = 1;
+static constexpr uint16_t LABEL_GAP_Y = 1;
+static constexpr uint16_t TITLE_H_PAD = 8;
+
+static constexpr uint8_t GRID_COLS = 3;
+static constexpr uint8_t GRID_ROWS = 4;
+static constexpr uint8_t ICON_NATIVE_SIZE = 48;
+static constexpr uint8_t ICON_OUTLINE_STROKE = 1;
+
+enum class IconKind : uint8_t { GENERIC, ALL_MESSAGES, DMS, CHANNEL, POSITIONS, RECENTS, HEARD, FAVORITES };
+
+struct GridLayout {
+ uint16_t footerH = 0;
+ uint16_t bodyTop = 0;
+ uint16_t bodyBottom = 0;
+ uint16_t slotW = 0;
+ uint16_t slotH = 0;
+ uint16_t iconBox = 0;
+};
+
+/*
+ * Icons sourced from Material Design Icons PNG set (Apache 2.0):
+ * https://github.com/material-icons/material-icons-png
+ *
+ * Families used: outline-2x (48x48)
+ * apps, markunread, chat, forum, place, history, hearing, star_border
+ */
+static constexpr uint64_t icon_generic_apps[48] = {
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+ 0x000000000000ULL, 0x000000000000ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL,
+ 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x000000000000ULL, 0x000000000000ULL,
+ 0x000000000000ULL, 0x000000000000ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL,
+ 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x000000000000ULL, 0x000000000000ULL,
+ 0x000000000000ULL, 0x000000000000ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL,
+ 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x00FF0FF0FF00ULL, 0x000000000000ULL, 0x000000000000ULL,
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+};
+
+static constexpr uint64_t icon_all_messages[48] = {
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+ 0x000000000000ULL, 0x000000000000ULL, 0x07FFFFFFFFE0ULL, 0x0FFFFFFFFFF0ULL, 0x0FFFFFFFFFF0ULL, 0x0FFFFFFFFFF0ULL,
+ 0x0FC0000003F0ULL, 0x0FE0000007F0ULL, 0x0FF800001FF0ULL, 0x0FFE00007FF0ULL, 0x0FFF0000FFF0ULL, 0x0F7FC003FEF0ULL,
+ 0x0F1FE007F8F0ULL, 0x0F07F81FE0F0ULL, 0x0F03FE7FC0F0ULL, 0x0F00FFFF00F0ULL, 0x0F007FFE00F0ULL, 0x0F001FF800F0ULL,
+ 0x0F0007E000F0ULL, 0x0F0003C000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL,
+ 0x0F00000000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL,
+ 0x0FFFFFFFFFF0ULL, 0x0FFFFFFFFFF0ULL, 0x0FFFFFFFFFF0ULL, 0x07FFFFFFFFE0ULL, 0x000000000000ULL, 0x000000000000ULL,
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+};
+
+static constexpr uint64_t icon_dms[48] = {
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x07FFFFFFFFE0ULL, 0x0FFFFFFFFFF0ULL,
+ 0x0FFFFFFFFFF0ULL, 0x0FFFFFFFFFF0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL,
+ 0x0F0FFFFFF0F0ULL, 0x0F0FFFFFF0F0ULL, 0x0F0FFFFFF0F0ULL, 0x0F0FFFFFF0F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL,
+ 0x0F0FFFFFF0F0ULL, 0x0F0FFFFFF0F0ULL, 0x0F0FFFFFF0F0ULL, 0x0F0FFFFFF0F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL,
+ 0x0F0FFFF000F0ULL, 0x0F0FFFF000F0ULL, 0x0F0FFFF000F0ULL, 0x0F0FFFF000F0ULL, 0x0F00000000F0ULL, 0x0F00000000F0ULL,
+ 0x0F00000000F0ULL, 0x0F00000000F0ULL, 0x0F7FFFFFFFF0ULL, 0x0FFFFFFFFFF0ULL, 0x0FFFFFFFFFF0ULL, 0x0FFFFFFFFFE0ULL,
+ 0x0FF000000000ULL, 0x0FE000000000ULL, 0x0FC000000000ULL, 0x0F8000000000ULL, 0x0F0000000000ULL, 0x0E0000000000ULL,
+ 0x0C0000000000ULL, 0x080000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+};
+
+static constexpr uint64_t icon_channel[48] = {
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x0FFFFFFFC000ULL, 0x0FFFFFFFC000ULL,
+ 0x0FFFFFFFC000ULL, 0x0FFFFFFFC000ULL, 0x0F000003C000ULL, 0x0F000003C000ULL, 0x0F000003C000ULL, 0x0F000003C000ULL,
+ 0x0F000003C3F0ULL, 0x0F000003C3F0ULL, 0x0F000003C3F0ULL, 0x0F000003C3F0ULL, 0x0F000003C3F0ULL, 0x0F000003C3F0ULL,
+ 0x0F000003C3F0ULL, 0x0F000003C3F0ULL, 0x0F000003C3F0ULL, 0x0F000003C3F0ULL, 0x0F7FFFFFC3F0ULL, 0x0FFFFFFFC3F0ULL,
+ 0x0FFFFFFFC3F0ULL, 0x0FFFFFFFC3F0ULL, 0x0FF0000003F0ULL, 0x0FE0000003F0ULL, 0x0FC0000003F0ULL, 0x0F80000003F0ULL,
+ 0x0F0FFFFFFFF0ULL, 0x0E0FFFFFFFF0ULL, 0x0C0FFFFFFFF0ULL, 0x080FFFFFFFF0ULL, 0x000FFFFFFFF0ULL, 0x000FFFFFFFF0ULL,
+ 0x000000000FF0ULL, 0x0000000007F0ULL, 0x0000000003F0ULL, 0x0000000001F0ULL, 0x0000000000F0ULL, 0x000000000070ULL,
+ 0x000000000030ULL, 0x000000000010ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+};
+
+static constexpr uint64_t icon_positions[48] = {
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x00001FF80000ULL, 0x00007FFE0000ULL,
+ 0x0001FFFF8000ULL, 0x0003FFFFC000ULL, 0x0007FFFFE000ULL, 0x000FF00FF000ULL, 0x000FC003F000ULL, 0x001F8001F800ULL,
+ 0x001F0000F800ULL, 0x003F07E0FC00ULL, 0x003E0FF07C00ULL, 0x003E1FF87C00ULL, 0x003E1FF87C00ULL, 0x003E1FF87C00ULL,
+ 0x003E1FF87C00ULL, 0x003E1FF87C00ULL, 0x003E1FF87C00ULL, 0x003E0FF07C00ULL, 0x003E07E07C00ULL, 0x001F0000F800ULL,
+ 0x001F0000F800ULL, 0x001F8001F800ULL, 0x000F8001F000ULL, 0x000FC003F000ULL, 0x0007C003E000ULL, 0x0007E007E000ULL,
+ 0x0003F00FC000ULL, 0x0003F00FC000ULL, 0x0001F81F8000ULL, 0x0001FC3F8000ULL, 0x0000FC3F0000ULL, 0x00007E7E0000ULL,
+ 0x00003FFC0000ULL, 0x00003FFC0000ULL, 0x00001FF80000ULL, 0x00000FF00000ULL, 0x00000FF00000ULL, 0x000007E00000ULL,
+ 0x000003C00000ULL, 0x000001800000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+};
+
+static constexpr uint64_t icon_recents[48] = {
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+ 0x00000FFF0000ULL, 0x00003FFFC000ULL, 0x0000FFFFF000ULL, 0x0001FFFFF800ULL, 0x0007FFFFFE00ULL, 0x000FF801FF00ULL,
+ 0x000FE0007F00ULL, 0x001FC0003F80ULL, 0x003F00000FC0ULL, 0x003F00000FC0ULL, 0x007E00E007E0ULL, 0x007C00E003E0ULL,
+ 0x00FC00E003F0ULL, 0x00F800E001F0ULL, 0x00F800E001F0ULL, 0x00F800E001F0ULL, 0x00F800E001F0ULL, 0x00F800E001F0ULL,
+ 0x3FFFC0F001F0ULL, 0x1FFF80FC01F0ULL, 0x0FFF00FF01F0ULL, 0x07FE003F81F0ULL, 0x03FC001FC1F0ULL, 0x01F80007C3F0ULL,
+ 0x00F0000183E0ULL, 0x0060000007E0ULL, 0x000000000FC0ULL, 0x000000000FC0ULL, 0x0001C0003F80ULL, 0x0003E0007F00ULL,
+ 0x0007F801FF00ULL, 0x0007FFFFFE00ULL, 0x0001FFFFF800ULL, 0x0000FFFFF000ULL, 0x00003FFFC000ULL, 0x00000FFF0000ULL,
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+};
+
+static constexpr uint64_t icon_heard[48] = {
+ 0x000000000000ULL, 0x000000000000ULL, 0x000800000000ULL, 0x001C00000000ULL, 0x003E01FF8000ULL, 0x007F07FFE000ULL,
+ 0x007E1FFFF800ULL, 0x00FC3FFFFC00ULL, 0x00F87FFFFE00ULL, 0x01F8FF00FF00ULL, 0x01F0FC003F00ULL, 0x01F1F8001F80ULL,
+ 0x03E1F0000F80ULL, 0x03E3F07E0FC0ULL, 0x03E3E0FF07C0ULL, 0x03E3E1FF87C0ULL, 0x03E3E1FF87C0ULL, 0x03E3E1FF87C0ULL,
+ 0x03E3E1FF8000ULL, 0x03E3E1FF8000ULL, 0x03E3E1FF8000ULL, 0x03E3E0FF0000ULL, 0x03E3F07E0000ULL, 0x03E1F0000000ULL,
+ 0x01F1F8000000ULL, 0x01F1F8000000ULL, 0x01F8FC000000ULL, 0x00F87E000000ULL, 0x00FC7F800000ULL, 0x007E3FC00000ULL,
+ 0x007F1FE00000ULL, 0x003E0FF00000ULL, 0x001C03F00000ULL, 0x000801F80000ULL, 0x000000F80000ULL, 0x000000FC0000ULL,
+ 0x0000007C07C0ULL, 0x0000007E07C0ULL, 0x0000003F0FC0ULL, 0x0000003FFFC0ULL, 0x0000001FFF80ULL, 0x0000000FFF00ULL,
+ 0x00000007FE00ULL, 0x00000003FC00ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+};
+
+static constexpr uint64_t icon_favorites[48] = {
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000001800000ULL, 0x000001800000ULL,
+ 0x000003C00000ULL, 0x000003C00000ULL, 0x000003C00000ULL, 0x000007E00000ULL, 0x000007E00000ULL, 0x00000FF00000ULL,
+ 0x00000FF00000ULL, 0x00001FF80000ULL, 0x00001FF80000ULL, 0x00001E780000ULL, 0x00003E7C0000ULL, 0x003FFC3FFC00ULL,
+ 0x0FFFFC3FFFF0ULL, 0x0FFFF81FFFF0ULL, 0x03FFF81FFFC0ULL, 0x01F800001F80ULL, 0x00FC00003F00ULL, 0x007E00007E00ULL,
+ 0x003F8001FC00ULL, 0x001FC003F800ULL, 0x000FE007F000ULL, 0x0003E007C000ULL, 0x0003E007C000ULL, 0x0003C003C000ULL,
+ 0x0003C003C000ULL, 0x0003C3C3C000ULL, 0x0007CFF3E000ULL, 0x00079FF9E000ULL, 0x0007FFFFE000ULL, 0x0007FE7FE000ULL,
+ 0x000FFC3FF000ULL, 0x000FF00FF000ULL, 0x000FC003F000ULL, 0x000F8001F000ULL, 0x001E00007000ULL, 0x001800001800ULL,
+ 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL, 0x000000000000ULL,
+};
+
+using IconBitmap = const uint64_t *;
+
+IconBitmap iconBitmapForKind(IconKind kind)
+{
+ switch (kind) {
+ case IconKind::ALL_MESSAGES:
+ return icon_all_messages;
+ case IconKind::DMS:
+ return icon_dms;
+ case IconKind::CHANNEL:
+ return icon_channel;
+ case IconKind::POSITIONS:
+ return icon_positions;
+ case IconKind::RECENTS:
+ return icon_recents;
+ case IconKind::HEARD:
+ return icon_heard;
+ case IconKind::FAVORITES:
+ return icon_favorites;
+ case IconKind::GENERIC:
+ default:
+ return icon_generic_apps;
+ }
+}
+
+GridLayout computeLayout(const InkHUD::Applet *applet)
+{
+ GridLayout layout;
+
+ const uint16_t w = applet->width();
+ const uint16_t h = applet->height();
+
+ layout.footerH = InkHUD::Applet::fontSmall.lineHeight() + (FOOTER_PAD * 2);
+ layout.bodyTop = BODY_MARGIN_Y;
+ layout.bodyBottom = (h > (layout.footerH + BODY_MARGIN_Y)) ? (h - layout.footerH - BODY_MARGIN_Y) : layout.bodyTop;
+
+ const uint16_t bodyW = (w > (BODY_MARGIN_X * 2)) ? (w - (BODY_MARGIN_X * 2)) : 1;
+ const uint16_t bodyH = (layout.bodyBottom > layout.bodyTop) ? (layout.bodyBottom - layout.bodyTop) : 1;
+ const uint16_t gapsX = SLOT_GAP_X * (GRID_COLS - 1);
+ const uint16_t gapsY = SLOT_GAP_Y * (GRID_ROWS - 1);
+
+ layout.slotW = (bodyW > gapsX) ? ((bodyW - gapsX) / GRID_COLS) : 1;
+ layout.slotH = (bodyH > gapsY) ? ((bodyH - gapsY) / GRID_ROWS) : 1;
+
+ const uint16_t maxIconW = (layout.slotW > 6) ? (layout.slotW - 6) : layout.slotW;
+ const uint16_t maxIconH = (layout.slotH > (InkHUD::Applet::fontSmall.lineHeight() + LABEL_GAP_Y + LABEL_BOTTOM_PAD + 6))
+ ? (layout.slotH - InkHUD::Applet::fontSmall.lineHeight() - LABEL_GAP_Y - LABEL_BOTTOM_PAD - 6)
+ : layout.slotH / 2;
+ layout.iconBox = std::max(20, std::min(maxIconW, maxIconH));
+ return layout;
+}
+
+std::string lowercase(const char *name)
+{
+ if (!name)
+ return "";
+ std::string out(name);
+ std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return (char)std::tolower(c); });
+ return out;
+}
+
+IconKind iconKindForAppletName(const char *name)
+{
+ const std::string lower = lowercase(name);
+ if (lower.find("all message") != std::string::npos || lower.find("messages") != std::string::npos)
+ return IconKind::ALL_MESSAGES;
+ if (lower.find("dm") != std::string::npos)
+ return IconKind::DMS;
+ if (lower.find("channel") != std::string::npos)
+ return IconKind::CHANNEL;
+ if (lower.find("position") != std::string::npos)
+ return IconKind::POSITIONS;
+ if (lower.find("recent") != std::string::npos)
+ return IconKind::RECENTS;
+ if (lower.find("heard") != std::string::npos)
+ return IconKind::HEARD;
+ if (lower.find("favorite") != std::string::npos)
+ return IconKind::FAVORITES;
+ return IconKind::GENERIC;
+}
+
+void drawIconBitmapScaled(InkHUD::Applet *applet, IconBitmap bmp48, int16_t left, int16_t top, uint16_t boxSize, uint16_t color)
+{
+ if (!bmp48 || boxSize == 0)
+ return;
+
+ auto srcOn = [bmp48](int16_t sx, int16_t sy) -> bool {
+ if (sx < 0 || sy < 0 || sx >= ICON_NATIVE_SIZE || sy >= ICON_NATIVE_SIZE)
+ return false;
+ const uint64_t rowBits = bmp48[sy];
+ return (rowBits & (1ULL << (47 - sx))) != 0;
+ };
+
+ for (uint16_t y = 0; y < boxSize; y++) {
+ const uint8_t srcY = (uint8_t)((y * ICON_NATIVE_SIZE) / boxSize);
+ for (uint16_t x = 0; x < boxSize; x++) {
+ const uint8_t srcX = (uint8_t)((x * ICON_NATIVE_SIZE) / boxSize);
+ if (!srcOn(srcX, srcY))
+ continue;
+
+ const uint16_t w = std::min(ICON_OUTLINE_STROKE, boxSize - x);
+ const uint16_t h = std::min(ICON_OUTLINE_STROKE, boxSize - y);
+ applet->fillRect(left + x, top + y, w, h, color);
+ }
+ }
+}
+} // namespace
+
+InkHUD::AppSwitcherApplet::AppSwitcherApplet()
+{
+ alwaysRender = true;
+}
+
+void InkHUD::AppSwitcherApplet::rebuildActiveAppletList()
+{
+ activeAppletIndices.clear();
+
+ const auto &settings = inkhud->persistence->settings;
+ const uint8_t tileCount = std::min(settings.userTiles.count, Persistence::MAX_TILES_GLOBAL);
+ const uint8_t focusedTile = (tileCount > 0) ? std::min(settings.userTiles.focused, tileCount - 1) : 0;
+
+ // Applets displayed on other tiles should not be selectable here.
+ std::vector occupiedOnOtherTiles(inkhud->userApplets.size(), false);
+ for (uint8_t tile = 0; tile < tileCount; tile++) {
+ if (tile == focusedTile)
+ continue;
+
+ const uint8_t appletIndex = settings.userTiles.displayedUserApplet[tile];
+ if (appletIndex < occupiedOnOtherTiles.size())
+ occupiedOnOtherTiles[appletIndex] = true;
+ }
+
+ for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
+ Applet *a = inkhud->userApplets.at(i);
+ if (a && a->isActive() && !occupiedOnOtherTiles[i])
+ activeAppletIndices.push_back(i);
+ }
+}
+
+uint8_t InkHUD::AppSwitcherApplet::cardsPerPage() const
+{
+ return GRID_COLS * GRID_ROWS;
+}
+
+uint8_t InkHUD::AppSwitcherApplet::currentPage() const
+{
+ const uint8_t cpp = cardsPerPage();
+ if (cpp == 0)
+ return 0;
+ return selectedIndex / cpp;
+}
+
+void InkHUD::AppSwitcherApplet::stepPage(int8_t delta)
+{
+ if (activeAppletIndices.empty())
+ return;
+
+ const uint8_t cpp = cardsPerPage();
+ const uint8_t pageCount = std::max(1, (activeAppletIndices.size() + cpp - 1) / cpp);
+ int16_t nextPage = (int16_t)currentPage() + delta;
+ while (nextPage < 0)
+ nextPage += pageCount;
+ while (nextPage >= pageCount)
+ nextPage -= pageCount;
+
+ selectedIndex = std::min((uint8_t)(nextPage * cpp), activeAppletIndices.size() - 1);
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+}
+
+void InkHUD::AppSwitcherApplet::clampSelection()
+{
+ if (activeAppletIndices.empty()) {
+ selectedIndex = 0;
+ return;
+ }
+
+ if (selectedIndex >= activeAppletIndices.size())
+ selectedIndex = activeAppletIndices.size() - 1;
+}
+
+void InkHUD::AppSwitcherApplet::activateSelectedApplet()
+{
+ if (activeAppletIndices.empty()) {
+ sendToBackground();
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+ return;
+ }
+
+ const uint8_t appletIndex = activeAppletIndices.at(selectedIndex);
+
+ sendToBackground();
+ inkhud->showApplet(appletIndex);
+}
+
+void InkHUD::AppSwitcherApplet::onForeground()
+{
+ rebuildActiveAppletList();
+ clampSelection();
+ handleInput = true;
+ lockRequests = true;
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+}
+
+void InkHUD::AppSwitcherApplet::onBackground()
+{
+ handleInput = false;
+ lockRequests = false;
+
+ if (borrowedTileOwner)
+ borrowedTileOwner->bringToForeground();
+
+ Tile *t = getTile();
+ if (t)
+ t->assignApplet(borrowedTileOwner);
+ borrowedTileOwner = nullptr;
+}
+
+void InkHUD::AppSwitcherApplet::show(Tile *t)
+{
+ if (!t)
+ return;
+
+ borrowedTileOwner = t->getAssignedApplet();
+ if (borrowedTileOwner)
+ borrowedTileOwner->sendToBackground();
+
+ t->assignApplet(this);
+ bringToForeground();
+}
+
+void InkHUD::AppSwitcherApplet::onRender(bool full)
+{
+ (void)full;
+
+ const GridLayout layout = computeLayout(this);
+ const uint8_t cpp = cardsPerPage();
+ const uint8_t page = currentPage();
+ const uint8_t pageStart = page * cpp;
+
+ setFont(fontMedium);
+ setTextColor(BLACK);
+
+ fillRect(0, 0, width(), height(), WHITE);
+ drawRect(0, 0, width(), height(), BLACK);
+
+ if (activeAppletIndices.empty()) {
+ setFont(fontSmall);
+ printAt(width() / 2, height() / 2, "No Available Applets", CENTER, MIDDLE);
+ return;
+ }
+
+ for (uint8_t i = 0; i < cpp; i++) {
+ const uint8_t idx = pageStart + i;
+ if (idx >= activeAppletIndices.size())
+ break;
+
+ const uint8_t row = i / GRID_COLS;
+ const uint8_t col = i % GRID_COLS;
+ const int16_t slotL = BODY_MARGIN_X + (col * (layout.slotW + SLOT_GAP_X));
+ const int16_t slotT = layout.bodyTop + (row * (layout.slotH + SLOT_GAP_Y));
+ const bool selected = (idx == selectedIndex);
+
+ const uint8_t appletIndex = activeAppletIndices.at(idx);
+ Applet *a = inkhud->userApplets.at(appletIndex);
+ if (!a)
+ continue;
+
+ const int16_t iconLeft = slotL + ((layout.slotW - layout.iconBox) / 2);
+ const int16_t iconTop = slotT + 1;
+
+ // Requested style: icon in outlined rounded square only (no filled box, no outer app card).
+ drawRoundRect(iconLeft, iconTop, layout.iconBox, layout.iconBox, ICON_RADIUS, BLACK);
+ if (selected)
+ drawRoundRect(iconLeft + 2, iconTop + 2, layout.iconBox - 4, layout.iconBox - 4, ICON_RADIUS, BLACK);
+
+ const IconBitmap bmp = iconBitmapForKind(iconKindForAppletName(a->name));
+ drawIconBitmapScaled(this, bmp, iconLeft + 3, iconTop + 3, layout.iconBox - 6, BLACK);
+
+ setFont(fontSmall);
+ std::string label = a->name ? a->name : "Applet";
+ const uint16_t maxLabelW = layout.slotW > 4 ? (layout.slotW - 4) : layout.slotW;
+ if (getTextWidth(label) > maxLabelW) {
+ while (!label.empty() && getTextWidth(label + "...") > maxLabelW)
+ label.pop_back();
+ label = label.empty() ? "..." : label + "...";
+ }
+ const int16_t labelY = iconTop + layout.iconBox + LABEL_GAP_Y;
+ setTextColor(BLACK);
+ printAt(slotL + (layout.slotW / 2), labelY, label.c_str(), CENTER, TOP);
+
+ if (a->isForeground())
+ fillCircle(iconLeft + layout.iconBox - 4, iconTop + 4, 2, BLACK);
+ }
+
+ const uint8_t pageCount = std::max(1, (activeAppletIndices.size() + cpp - 1) / cpp);
+ if (pageCount > 1) {
+ setFont(fontSmall);
+ setTextColor(BLACK);
+ const int16_t footerY = height() - layout.footerH + FOOTER_PAD;
+ printAt(TITLE_H_PAD, footerY, "<", LEFT, TOP);
+ printAt(width() - TITLE_H_PAD, footerY, ">", RIGHT, TOP);
+ const std::string pageText = std::to_string(page + 1) + "/" + std::to_string(pageCount);
+ printAt(width() / 2, footerY, pageText.c_str(), CENTER, TOP);
+ }
+}
+
+bool InkHUD::AppSwitcherApplet::onTouchPoint(uint16_t x, uint16_t y, bool longPress)
+{
+ (void)longPress;
+
+ Tile *t = getTile();
+ if (!t || activeAppletIndices.empty())
+ return true;
+
+ const uint16_t tileL = t->getLeft();
+ const uint16_t tileT = t->getTop();
+ const uint16_t tileR = tileL + t->getWidth();
+ const uint16_t tileB = tileT + t->getHeight();
+ if (x < tileL || x >= tileR || y < tileT || y >= tileB)
+ return false;
+
+ const GridLayout layout = computeLayout(this);
+ const uint8_t cpp = cardsPerPage();
+ const uint8_t page = currentPage();
+ const uint8_t pageStart = page * cpp;
+ const int16_t localX = (int16_t)x - (int16_t)tileL;
+ const int16_t localY = (int16_t)y - (int16_t)tileT;
+
+ for (uint8_t i = 0; i < cpp; i++) {
+ const uint8_t idx = pageStart + i;
+ if (idx >= activeAppletIndices.size())
+ break;
+
+ const uint8_t row = i / GRID_COLS;
+ const uint8_t col = i % GRID_COLS;
+ const int16_t slotL = BODY_MARGIN_X + (col * (layout.slotW + SLOT_GAP_X));
+ const int16_t slotT = layout.bodyTop + (row * (layout.slotH + SLOT_GAP_Y));
+
+ if (localX < slotL || localX >= (slotL + (int16_t)layout.slotW))
+ continue;
+ if (localY < slotT || localY >= (slotT + (int16_t)layout.slotH))
+ continue;
+
+ selectedIndex = idx;
+ clampSelection();
+ activateSelectedApplet();
+ return true;
+ }
+
+ const uint8_t pageCount = std::max(1, (activeAppletIndices.size() + cpp - 1) / cpp);
+ if (pageCount <= 1)
+ return true;
+
+ const int16_t footerTop = height() - layout.footerH;
+ if (localY >= footerTop) {
+ if (localX < (int16_t)(width() / 3))
+ stepPage(-1);
+ else if (localX >= (int16_t)((width() * 2) / 3))
+ stepPage(1);
+ }
+
+ return true;
+}
+
+void InkHUD::AppSwitcherApplet::onButtonShortPress()
+{
+ if (activeAppletIndices.empty())
+ return;
+
+ selectedIndex = (selectedIndex + 1) % activeAppletIndices.size();
+ clampSelection();
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+}
+
+void InkHUD::AppSwitcherApplet::onButtonLongPress()
+{
+ activateSelectedApplet();
+}
+
+void InkHUD::AppSwitcherApplet::onExitShort()
+{
+ sendToBackground();
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+}
+
+void InkHUD::AppSwitcherApplet::onNavUp()
+{
+ if (activeAppletIndices.empty())
+ return;
+
+ if (selectedIndex == 0)
+ selectedIndex = activeAppletIndices.size() - 1;
+ else
+ selectedIndex--;
+
+ clampSelection();
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+}
+
+void InkHUD::AppSwitcherApplet::onNavDown()
+{
+ if (activeAppletIndices.empty())
+ return;
+
+ selectedIndex = (selectedIndex + 1) % activeAppletIndices.size();
+ clampSelection();
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+}
+
+#endif
diff --git a/src/graphics/niche/InkHUD/Applets/System/AppSwitcher/AppSwitcherApplet.h b/src/graphics/niche/InkHUD/Applets/System/AppSwitcher/AppSwitcherApplet.h
new file mode 100644
index 000000000..01ea561ae
--- /dev/null
+++ b/src/graphics/niche/InkHUD/Applets/System/AppSwitcher/AppSwitcherApplet.h
@@ -0,0 +1,51 @@
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+
+#pragma once
+
+#include "configuration.h"
+
+#include "graphics/niche/InkHUD/SystemApplet.h"
+
+#include
+
+namespace NicheGraphics::InkHUD
+{
+
+class Tile;
+
+class AppSwitcherApplet : public SystemApplet
+{
+ public:
+ AppSwitcherApplet();
+
+ void onForeground() override;
+ void onBackground() override;
+ void onRender(bool full) override;
+
+ void onButtonShortPress() override;
+ void onButtonLongPress() override;
+ void onExitShort() override;
+ void onNavUp() override;
+ void onNavDown() override;
+ bool onTouchPoint(uint16_t x, uint16_t y, bool longPress) override;
+
+ // Open the app switcher on a user tile and temporarily replace the tile's owner.
+ void show(Tile *t);
+
+ private:
+ void rebuildActiveAppletList();
+ void clampSelection();
+ uint8_t cardsPerPage() const;
+ uint8_t currentPage() const;
+ void stepPage(int8_t delta);
+ void activateSelectedApplet();
+
+ std::vector activeAppletIndices;
+ uint8_t selectedIndex = 0;
+
+ Applet *borrowedTileOwner = nullptr;
+};
+
+} // namespace NicheGraphics::InkHUD
+
+#endif
diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp
index 57581d56b..cb2b59953 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp
@@ -1,155 +1,100 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./KeyboardApplet.h"
+#include
+
using namespace NicheGraphics;
+namespace
+{
+bool usePortraitKeyboardSizing()
+{
+ InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
+ return inkhud && inkhud->height() > inkhud->width();
+}
+} // namespace
+
InkHUD::KeyboardApplet::KeyboardApplet()
{
- // Calculate row widths
- for (uint8_t row = 0; row < KBD_ROWS; row++) {
- rowWidths[row] = 0;
- for (uint8_t col = 0; col < KBD_COLS; col++)
- rowWidths[row] += keyWidths[row * KBD_COLS + col];
- }
+ mode = MODE_TEXT;
+ lastTypingMode = MODE_TEXT;
+ emotePage = 0;
+ selectedKey = 0;
+ prevSelectedKey = 0;
+ normalizeSelection();
}
void InkHUD::KeyboardApplet::onRender(bool full)
{
- uint16_t em = fontSmall.lineHeight(); // 16 pt
- uint16_t keyH = Y(1.0) / KBD_ROWS;
- int16_t keyTopPadding = (keyH - fontSmall.lineHeight()) / 2;
+ const bool showSelection = showSelectionHighlight();
- if (full) { // Draw full keyboard
- for (uint8_t row = 0; row < KBD_ROWS; row++) {
-
- // Calculate the remaining space to be used as padding
- int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
-
- // Draw keys
- uint16_t xPos = 0;
- for (uint8_t col = 0; col < KBD_COLS; col++) {
- Color fgcolor = BLACK;
- uint8_t index = row * KBD_COLS + col;
- uint16_t keyX = ((xPos * em) >> 4) + ((col * keyXPadding) / (KBD_COLS - 1));
- uint16_t keyY = row * keyH;
- uint16_t keyW = (keyWidths[index] * em) >> 4;
- if (index == selectedKey) {
- fgcolor = WHITE;
- fillRect(keyX, keyY, keyW, keyH, BLACK);
- }
- drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[index], fgcolor);
- xPos += keyWidths[index];
- }
- }
- } else { // Only draw the difference
- if (selectedKey != prevSelectedKey) {
- // Draw previously selected key
- uint8_t row = prevSelectedKey / KBD_COLS;
- int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
- uint16_t xPos = 0;
- for (uint8_t i = prevSelectedKey - (prevSelectedKey % KBD_COLS); i < prevSelectedKey; i++)
- xPos += keyWidths[i];
- uint16_t keyX = ((xPos * em) >> 4) + (((prevSelectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1));
- uint16_t keyY = row * keyH;
- uint16_t keyW = (keyWidths[prevSelectedKey] * em) >> 4;
- fillRect(keyX, keyY, keyW, keyH, WHITE);
- drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[prevSelectedKey], BLACK);
-
- // Draw newly selected key
- row = selectedKey / KBD_COLS;
- keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
- xPos = 0;
- for (uint8_t i = selectedKey - (selectedKey % KBD_COLS); i < selectedKey; i++)
- xPos += keyWidths[i];
- keyX = ((xPos * em) >> 4) + (((selectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1));
- keyY = row * keyH;
- keyW = (keyWidths[selectedKey] * em) >> 4;
- fillRect(keyX, keyY, keyW, keyH, BLACK);
- drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[selectedKey], WHITE);
- }
+ if (full) {
+ for (uint8_t i = 0; i < KBD_KEY_COUNT; i++)
+ drawKey(i, showSelection && i == selectedKey);
+ } else if (showSelection && selectedKey != prevSelectedKey) {
+ drawKey(prevSelectedKey, false);
+ drawKey(selectedKey, true);
}
prevSelectedKey = selectedKey;
}
-// Draw the key label corresponding to the char
-// for most keys it draws the character itself
-// for ['\b', '\n', ' ', '\x1b'] it draws special glyphs
-void InkHUD::KeyboardApplet::drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color)
+void InkHUD::KeyboardApplet::drawKey(uint8_t index, bool selected)
{
- if (key == '\b') {
- // Draw backspace glyph: 13 x 9 px
- /**
- * [][][][][][][][][]
- * [][] []
- * [][] [] [] []
- * [][] [] [] []
- * [][] [] []
- * [][] [] [] []
- * [][] [] [] []
- * [][] []
- * [][][][][][][][][]
- */
- const uint8_t bsBitmap[] = {0x0f, 0xf8, 0x18, 0x08, 0x32, 0x28, 0x61, 0x48, 0xc0,
- 0x88, 0x61, 0x48, 0x32, 0x28, 0x18, 0x08, 0x0f, 0xf8};
- uint16_t leftPadding = (width - 13) >> 1;
- drawBitmap(left + leftPadding, top + 1, bsBitmap, 13, 9, color);
- } else if (key == '\n') {
- // Draw done glyph: 12 x 9 px
- /**
- * [][]
- * [][]
- * [][]
- * [][]
- * [][]
- * [][] [][]
- * [][] [][]
- * [][][]
- * []
- */
- const uint8_t doneBitmap[] = {0x00, 0x30, 0x00, 0x60, 0x00, 0xc0, 0x01, 0x80, 0x03,
- 0x00, 0xc6, 0x00, 0x6c, 0x00, 0x38, 0x00, 0x10, 0x00};
- uint16_t leftPadding = (width - 12) >> 1;
- drawBitmap(left + leftPadding, top + 1, doneBitmap, 12, 9, color);
- } else if (key == ' ') {
- // Draw space glyph: 13 x 9 px
- /**
- *
- *
- *
- *
- * [] []
- * [] []
- * [][][][][][][][][][][][][]
- *
- *
- */
- const uint8_t spaceBitmap[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
- 0x08, 0x80, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00};
- uint16_t leftPadding = (width - 13) >> 1;
- drawBitmap(left + leftPadding, top + 1, spaceBitmap, 13, 9, color);
- } else if (key == '\x1b') {
- setTextColor(color);
- std::string keyText = "ESC";
- uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1;
- printAt(left + leftPadding, top, keyText);
- } else {
- setTextColor(color);
- if (key >= 0x61)
- key -= 32; // capitalize
- std::string keyText = std::string(1, key);
- uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1;
- printAt(left + leftPadding, top, keyText);
+ uint16_t keyX = 0;
+ uint16_t keyY = 0;
+ uint16_t keyW = 0;
+ uint16_t keyH = 0;
+ if (!getKeyBounds(index, keyX, keyY, keyW, keyH))
+ return;
+ if (keyW == 0 || keyH == 0)
+ return;
+
+ // Translate absolute tile coordinates into applet-local coordinates.
+ const int16_t localX = keyX - getTile()->getLeft();
+ const int16_t localY = keyY - getTile()->getTop();
+ const bool enabled = isKeyEnabledAt(index);
+
+ // Clean background first so hidden keys never leave stale pixels when mode changes.
+ fillRect(localX, localY, keyW, keyH, WHITE);
+
+ if (!enabled)
+ return;
+
+ fillRoundRect(localX, localY, keyW, keyH, KEY_RADIUS, selected ? BLACK : WHITE);
+ drawRoundRect(localX, localY, keyW, keyH, KEY_RADIUS, BLACK);
+
+ const int16_t labelTop = localY + ((keyH - fontSmall.lineHeight()) / 2);
+ drawKeyLabel(localX, labelTop, keyW, getKeyLabelAt(index), selected ? WHITE : BLACK);
+}
+
+void InkHUD::KeyboardApplet::drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, const std::string &label, Color color)
+{
+ if (label.empty())
+ return;
+
+ setTextColor(color);
+ uint16_t textW = getTextWidth(label);
+ if (textW > width) {
+ // Keep labels readable in narrow keys.
+ textW = getTextWidth("..");
+ printAt(left + ((width - textW) >> 1), top, "..");
+ return;
}
+
+ uint16_t leftPadding = (width - textW) >> 1;
+ printAt(left + leftPadding, top, label);
}
void InkHUD::KeyboardApplet::onForeground()
{
- handleInput = true; // Intercept the button input for our applet
-
- // Select the first key
+ handleInput = true;
+ mode = MODE_TEXT;
+ lastTypingMode = MODE_TEXT;
+ emotePage = 0;
selectedKey = 0;
prevSelectedKey = 0;
+ normalizeSelection();
}
void InkHUD::KeyboardApplet::onBackground()
@@ -159,32 +104,12 @@ void InkHUD::KeyboardApplet::onBackground()
void InkHUD::KeyboardApplet::onButtonShortPress()
{
- char key = keys[selectedKey];
- if (key == '\n') {
- inkhud->freeTextDone();
- inkhud->closeKeyboard();
- } else if (key == '\x1b') {
- inkhud->freeTextCancel();
- inkhud->closeKeyboard();
- } else {
- inkhud->freeText(key);
- }
+ inputSelectedKey(false);
}
void InkHUD::KeyboardApplet::onButtonLongPress()
{
- char key = keys[selectedKey];
- if (key == '\n') {
- inkhud->freeTextDone();
- inkhud->closeKeyboard();
- } else if (key == '\x1b') {
- inkhud->freeTextCancel();
- inkhud->closeKeyboard();
- } else {
- if (key >= 0x61)
- key -= 32; // capitalize
- inkhud->freeText(key);
- }
+ inputSelectedKey(true);
}
void InkHUD::KeyboardApplet::onExitShort()
@@ -201,57 +126,377 @@ void InkHUD::KeyboardApplet::onExitLong()
void InkHUD::KeyboardApplet::onNavUp()
{
- if (selectedKey < KBD_COLS) // wrap
+ if (selectedKey < KBD_COLS)
selectedKey += KBD_COLS * (KBD_ROWS - 1);
- else // move 1 row back
+ else
selectedKey -= KBD_COLS;
- // Request rendering over the previously drawn render
- requestUpdate(EInk::UpdateTypes::FAST, false);
- // Force an update to bypass lockRequests
- inkhud->forceUpdate(EInk::UpdateTypes::FAST);
+ normalizeSelection();
+ requestFastKeyboardRefresh();
}
void InkHUD::KeyboardApplet::onNavDown()
{
selectedKey += KBD_COLS;
- selectedKey %= (KBD_COLS * KBD_ROWS);
-
- // Request rendering over the previously drawn render
- requestUpdate(EInk::UpdateTypes::FAST, false);
- // Force an update to bypass lockRequests
- inkhud->forceUpdate(EInk::UpdateTypes::FAST);
+ selectedKey %= KBD_KEY_COUNT;
+ normalizeSelection();
+ requestFastKeyboardRefresh();
}
void InkHUD::KeyboardApplet::onNavLeft()
{
- if (selectedKey % KBD_COLS == 0) // wrap
+ if (selectedKey % KBD_COLS == 0)
selectedKey += KBD_COLS - 1;
- else // move 1 column back
+ else
selectedKey--;
- // Request rendering over the previously drawn render
- requestUpdate(EInk::UpdateTypes::FAST, false);
- // Force an update to bypass lockRequests
- inkhud->forceUpdate(EInk::UpdateTypes::FAST);
+ normalizeSelection();
+ requestFastKeyboardRefresh();
}
void InkHUD::KeyboardApplet::onNavRight()
{
- if (selectedKey % KBD_COLS == KBD_COLS - 1) // wrap
+ if (selectedKey % KBD_COLS == KBD_COLS - 1)
selectedKey -= KBD_COLS - 1;
- else // move 1 column forward
+ else
selectedKey++;
- // Request rendering over the previously drawn render
- requestUpdate(EInk::UpdateTypes::FAST, false);
- // Force an update to bypass lockRequests
- inkhud->forceUpdate(EInk::UpdateTypes::FAST);
+ normalizeSelection();
+ requestFastKeyboardRefresh();
+}
+
+bool InkHUD::KeyboardApplet::onTouchPoint(uint16_t x, uint16_t y, bool longPress)
+{
+ // If touch is outside our tile, let other handlers process it.
+ if (!getTile())
+ return false;
+ const uint16_t tileL = getTile()->getLeft();
+ const uint16_t tileT = getTile()->getTop();
+ const uint16_t tileR = tileL + getTile()->getWidth();
+ const uint16_t tileB = tileT + getTile()->getHeight();
+ if (x < tileL || x >= tileR || y < tileT || y >= tileB)
+ return false;
+
+ const int16_t hitIndex = getKeyIndexAt(x, y);
+ // Consume touches that land in keyboard whitespace/disabled cells so we don't
+ // fall back to generic short-press behavior (which would type the old selection).
+ if (hitIndex < 0)
+ return true;
+
+ const uint8_t newSelected = (uint8_t)hitIndex;
+ if (selectedKey != newSelected) {
+ selectedKey = newSelected;
+ normalizeSelection();
+ if (showSelectionHighlight())
+ requestFastKeyboardRefresh();
+ }
+
+ if (!isKeyEnabledAt(selectedKey))
+ return true;
+
+ inputSelectedKey(longPress);
+ return true;
+}
+
+bool InkHUD::KeyboardApplet::getKeyBounds(uint8_t index, uint16_t &left, uint16_t &top, uint16_t &width, uint16_t &height)
+{
+ if (index >= KBD_KEY_COUNT || !getTile())
+ return false;
+
+ const uint16_t tileW = getTile()->getWidth();
+ const uint16_t tileH = getTile()->getHeight();
+ const uint16_t tileL = getTile()->getLeft();
+ const uint16_t tileT = getTile()->getTop();
+ const uint8_t row = index / KBD_COLS;
+ const uint8_t col = index % KBD_COLS;
+
+ const uint16_t totalGapY = KEY_GAP_Y * (KBD_ROWS + 1);
+ const uint16_t keyH = (tileH > totalGapY) ? ((tileH - totalGapY) / KBD_ROWS) : (tileH / KBD_ROWS);
+ top = tileT + KEY_GAP_Y + row * (keyH + KEY_GAP_Y);
+ height = keyH;
+
+ const uint16_t totalGapX = KEY_GAP_X * (KBD_COLS + 1);
+ const uint16_t rowSpace = (tileW > totalGapX) ? (tileW - totalGapX) : tileW;
+ uint32_t rowUnits = 0;
+ const uint8_t rowStart = row * KBD_COLS;
+ for (uint8_t i = 0; i < KBD_COLS; i++) {
+ rowUnits += getKeyWidthAt(rowStart + i);
+ }
+ if (rowUnits == 0)
+ return false;
+
+ uint32_t cursorX = tileL + KEY_GAP_X;
+ for (uint8_t i = 0; i < col; i++) {
+ const uint8_t rowIndex = rowStart + i;
+ const uint32_t keyW = ((uint32_t)rowSpace * getKeyWidthAt(rowIndex)) / rowUnits;
+ cursorX += keyW + KEY_GAP_X;
+ }
+
+ left = (uint16_t)cursorX;
+
+ if (col == (KBD_COLS - 1)) {
+ const uint32_t rightEdge = tileL + tileW - KEY_GAP_X;
+ width = (rightEdge > cursorX) ? (uint16_t)(rightEdge - cursorX) : 0;
+ } else {
+ width = (uint16_t)(((uint32_t)rowSpace * getKeyWidthAt(index)) / rowUnits);
+ }
+
+ return true;
+}
+
+int16_t InkHUD::KeyboardApplet::getKeyIndexAt(uint16_t x, uint16_t y)
+{
+ for (uint8_t i = 0; i < KBD_KEY_COUNT; i++) {
+ uint16_t keyL = 0;
+ uint16_t keyT = 0;
+ uint16_t keyW = 0;
+ uint16_t keyH = 0;
+ if (!getKeyBounds(i, keyL, keyT, keyW, keyH))
+ return -1;
+
+ if (keyW == 0 || keyH == 0)
+ continue;
+
+ if (x >= keyL && x < (keyL + keyW) && y >= keyT && y < (keyT + keyH))
+ return i;
+ }
+
+ return -1;
+}
+
+void InkHUD::KeyboardApplet::inputSelectedKey(bool longPress)
+{
+ inputKeyCode(getKeyCodeAt(selectedKey), longPress);
+}
+
+void InkHUD::KeyboardApplet::inputKeyCode(int16_t keyCode, bool longPress)
+{
+ if (keyCode == KEY_NONE)
+ return;
+
+ if (keyCode >= KEY_EMOTE_SLOT_BASE) {
+ const uint8_t slot = (uint8_t)(keyCode - KEY_EMOTE_SLOT_BASE);
+ const uint16_t emoteIndex = emotePage * EMOTE_SLOT_COUNT + slot;
+ if (emoteIndex < fontEmoteCount)
+ inkhud->freeText((char)fontEmotes[emoteIndex]);
+ return;
+ }
+
+ switch (keyCode) {
+ case KEY_BACKSPACE:
+ inkhud->freeText('\b');
+ return;
+ case KEY_SEND:
+ inkhud->freeTextDone();
+ inkhud->closeKeyboard();
+ return;
+ case KEY_EMOTE_TOGGLE:
+ toggleEmoteMode();
+ return;
+ case KEY_PUNCT_TOGGLE:
+ case KEY_ALPHA_TOGGLE:
+ togglePunctuationMode();
+ return;
+ case KEY_EMOTE_UP:
+ pageEmotes(false);
+ return;
+ case KEY_EMOTE_DOWN:
+ pageEmotes(true);
+ return;
+ default:
+ break;
+ }
+
+ if (keyCode >= 0 && keyCode <= 0xFF) {
+ char key = (char)keyCode;
+ if (longPress && key >= 'a' && key <= 'z')
+ key = (char)std::toupper((unsigned char)key);
+ inkhud->freeText(key);
+ }
+}
+
+int16_t InkHUD::KeyboardApplet::getKeyCodeAt(uint8_t index) const
+{
+ if (index >= KBD_KEY_COUNT)
+ return KEY_NONE;
+
+ if (mode == MODE_TEXT)
+ return textKeys[index];
+ if (mode == MODE_PUNCT)
+ return punctKeys[index];
+
+ // Emote mode
+ if (index < EMOTE_SLOT_COUNT) {
+ const uint16_t emoteIndex = emotePage * EMOTE_SLOT_COUNT + index;
+ if (emoteIndex < fontEmoteCount)
+ return KEY_EMOTE_SLOT_BASE + index;
+ return KEY_NONE;
+ }
+
+ // Emote controls on the bottom row
+ switch (index - EMOTE_SLOT_COUNT) {
+ case 0:
+ return KEY_EMOTE_UP;
+ case 1:
+ return KEY_EMOTE_DOWN;
+ case 2:
+ return KEY_ALPHA_TOGGLE;
+ case 3:
+ return ',';
+ case 4:
+ return ' ';
+ case 5:
+ return '.';
+ case 6:
+ return KEY_SEND;
+ case 7:
+ return KEY_BACKSPACE;
+ default:
+ return KEY_NONE;
+ }
+}
+
+uint16_t InkHUD::KeyboardApplet::getKeyWidthAt(uint8_t index) const
+{
+ if (index >= KBD_KEY_COUNT)
+ return 0;
+
+ if (mode == MODE_EMOTE)
+ return emoteKeyWidths[index];
+ return typingKeyWidths[index];
+}
+
+std::string InkHUD::KeyboardApplet::getKeyLabelAt(uint8_t index) const
+{
+ const int16_t keyCode = getKeyCodeAt(index);
+ if (keyCode == KEY_NONE)
+ return "";
+
+ if (keyCode >= KEY_EMOTE_SLOT_BASE) {
+ const uint8_t slot = (uint8_t)(keyCode - KEY_EMOTE_SLOT_BASE);
+ const uint16_t emoteIndex = emotePage * EMOTE_SLOT_COUNT + slot;
+ if (emoteIndex < fontEmoteCount)
+ return std::string(1, (char)fontEmotes[emoteIndex]);
+ return "";
+ }
+
+ switch (keyCode) {
+ case KEY_BACKSPACE:
+ return "DEL";
+ case KEY_SEND:
+ return "SEND";
+ case KEY_EMOTE_TOGGLE:
+ return std::string(1, (char)0x03); // Smiling face icon from InkHUD emote font map
+ case KEY_PUNCT_TOGGLE:
+ return "!#1";
+ case KEY_ALPHA_TOGGLE:
+ return "ABC";
+ case KEY_EMOTE_UP:
+ return "UP";
+ case KEY_EMOTE_DOWN:
+ return "DN";
+ default:
+ break;
+ }
+
+ if (keyCode >= 0 && keyCode <= 0xFF) {
+ const char c = (char)keyCode;
+ if (c == ' ')
+ return "SPACE";
+ if (c >= 'a' && c <= 'z')
+ return std::string(1, (char)std::toupper((unsigned char)c));
+ return std::string(1, c);
+ }
+
+ return "";
+}
+
+bool InkHUD::KeyboardApplet::isKeyEnabledAt(uint8_t index) const
+{
+ return getKeyCodeAt(index) != KEY_NONE;
+}
+
+void InkHUD::KeyboardApplet::normalizeSelection()
+{
+ if (selectedKey >= KBD_KEY_COUNT)
+ selectedKey = 0;
+
+ if (isKeyEnabledAt(selectedKey))
+ return;
+
+ for (uint8_t i = 0; i < KBD_KEY_COUNT; i++) {
+ if (isKeyEnabledAt(i)) {
+ selectedKey = i;
+ return;
+ }
+ }
+}
+
+void InkHUD::KeyboardApplet::togglePunctuationMode()
+{
+ if (mode == MODE_EMOTE) {
+ mode = lastTypingMode;
+ } else {
+ mode = (mode == MODE_TEXT) ? MODE_PUNCT : MODE_TEXT;
+ lastTypingMode = mode;
+ }
+
+ normalizeSelection();
+ requestFastKeyboardRefresh(true);
+}
+
+void InkHUD::KeyboardApplet::toggleEmoteMode()
+{
+ if (mode == MODE_EMOTE) {
+ mode = lastTypingMode;
+ } else {
+ lastTypingMode = mode;
+ mode = MODE_EMOTE;
+ }
+
+ emotePage = 0;
+ normalizeSelection();
+ requestFastKeyboardRefresh(true);
+}
+
+void InkHUD::KeyboardApplet::pageEmotes(bool down)
+{
+ if (mode != MODE_EMOTE)
+ return;
+
+ const uint8_t maxPage = (fontEmoteCount == 0) ? 0 : (uint8_t)((fontEmoteCount - 1) / EMOTE_SLOT_COUNT);
+
+ if (down) {
+ if (emotePage < maxPage)
+ emotePage++;
+ } else {
+ if (emotePage > 0)
+ emotePage--;
+ }
+
+ normalizeSelection();
+ requestFastKeyboardRefresh(true);
+}
+
+void InkHUD::KeyboardApplet::requestFastKeyboardRefresh(bool full)
+{
+ requestUpdate(EInk::UpdateTypes::FAST, full);
+}
+
+bool InkHUD::KeyboardApplet::showSelectionHighlight() const
+{
+ // On touch-capable devices, prioritize input throughput over per-key highlight updates.
+ // E-ink refresh can lag rapid taps; skipping highlight avoids update-induced input latency.
+ return !inkhud->hasTouchEnabledProvider();
}
uint16_t InkHUD::KeyboardApplet::getKeyboardHeight()
{
- const uint16_t keyH = fontSmall.lineHeight() * 1.2;
- return keyH * KBD_ROWS;
+ // Keep touch keys tall and roomy for finger input.
+ // In portrait orientation we increase row height for larger touch targets.
+ const uint16_t rowUnit = fontSmall.lineHeight() + 8;
+ const uint8_t rowScale = usePortraitKeyboardSizing() ? 3 : 2;
+ const uint16_t keyH = rowUnit * rowScale;
+ return (keyH * KBD_ROWS) + (KEY_GAP_Y * (KBD_ROWS + 1));
}
#endif
diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h
index 0ae181a2c..b71376459 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h
+++ b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h
@@ -12,6 +12,7 @@ System Applet to render an on-screen keyboard
#include "graphics/niche/InkHUD/InkHUD.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include
+
namespace NicheGraphics::InkHUD
{
@@ -31,34 +32,111 @@ class KeyboardApplet : public SystemApplet
void onNavDown() override;
void onNavLeft() override;
void onNavRight() override;
+ bool onTouchPoint(uint16_t x, uint16_t y, bool longPress) override;
static uint16_t getKeyboardHeight(); // used to set the keyboard tile height
private:
- void drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color);
+ enum KeyCode : int16_t {
+ KEY_NONE = -1,
+ KEY_BACKSPACE = 256,
+ KEY_SEND,
+ KEY_EMOTE_TOGGLE,
+ KEY_PUNCT_TOGGLE,
+ KEY_ALPHA_TOGGLE,
+ KEY_EMOTE_UP,
+ KEY_EMOTE_DOWN,
+ KEY_EMOTE_SLOT_BASE = 512
+ };
+
+ enum KeyboardMode : uint8_t { MODE_TEXT = 0, MODE_PUNCT = 1, MODE_EMOTE = 2 };
+
+ void drawKey(uint8_t index, bool selected);
+ void drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, const std::string &label, Color color);
+ bool getKeyBounds(uint8_t index, uint16_t &left, uint16_t &top, uint16_t &width, uint16_t &height);
+ int16_t getKeyIndexAt(uint16_t x, uint16_t y);
+ void inputSelectedKey(bool longPress);
+ void inputKeyCode(int16_t keyCode, bool longPress);
+ int16_t getKeyCodeAt(uint8_t index) const;
+ uint16_t getKeyWidthAt(uint8_t index) const;
+ std::string getKeyLabelAt(uint8_t index) const;
+ bool isKeyEnabledAt(uint8_t index) const;
+ void normalizeSelection();
+ void togglePunctuationMode();
+ void toggleEmoteMode();
+ void pageEmotes(bool down);
+ void requestFastKeyboardRefresh(bool full = false);
+ bool showSelectionHighlight() const;
static const uint8_t KBD_COLS = 11;
- static const uint8_t KBD_ROWS = 4;
+ static const uint8_t KBD_ROWS = 5;
+ static const uint8_t KBD_KEY_COUNT = KBD_COLS * KBD_ROWS;
+ static const uint8_t EMOTE_SLOT_COUNT = KBD_COLS * (KBD_ROWS - 1); // top 4 rows
+ static constexpr uint8_t fontEmotes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0C, 0x0E, 0x0F, 0x10, 0x11,
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F};
+ static constexpr uint8_t fontEmoteCount = sizeof(fontEmotes) / sizeof(fontEmotes[0]);
- const char keys[KBD_COLS * KBD_ROWS] = {
- '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b', // row 0
- 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n', // row 1
- 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '!', ' ', // row 2
- 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?', '\x1b' // row 3
- };
+ // Text keyboard (requested layout):
+ // row 0: 1..0
+ // row 1: q..p
+ // row 2: a..l
+ // row 3: EMO, z..m, DEL
+ // row 4: !#1, comma, space, period, SEND
+ const int16_t textKeys[KBD_KEY_COUNT] = {
+ // row 0
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', KEY_NONE,
+ // row 1
+ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', KEY_NONE,
+ // row 2
+ 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', KEY_NONE, KEY_NONE,
+ // row 3
+ KEY_EMOTE_TOGGLE, 'z', 'x', 'c', 'v', 'b', 'n', 'm', KEY_BACKSPACE, KEY_NONE, KEY_NONE,
+ // row 4
+ KEY_PUNCT_TOGGLE, ',', ' ', '.', KEY_SEND, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE};
- // This array represents the widths of each key in points
- // 16 pt = line height of the text
- const uint16_t keyWidths[KBD_COLS * KBD_ROWS] = {
- 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 0
- 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 1
- 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 2
- 16, 16, 16, 16, 16, 16, 16, 10, 10, 12, 40 // row 3
- };
+ // Punctuation keyboard (toggle via !#1/ABC)
+ const int16_t punctKeys[KBD_KEY_COUNT] = {
+ // row 0
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', KEY_NONE,
+ // row 1
+ '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', KEY_NONE,
+ // row 2
+ '-', '_', '=', '+', '[', ']', '{', '}', '/', '?', KEY_NONE,
+ // row 3
+ KEY_EMOTE_TOGGLE, ';', ':', '\'', '"', '<', '>', '\\', KEY_BACKSPACE, KEY_NONE, KEY_NONE,
+ // row 4
+ KEY_ALPHA_TOGGLE, ',', ' ', '.', KEY_SEND, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE};
- uint16_t rowWidths[KBD_ROWS];
- uint8_t selectedKey = 0; // selected key index
+ const uint16_t typingKeyWidths[KBD_KEY_COUNT] = {// row 0
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0,
+ // row 1
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0,
+ // row 2
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0,
+ // row 3
+ 18, 12, 12, 12, 12, 12, 12, 12, 20, 0, 0,
+ // row 4
+ 20, 12, 56, 12, 24, 0, 0, 0, 0, 0, 0};
+
+ const uint16_t emoteKeyWidths[KBD_KEY_COUNT] = {// row 0
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ // row 1
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ // row 2
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ // row 3
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ // row 4 controls
+ 14, 14, 18, 12, 40, 12, 20, 18, 0, 0, 0};
+
+ uint8_t selectedKey = 0;
uint8_t prevSelectedKey = 0;
+ uint8_t emotePage = 0;
+ KeyboardMode mode = MODE_TEXT;
+ KeyboardMode lastTypingMode = MODE_TEXT;
+ static constexpr uint8_t KEY_GAP_X = 3;
+ static constexpr uint8_t KEY_GAP_Y = 4;
+ static constexpr uint8_t KEY_RADIUS = 4;
};
} // namespace NicheGraphics::InkHUD
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h
index 7ec76292b..e1f004d38 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h
+++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h
@@ -109,6 +109,7 @@ enum MenuAction {
TOGGLE_CHANNEL_POSITION,
SET_CHANNEL_PRECISION,
// Display
+ SET_DISPLAY_TIMEOUT,
TOGGLE_DISPLAY_UNITS,
// Network
TOGGLE_WIFI,
@@ -119,4 +120,4 @@ enum MenuAction {
} // namespace NicheGraphics::InkHUD
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
index d489d21ee..dfef4d085 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
@@ -8,6 +8,7 @@
#include "RTC.h"
#include "Router.h"
#include "airtime.h"
+#include "graphics/niche/Utils/FlashData.h"
#include "main.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "power.h"
@@ -27,6 +28,16 @@ static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu
// These are offered to users as possible values for settings.recentlyActiveSeconds
static constexpr uint8_t RECENTS_OPTIONS_MINUTES[] = {2, 5, 10, 30, 60, 120};
+struct DisplayTimeoutOption {
+ uint32_t seconds;
+ const char *label;
+};
+
+static constexpr DisplayTimeoutOption DISPLAY_TIMEOUT_OPTIONS[] = {
+ {0, "Forever"}, {30, "30 secs"}, {60, "1 min"}, {5 * 60, "5 min"},
+ {15 * 60, "15 min"}, {30 * 60, "30 min"}, {60 * 60, "1 hr"},
+};
+
struct PositionPrecisionOption {
uint8_t value; // proto value
const char *metric;
@@ -39,6 +50,77 @@ static constexpr PositionPrecisionOption POSITION_PRECISION_OPTIONS[] = {
{12, "5.8 km", "3.6 mi"}, {11, "12 km", "7.3 mi"}, {10, "23 km", "15 mi"},
};
+static const char *getDisplayTimeoutLabel(uint32_t timeoutSeconds)
+{
+ constexpr uint8_t optionCount = sizeof(DISPLAY_TIMEOUT_OPTIONS) / sizeof(DISPLAY_TIMEOUT_OPTIONS[0]);
+ for (uint8_t i = 0; i < optionCount; i++) {
+ if (DISPLAY_TIMEOUT_OPTIONS[i].seconds == timeoutSeconds) {
+ return DISPLAY_TIMEOUT_OPTIONS[i].label;
+ }
+ }
+
+ return "Custom";
+}
+
+static bool supportsFreeTextKeyboard(const InkHUD::InkHUD *inkhud, const InkHUD::Persistence::Settings *settings)
+{
+ return !inkhud->twoWayRocker && (settings->joystick.enabled || inkhud->hasTouchEnabledProvider());
+}
+
+static bool useTouchFriendlyMenuLayout(const InkHUD::InkHUD *inkhud)
+{
+ return inkhud != nullptr && inkhud->hasTouchEnabledProvider();
+}
+
+static uint16_t getMenuItemHeightPx(const InkHUD::InkHUD *inkhud)
+{
+ const bool touchFriendly = useTouchFriendlyMenuLayout(inkhud);
+ const uint16_t lineH = touchFriendly ? InkHUD::Applet::fontMedium.lineHeight() : InkHUD::Applet::fontSmall.lineHeight();
+ const float rowScale = touchFriendly ? 1.9f : 1.6f;
+ uint16_t itemH = (uint16_t)(lineH * rowScale);
+ if (itemH == 0) {
+ itemH = 1;
+ }
+ return itemH;
+}
+
+#if defined(T5_S3_EPAPER_PRO)
+namespace
+{
+static constexpr uint32_t T5_BACKLIGHT_PREFS_VERSION = 1;
+
+struct T5BacklightPrefs {
+ uint32_t version = T5_BACKLIGHT_PREFS_VERSION;
+ bool keepOn = true;
+};
+
+T5BacklightPrefs t5BacklightPrefs;
+bool t5BacklightPrefsLoaded = false;
+
+bool loadT5BacklightKeepOn()
+{
+ if (!t5BacklightPrefsLoaded) {
+ T5BacklightPrefs loaded;
+ const bool ok = FlashData::load(&loaded, "t5_backlight");
+ if (ok && loaded.version == T5_BACKLIGHT_PREFS_VERSION) {
+ t5BacklightPrefs = loaded;
+ }
+ t5BacklightPrefsLoaded = true;
+ }
+
+ return t5BacklightPrefs.keepOn;
+}
+
+void saveT5BacklightKeepOn(bool keepOn)
+{
+ loadT5BacklightKeepOn();
+ t5BacklightPrefs.version = T5_BACKLIGHT_PREFS_VERSION;
+ t5BacklightPrefs.keepOn = keepOn;
+ FlashData::save(&t5BacklightPrefs, "t5_backlight");
+}
+} // namespace
+#endif
+
InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
{
// No timer tasks at boot
@@ -47,7 +129,11 @@ InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
// Note: don't get instance if we're not actually using the backlight,
// or else you will unintentionally instantiate it
if (settings->optionalMenuItems.backlight) {
+#if defined(T5_S3_EPAPER_PRO)
+ t5BacklightSetUserEnabled(loadT5BacklightKeepOn());
+#else
backlight = Drivers::LatchingBacklight::getInstance();
+#endif
}
// Initialize the Canned Message store
@@ -76,9 +162,11 @@ void InkHUD::MenuApplet::onForeground()
// backlight on always when menu opens.
// Courtesy to T-Echo users who removed the capacitive touch button
if (settings->optionalMenuItems.backlight) {
+#if !defined(T5_S3_EPAPER_PRO)
assert(backlight);
if (!backlight->isOn())
backlight->peek();
+#endif
}
// Prevent user applets requesting update while menu is open
@@ -106,9 +194,11 @@ void InkHUD::MenuApplet::onBackground()
// Item in options submenu allows keeping backlight on after menu is closed
// If this item is deselected we will turn backlight off again, now that menu is closing
if (settings->optionalMenuItems.backlight) {
+#if !defined(T5_S3_EPAPER_PRO)
assert(backlight);
if (!backlight->isLatched())
backlight->off();
+#endif
}
// Stop the auto-timeout
@@ -333,17 +423,14 @@ void InkHUD::MenuApplet::execute(MenuItem item)
handleFreeText = true;
cm.freeTextItem.rawText.erase(); // clear the previous freetext message
freeTextMode = true; // render input field instead of normal menu
- // Open the on-screen keyboard only for full joystick devices
- if (settings->joystick.enabled && !inkhud->twoWayRocker)
+ if (supportsFreeTextKeyboard(inkhud, settings))
inkhud->openKeyboard();
break;
- case STORE_CANNEDMESSAGE_SELECTION:
- if (!settings->joystick.enabled || inkhud->twoWayRocker)
- cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry
- else
- cm.selectedMessageItem = &cm.messageItems.at(cursor - 2); // Minus two: offset for the "Send Ping" and free text entry
- break;
+ case STORE_CANNEDMESSAGE_SELECTION: {
+ const uint8_t prefixItems = supportsFreeTextKeyboard(inkhud, settings) ? 2 : 1;
+ cm.selectedMessageItem = &cm.messageItems.at(cursor - prefixItems);
+ } break;
case SEND_CANNEDMESSAGE:
cm.selectedRecipientItem = &cm.recipientItems.at(cursor);
@@ -422,14 +509,27 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break;
case TOGGLE_BACKLIGHT:
- // Note: backlight is already on in this situation
- // We're marking that it should *remain* on once menu closes
- assert(backlight);
+ // Note: backlight is already on in this situation.
+ // This toggle controls whether it should remain on when menu closes.
+#if defined(T5_S3_EPAPER_PRO)
+ {
+ const bool keepOn = !t5BacklightIsUserEnabled();
+ t5BacklightSetUserEnabled(keepOn);
+ saveT5BacklightKeepOn(keepOn);
+ if (item.checkState)
+ *(item.checkState) = keepOn;
+ }
+#else
+ if (!backlight)
+ backlight = Drivers::LatchingBacklight::getInstance();
if (backlight->isLatched())
backlight->off();
else
backlight->latch();
- break;
+ if (item.checkState)
+ *(item.checkState) = backlight->isLatched();
+#endif
+ break;
case TOGGLE_12H_CLOCK:
config.display.use_12h_clock = !config.display.use_12h_clock;
@@ -527,6 +627,17 @@ void InkHUD::MenuApplet::execute(MenuItem item)
}
// Display
+ case SET_DISPLAY_TIMEOUT: {
+ // cursor - 1 because index 0 is "Back"
+ const uint8_t index = cursor - 1;
+ constexpr uint8_t optionCount = sizeof(DISPLAY_TIMEOUT_OPTIONS) / sizeof(DISPLAY_TIMEOUT_OPTIONS[0]);
+ if (index < optionCount) {
+ config.display.screen_on_secs = DISPLAY_TIMEOUT_OPTIONS[index].seconds;
+ nodeDB->saveToDisk(SEGMENT_CONFIG);
+ }
+ break;
+ }
+
case TOGGLE_DISPLAY_UNITS:
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC;
@@ -893,11 +1004,16 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
previousPage = MenuPage::ROOT;
items.push_back(MenuItem("Back", previousPage));
// Optional: backlight
- if (settings->optionalMenuItems.backlight)
- items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
- MenuAction::TOGGLE_BACKLIGHT, // Action
- MenuPage::EXIT // Exit once complete
- ));
+ if (settings->optionalMenuItems.backlight) {
+#if defined(T5_S3_EPAPER_PRO)
+ keepBacklightOn = t5BacklightIsUserEnabled();
+#else
+ if (!backlight)
+ backlight = Drivers::LatchingBacklight::getInstance();
+ keepBacklightOn = backlight->isLatched();
+#endif
+ items.push_back(MenuItem("Keep Backlight On", MenuAction::TOGGLE_BACKLIGHT, MenuPage::OPTIONS, &keepBacklightOn));
+ }
// Options Toggles
items.push_back(MenuItem("Applets", MenuPage::APPLETS));
@@ -1109,6 +1225,9 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
items.push_back(MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::NODE_CONFIG_DISPLAY,
&config.display.use_12h_clock));
+ nodeConfigLabels.emplace_back("Screen Timeout: " + std::string(getDisplayTimeoutLabel(config.display.screen_on_secs)));
+ items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_DISPLAY_TIMEOUT));
+
const char *unitsLabel =
(config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? "Units: Imperial" : "Units: Metric";
@@ -1118,6 +1237,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
break;
}
+ case NODE_CONFIG_DISPLAY_TIMEOUT:
+ previousPage = MenuPage::NODE_CONFIG_DISPLAY;
+ items.push_back(MenuItem("Back", previousPage));
+ populateDisplayTimeoutPage();
+ items.push_back(MenuItem("Exit", MenuPage::EXIT));
+ break;
+
case NODE_CONFIG_BLUETOOTH: {
previousPage = MenuPage::NODE_CONFIG;
items.push_back(MenuItem("Back", previousPage));
@@ -1386,10 +1512,14 @@ void InkHUD::MenuApplet::onRender(bool full)
if (items.size() == 0)
LOG_ERROR("Empty Menu");
+ const bool touchFriendlyLayout = useTouchFriendlyMenuLayout(inkhud);
+ AppletFont menuItemFont = touchFriendlyLayout ? fontMedium : fontSmall;
+ setFont(menuItemFont);
+
// Dimensions for the slots where we will draw menuItems
const float padding = 0.05;
- const uint16_t itemH = fontSmall.lineHeight() * 1.6;
- const int16_t selectInsetY = 2;
+ const uint16_t itemH = getMenuItemHeightPx(inkhud);
+ const int16_t selectInsetY = touchFriendlyLayout ? 3 : 2;
const int16_t itemW = width() - X(padding) - X(padding);
const int16_t itemL = X(padding);
const int16_t itemR = X(1 - padding);
@@ -1422,6 +1552,11 @@ void InkHUD::MenuApplet::onRender(bool full)
itemT = max(siT + siH, 0); // Offset the first menu entry, so menu starts below the system info panel
}
+ // drawSystemInfoPanel() changes font state (clock/info text).
+ // Restore the active menu font so ROOT page item text matches other menu pages,
+ // including touch-friendly layouts.
+ setFont(menuItemFont);
+
// Draw menu items
// ===================
@@ -1449,8 +1584,6 @@ void InkHUD::MenuApplet::onRender(bool full)
// Header (non-selectable section label)
if (item.isHeader) {
- setFont(fontSmall);
-
// Header text (flush left)
printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE);
@@ -1459,8 +1592,17 @@ void InkHUD::MenuApplet::onRender(bool full)
drawLine(itemL + X(padding), underlineY, itemR - X(padding), underlineY, BLACK);
} else {
// Box, if currently selected
- if (cursorShown && i == cursor)
- drawRect(itemL, itemT + selectInsetY, itemW, itemH - (selectInsetY * 2), BLACK);
+ if (cursorShown && i == cursor && (!touchFriendlyLayout || !hideTouchSelectionHighlight)) {
+ const int16_t selTop = itemT + selectInsetY;
+ const int16_t selH = itemH - (selectInsetY * 2);
+ drawRect(itemL, selTop, itemW, selH, BLACK);
+ // Touch layouts need a stronger visual cue than a thin outline.
+ if (touchFriendlyLayout) {
+ const int16_t markerInset = 3;
+ const int16_t markerW = 4;
+ fillRect(itemL + markerInset, selTop + markerInset, markerW, max(1, selH - (markerInset * 2)), BLACK);
+ }
+ }
// Indented normal item text
printAt(itemL + X(padding * 2), center, item.label, LEFT, MIDDLE);
@@ -1468,9 +1610,9 @@ void InkHUD::MenuApplet::onRender(bool full)
// Checkbox, if relevant
if (item.checkState) {
- const uint16_t cbWH = fontSmall.lineHeight(); // Checkbox: width / height
- const int16_t cbL = itemR - X(padding) - cbWH; // Checkbox: left
- const int16_t cbT = center - (cbWH / 2); // Checkbox : top
+ const uint16_t cbWH = menuItemFont.lineHeight(); // Checkbox: width / height
+ const int16_t cbL = itemR - X(padding) - cbWH; // Checkbox: left
+ const int16_t cbT = center - (cbWH / 2); // Checkbox : top
// Checkbox ticked
if (*(item.checkState)) {
drawRect(cbL, cbT, cbWH, cbWH, BLACK);
@@ -1499,13 +1641,102 @@ void InkHUD::MenuApplet::onRender(bool full)
}
}
+bool InkHUD::MenuApplet::onTouchPoint(uint16_t x, uint16_t y, bool longPress)
+{
+ (void)longPress;
+
+ if (freeTextMode || !getTile()) {
+ return false;
+ }
+
+ const uint16_t tileL = getTile()->getLeft();
+ const uint16_t tileT = getTile()->getTop();
+ const uint16_t tileR = tileL + getTile()->getWidth();
+ const uint16_t tileB = tileT + getTile()->getHeight();
+ if (x < tileL || x >= tileR || y < tileT || y >= tileB) {
+ return false;
+ }
+
+ if (items.empty()) {
+ return true;
+ }
+
+ // Direct touch controls should act as activity and keep the menu open.
+ OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
+
+ // If button-driven selection is active on touch-first layouts, clear it as soon as
+ // touch interaction resumes so touch behavior remains direct/tap-first.
+ if (useTouchFriendlyMenuLayout(inkhud)) {
+ cursorShown = false;
+ hideTouchSelectionHighlight = true;
+ }
+
+ const int16_t localY = (int16_t)y - (int16_t)tileT;
+
+ // Keep geometry in sync with onRender() so touch hit-testing matches what users see.
+ const uint16_t itemH = getMenuItemHeightPx(inkhud);
+ int16_t itemT = 0;
+ uint8_t slotCount = (height() - itemT) / itemH;
+ if (slotCount == 0) {
+ slotCount = 1;
+ }
+ const uint16_t &siH = systemInfoPanelHeight;
+ const uint8_t slotsObscured = ceilf(siH / (float)itemH);
+
+ if (currentPage == ROOT) {
+ int16_t siT = 0;
+ const int16_t scrollThreshold = (int16_t)slotCount - (int16_t)slotsObscured - 1;
+ if (scrollThreshold >= 0 && (int16_t)cursor >= scrollThreshold) {
+ siT = 0 - ((cursor - scrollThreshold) * itemH);
+ }
+ itemT = max((int16_t)(siT + siH), (int16_t)0);
+ }
+
+ const uint8_t firstItem = (cursor < slotCount) ? 0 : (cursor - (slotCount - 1));
+ uint16_t visibleEnd = (uint16_t)firstItem + (uint16_t)slotCount;
+ const uint8_t maxIndex = (uint8_t)items.size() - 1;
+ if (visibleEnd > maxIndex) {
+ visibleEnd = maxIndex;
+ }
+ const uint8_t lastItem = (uint8_t)visibleEnd;
+
+ for (uint8_t i = firstItem; i <= lastItem; i++) {
+ const int16_t rowTop = itemT;
+ const int16_t rowBottom = itemT + itemH;
+
+ if (localY >= rowTop && localY < rowBottom) {
+ if (items.at(i).isHeader) {
+ // Consume taps on headers so they don't fall back to button semantics.
+ return true;
+ }
+
+ cursor = i;
+ cursorShown = true;
+ execute(items.at(cursor));
+
+ if (!wantsToRender()) {
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+ }
+ return true;
+ }
+
+ itemT += itemH;
+ }
+
+ // Consume taps on menu whitespace so we don't trigger button-like fallback behavior.
+ return true;
+}
+
void InkHUD::MenuApplet::onButtonShortPress()
{
if (!freeTextMode) {
// Push the auto-close timer back
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
- if (!settings->joystick.enabled) {
+ // Touch-first nodes keep user-button short-press as "advance selection" in menus.
+ // Any button-driven navigation should restore visible highlight.
+ hideTouchSelectionHighlight = false;
+ if (!settings->joystick.enabled || useTouchFriendlyMenuLayout(inkhud)) {
if (!cursorShown) {
cursorShown = true;
// Select the first item that isn't a header
@@ -1566,6 +1797,32 @@ void InkHUD::MenuApplet::onNavUp()
if (!freeTextMode) {
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
+ // Touch-first menus: swipe up/down should scroll only.
+ // Keep cursor movement for scroll math, but selection box is hidden in onRender().
+ if (useTouchFriendlyMenuLayout(inkhud)) {
+ hideTouchSelectionHighlight = true;
+ if (!cursorShown) {
+ cursorShown = true;
+ cursor = items.size() - 1;
+ while (items.at(cursor).isHeader) {
+ if (cursor == 0) {
+ cursorShown = false;
+ break;
+ }
+ cursor--;
+ }
+ } else {
+ do {
+ if (cursor == 0)
+ cursor = items.size() - 1;
+ else
+ cursor--;
+ } while (items.at(cursor).isHeader);
+ }
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+ return;
+ }
+
if (!cursorShown) {
cursorShown = true;
// Select the last item that isn't a header
@@ -1595,6 +1852,29 @@ void InkHUD::MenuApplet::onNavDown()
if (!freeTextMode) {
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
+ // Touch-first menus: swipe up/down should scroll only.
+ // Keep cursor movement for scroll math, but selection box is hidden in onRender().
+ if (useTouchFriendlyMenuLayout(inkhud)) {
+ hideTouchSelectionHighlight = true;
+ if (!cursorShown) {
+ cursorShown = true;
+ cursor = 0;
+ while (cursor < items.size() && items.at(cursor).isHeader) {
+ cursor++;
+ }
+ if (cursor >= items.size()) {
+ cursorShown = false;
+ cursor = 0;
+ }
+ } else {
+ do {
+ cursor = (cursor + 1) % items.size();
+ } while (items.at(cursor).isHeader);
+ }
+ requestUpdate(Drivers::EInk::UpdateTypes::FAST);
+ return;
+ }
+
if (!cursorShown) {
cursorShown = true;
// Select the first item that isn't a header
@@ -1727,6 +2007,17 @@ void InkHUD::MenuApplet::populateRecentsPage()
}
}
+void InkHUD::MenuApplet::populateDisplayTimeoutPage()
+{
+ constexpr uint8_t optionCount = sizeof(DISPLAY_TIMEOUT_OPTIONS) / sizeof(DISPLAY_TIMEOUT_OPTIONS[0]);
+ for (uint8_t i = 0; i < optionCount; i++) {
+ displayTimeoutSelected[i] = (config.display.screen_on_secs == DISPLAY_TIMEOUT_OPTIONS[i].seconds);
+ nodeConfigLabels.emplace_back(DISPLAY_TIMEOUT_OPTIONS[i].label);
+ items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_DISPLAY_TIMEOUT, MenuPage::NODE_CONFIG_DISPLAY,
+ &displayTimeoutSelected[i]));
+ }
+}
+
// MenuItem entries for the "send" page
// Dynamically creates menu items based on available canned messages
void InkHUD::MenuApplet::populateSendPage()
@@ -1734,8 +2025,8 @@ void InkHUD::MenuApplet::populateSendPage()
// Position / NodeInfo packet
items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT));
- // If joystick is available, include the Free Text option
- if (settings->joystick.enabled && !inkhud->twoWayRocker)
+ // Show the Free Text option on any node that supports the on-screen keyboard.
+ if (supportsFreeTextKeyboard(inkhud, settings))
items.push_back(MenuItem("Free Text", MenuAction::FREE_TEXT, MenuPage::SEND));
// One menu item for each canned message
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h
index b5c1c86e4..2d7fbd398 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h
+++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h
@@ -35,6 +35,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void onFreeText(char c) override;
void onFreeTextDone() override;
void onFreeTextCancel() override;
+ bool onTouchPoint(uint16_t x, uint16_t y, bool longPress) override;
void onRender(bool full) override;
void show(Tile *t); // Open the menu, onto a user tile
@@ -48,11 +49,12 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any
void showPage(MenuPage page); // Load and display a MenuPage
- void populateSendPage(); // Dynamically create MenuItems including canned messages
- void populateRecipientPage(); // Dynamically create a page of possible destinations for a canned message
- void populateAppletPage(); // Dynamically create MenuItems for toggling loaded applets
- void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow
- void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds
+ void populateSendPage(); // Dynamically create MenuItems including canned messages
+ void populateRecipientPage(); // Dynamically create a page of possible destinations for a canned message
+ void populateAppletPage(); // Dynamically create MenuItems for toggling loaded applets
+ void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow
+ void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds
+ void populateDisplayTimeoutPage(); // Create menu items for config.display.screen_on_secs
void drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height,
const std::string &text); // Draw input field for free text
@@ -65,8 +67,9 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
MenuPage startPageOverride = MenuPage::ROOT;
MenuPage currentPage = MenuPage::ROOT;
MenuPage previousPage = MenuPage::EXIT;
- uint8_t cursor = 0; // Which menu item is currently highlighted
- bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
+ uint8_t cursor = 0; // Which menu item is currently highlighted
+ bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
+ bool hideTouchSelectionHighlight = false; // Touch scrolling keeps cursor for paging math, but can hide highlight
bool freeTextMode = false;
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
uint16_t menuTextLimit = 200;
@@ -80,6 +83,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
// Recents menu checkbox state (derived from settings.recentlyActiveSeconds)
static constexpr uint8_t RECENTS_COUNT = 6;
bool recentsSelected[RECENTS_COUNT] = {};
+ static constexpr uint8_t DISPLAY_TIMEOUT_COUNT = 7;
+ bool displayTimeoutSelected[DISPLAY_TIMEOUT_COUNT] = {};
// Data for selecting and sending canned messages via the menu
// Placed into a sub-class for organization only
@@ -116,7 +121,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
- bool invertedColors = false; // Helper to display current state of config.display.displaymode in InkHUD options
+ bool invertedColors = false; // Helper to display current state of config.display.displaymode in InkHUD options
+ bool keepBacklightOn = false; // Helper to display current backlight latch state in InkHUD options
};
} // namespace NicheGraphics::InkHUD
diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h
index 138c389be..9bc6bb0cc 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h
+++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h
@@ -32,6 +32,7 @@ enum MenuPage : uint8_t {
NODE_CONFIG_POWER_ADC_CAL,
NODE_CONFIG_NETWORK,
NODE_CONFIG_DISPLAY,
+ NODE_CONFIG_DISPLAY_TIMEOUT,
NODE_CONFIG_BLUETOOTH,
NODE_CONFIG_POSITION,
NODE_CONFIG_ADMIN_RESET,
@@ -45,4 +46,4 @@ enum MenuPage : uint8_t {
} // namespace NicheGraphics::InkHUD
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/TouchStatusApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Notification/TouchStatusApplet.cpp
new file mode 100644
index 000000000..04475c803
--- /dev/null
+++ b/src/graphics/niche/InkHUD/Applets/System/Notification/TouchStatusApplet.cpp
@@ -0,0 +1,30 @@
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+
+#include "./TouchStatusApplet.h"
+
+using namespace NicheGraphics;
+
+InkHUD::TouchStatusApplet::TouchStatusApplet()
+{
+ alwaysRender = true;
+}
+
+void InkHUD::TouchStatusApplet::onRender(bool full)
+{
+ (void)full;
+
+ if (inkhud->isTouchEnabled()) {
+ return;
+ }
+
+ setFont(fontSmall);
+
+ const uint16_t barH = fontSmall.lineHeight() + 4;
+ const int16_t top = height() - barH;
+
+ fillRect(0, top, width(), barH, WHITE);
+ drawLine(0, top, width() - 1, top, BLACK);
+ printAt(width() / 2, top + (barH / 2), "TOUCH OFF", CENTER, MIDDLE);
+}
+
+#endif
diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/TouchStatusApplet.h b/src/graphics/niche/InkHUD/Applets/System/Notification/TouchStatusApplet.h
new file mode 100644
index 000000000..1d3ddc816
--- /dev/null
+++ b/src/graphics/niche/InkHUD/Applets/System/Notification/TouchStatusApplet.h
@@ -0,0 +1,29 @@
+#ifdef MESHTASTIC_INCLUDE_INKHUD
+
+/*
+
+ Low-profile bottom-edge touch status indicator.
+ Shown only while touch input is disabled.
+
+*/
+
+#pragma once
+
+#include "configuration.h"
+
+#include "graphics/niche/InkHUD/SystemApplet.h"
+
+namespace NicheGraphics::InkHUD
+{
+
+class TouchStatusApplet : public SystemApplet
+{
+ public:
+ TouchStatusApplet();
+
+ void onRender(bool full) override;
+};
+
+} // namespace NicheGraphics::InkHUD
+
+#endif
diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp
index a45e8d9b5..ffc8f6f99 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp
@@ -47,6 +47,9 @@ InkHUD::TipsApplet::TipsApplet()
void InkHUD::TipsApplet::onRender(bool full)
{
+ const char *continuePrompt =
+ (inkhud && inkhud->hasTouchEnabledProvider()) ? "Tap screen to continue" : "Press button to continue";
+
switch (tipQueue.front()) {
case Tip::WELCOME:
renderWelcome();
@@ -79,7 +82,7 @@ void InkHUD::TipsApplet::onRender(bool full)
cursorY += fontSmall.lineHeight() / 2;
drawBullet("More info at meshtastic.org");
- printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
+ printAt(0, Y(1.0), continuePrompt, LEFT, BOTTOM);
} break;
case Tip::PICK_REGION: {
@@ -109,7 +112,7 @@ void InkHUD::TipsApplet::onRender(bool full)
printWrapped(0, cursorY, width(), body);
cursorY += bodyH + (fontSmall.lineHeight() / 2);
- printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
+ printAt(0, Y(1.0), continuePrompt, LEFT, BOTTOM);
} break;
case Tip::CUSTOMIZATION: {
@@ -129,10 +132,13 @@ void InkHUD::TipsApplet::onRender(bool full)
printWrapped(0, cursorY, width(), body);
cursorY += bodyH + (fontSmall.lineHeight() / 2);
- printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
+ printAt(0, Y(1.0), continuePrompt, LEFT, BOTTOM);
} break;
case Tip::BUTTONS: {
+#if defined(T5_S3_EPAPER_PRO)
+ renderT5S3ButtonsTip();
+#else
setFont(fontMedium);
const char *title = "Tip: Buttons";
@@ -164,7 +170,8 @@ void InkHUD::TipsApplet::onRender(bool full)
drawBullet("- press: switch tile or close menu");
}
- printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
+ printAt(0, Y(1.0), continuePrompt, LEFT, BOTTOM);
+#endif
} break;
case Tip::ROTATION: {
@@ -189,7 +196,7 @@ void InkHUD::TipsApplet::onRender(bool full)
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
}
- printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
+ printAt(0, Y(1.0), continuePrompt, LEFT, BOTTOM);
// Revert the "flip screen" setting, preventing this message showing again
config.display.flip_screen = false;
@@ -198,6 +205,42 @@ void InkHUD::TipsApplet::onRender(bool full)
}
}
+#if defined(T5_S3_EPAPER_PRO)
+void InkHUD::TipsApplet::renderT5S3ButtonsTip()
+{
+ setFont(fontMedium);
+
+ const char *title = "Tip: T5-S3 Buttons";
+ uint16_t h = getWrappedTextHeight(0, width(), title);
+ printWrapped(0, 0, width(), title);
+
+ setFont(fontSmall);
+ int16_t cursorY = h + fontSmall.lineHeight();
+
+ auto drawBullet = [&](const char *text) {
+ uint16_t bh = getWrappedTextHeight(0, width(), text);
+ printWrapped(0, cursorY, width(), text);
+ cursorY += bh + (fontSmall.lineHeight() / 3);
+ };
+
+ drawBullet("BOOT button");
+ drawBullet("- short press: next");
+ drawBullet("- long press: open menu or select");
+
+ drawBullet("IO48 button");
+ drawBullet("- short press: toggle touch on/off");
+ drawBullet("- long press: toggle backlight on/off");
+
+ drawBullet("PWR button");
+ drawBullet("- Hold Press to wake after Shutdown");
+
+ drawBullet("HOME button");
+ drawBullet("- press: back/exit in InkHUD and open App switcher");
+
+ printAt(0, Y(1.0), "Tap screen to continue", LEFT, BOTTOM);
+}
+#endif
+
// This tip has its own render method, only because it's a big block of code
// Didn't want to clutter up the switch in onRender too much
void InkHUD::TipsApplet::renderWelcome()
@@ -244,7 +287,9 @@ void InkHUD::TipsApplet::renderWelcome()
// Block 3 - press to continue
// ============================
- printAt(X(0.5), Y(1), "Press button to continue", CENTER, BOTTOM);
+ const char *continuePrompt =
+ (inkhud && inkhud->hasTouchEnabledProvider()) ? "Tap screen to continue" : "Press button to continue";
+ printAt(X(0.5), Y(1), continuePrompt, CENTER, BOTTOM);
}
void InkHUD::TipsApplet::onForeground()
diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h
index 2e81d678b..ea2db228c 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h
+++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h
@@ -41,6 +41,9 @@ class TipsApplet : public SystemApplet
protected:
void renderWelcome(); // Very first screen of tutorial
+#if defined(T5_S3_EPAPER_PRO)
+ void renderT5S3ButtonsTip();
+#endif
std::deque tipQueue; // List of tips to show, one after another
@@ -49,4 +52,4 @@ class TipsApplet : public SystemApplet
} // namespace NicheGraphics::InkHUD
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp
index 577a773bb..ddcc6781b 100644
--- a/src/graphics/niche/InkHUD/Events.cpp
+++ b/src/graphics/niche/InkHUD/Events.cpp
@@ -2,6 +2,7 @@
#include "./Events.h"
+#include "PowerFSM.h"
#include "RTC.h"
#include "buzz.h"
#include "modules/ExternalNotificationModule.h"
@@ -14,6 +15,19 @@
using namespace NicheGraphics;
+namespace
+{
+// When touch long-press opens menu, some panels report a delayed release/tap
+// if the finger stays down briefly. Keep this long enough to cover that release.
+constexpr uint32_t TOUCH_MENU_OPEN_TAP_SUPPRESS_MS = 1200;
+
+inline void noteInkHUDUserInteraction()
+{
+ // Keep power state and screen-timeout behavior in sync with InkHUD input activity.
+ powerFSM.trigger(EVENT_INPUT);
+}
+} // namespace
+
InkHUD::Events::Events()
{
// Get convenient references
@@ -38,6 +52,8 @@ void InkHUD::Events::begin()
void InkHUD::Events::onButtonShort()
{
+ noteInkHUDUserInteraction();
+
// Audio feedback (via buzzer)
// Short tone
playChirp();
@@ -74,6 +90,8 @@ void InkHUD::Events::onButtonShort()
void InkHUD::Events::onButtonLong()
{
+ noteInkHUDUserInteraction();
+
// Audio feedback (via buzzer)
// Slightly longer than playChirp
playBoop();
@@ -102,193 +120,295 @@ void InkHUD::Events::onButtonLong()
void InkHUD::Events::onExitShort()
{
- if (settings->joystick.enabled) {
- // Audio feedback (via buzzer)
- // Short tone
- playChirp();
- // Cancel any beeping, buzzing, blinking
- // Some button handling suppressed if we are dismissing an external notification (see below)
- bool dismissedExt = dismissExternalNotification();
+ // Preserve legacy behavior on non-touch builds:
+ // EXIT input is only active when joystick mode is enabled.
+ // Touch-capable builds intentionally bypass this joystick gate.
+ if (!settings->joystick.enabled && !inkhud->hasTouchEnabledProvider()) {
+ return;
+ }
- // Check which system applet wants to handle the button press (if any)
- SystemApplet *consumer = nullptr;
- for (SystemApplet *sa : inkhud->systemApplets) {
- if (sa->handleInput) {
- consumer = sa;
- break;
- }
+ noteInkHUDUserInteraction();
+
+ // Audio feedback (via buzzer)
+ // Short tone
+ playChirp();
+ // Cancel any beeping, buzzing, blinking
+ // Some button handling suppressed if we are dismissing an external notification module (see below)
+ bool dismissedExt = dismissExternalNotification();
+
+ // Check which system applet wants to handle the button press (if any)
+ SystemApplet *consumer = nullptr;
+ for (SystemApplet *sa : inkhud->systemApplets) {
+ if (sa->handleInput) {
+ consumer = sa;
+ break;
}
+ }
- // If no system applet is handling input, default behavior instead is change tiles
- if (consumer)
- consumer->onExitShort();
- else if (!dismissedExt) { // Don't change tile if this button press silenced the external notification module
- Applet *userConsumer = inkhud->getActiveApplet();
+ // Always let active system applets consume EXIT/HOME input (menu close, keyboard cancel, etc),
+ // including on touch-first nodes where joystick mode is disabled.
+ if (consumer) {
+ consumer->onExitShort();
+ return;
+ }
- if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_SHORT))
- userConsumer->onExitShort();
- else
- inkhud->nextTile();
+ // Touch-capable InkHUD nodes use EXIT/HOME as a quick app switcher launcher.
+ if (!dismissedExt && inkhud->hasTouchEnabledProvider()) {
+ inkhud->openAppSwitcher();
+ return;
+ }
+
+ if (!dismissedExt) {
+ Applet *userConsumer = inkhud->getActiveApplet();
+
+ if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_SHORT)) {
+ userConsumer->onExitShort();
+ } else if (settings->joystick.enabled) {
+ // Preserve existing joystick behavior when no applet handles EXIT.
+ inkhud->nextTile();
}
}
}
void InkHUD::Events::onExitLong()
{
- if (settings->joystick.enabled) {
- // Audio feedback (via buzzer)
- // Slightly longer than playChirp
- playBoop();
+ // Preserve legacy behavior on non-touch builds:
+ // EXIT input is only active when joystick mode is enabled.
+ // Touch-capable builds intentionally bypass this joystick gate.
+ if (!settings->joystick.enabled && !inkhud->hasTouchEnabledProvider()) {
+ return;
+ }
- // Check which system applet wants to handle the button press (if any)
- SystemApplet *consumer = nullptr;
- for (SystemApplet *sa : inkhud->systemApplets) {
- if (sa->handleInput) {
- consumer = sa;
- break;
- }
+ noteInkHUDUserInteraction();
+
+ // Audio feedback (via buzzer)
+ // Slightly longer than playChirp
+ playBoop();
+
+ // Check which system applet wants to handle the button press (if any)
+ SystemApplet *consumer = nullptr;
+ for (SystemApplet *sa : inkhud->systemApplets) {
+ if (sa->handleInput) {
+ consumer = sa;
+ break;
}
+ }
- if (consumer)
- consumer->onExitLong();
- else {
- Applet *userConsumer = inkhud->getActiveApplet();
+ // Always allow system applets to consume EXIT/HOME long-press.
+ if (consumer) {
+ consumer->onExitLong();
+ } else {
+ Applet *userConsumer = inkhud->getActiveApplet();
- if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_LONG))
- userConsumer->onExitLong();
- // Nothing uses exit long yet
- }
+ if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_LONG))
+ userConsumer->onExitLong();
+ // Nothing uses exit long yet
}
}
void InkHUD::Events::onNavUp()
{
- if (settings->joystick.enabled) {
- // Audio feedback (via buzzer)
- // Short tone
- playChirp();
- // Cancel any beeping, buzzing, blinking
- // Some button handling suppressed if we are dismissing an external notification (see below)
- bool dismissedExt = dismissExternalNotification();
-
- // Check which system applet wants to handle the button press (if any)
- SystemApplet *consumer = nullptr;
- for (SystemApplet *sa : inkhud->systemApplets) {
- if (sa->handleInput) {
- consumer = sa;
- break;
- }
- }
-
- if (consumer)
- consumer->onNavUp();
- else if (!dismissedExt) {
- Applet *userConsumer = inkhud->getActiveApplet();
-
- if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_UP))
- userConsumer->onNavUp();
- }
- }
+ if (settings->joystick.enabled)
+ onTouchNavUp();
}
void InkHUD::Events::onNavDown()
{
- if (settings->joystick.enabled) {
- // Audio feedback (via buzzer)
- // Short tone
- playChirp();
- // Cancel any beeping, buzzing, blinking
- // Some button handling suppressed if we are dismissing an external notification (see below)
- bool dismissedExt = dismissExternalNotification();
-
- // Check which system applet wants to handle the button press (if any)
- SystemApplet *consumer = nullptr;
- for (SystemApplet *sa : inkhud->systemApplets) {
- if (sa->handleInput) {
- consumer = sa;
- break;
- }
- }
-
- if (consumer)
- consumer->onNavDown();
- else if (!dismissedExt) {
- Applet *userConsumer = inkhud->getActiveApplet();
-
- if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_DOWN))
- userConsumer->onNavDown();
- }
- }
+ if (settings->joystick.enabled)
+ onTouchNavDown();
}
void InkHUD::Events::onNavLeft()
{
- if (settings->joystick.enabled) {
- // Audio feedback (via buzzer)
- // Short tone
- playChirp();
- // Cancel any beeping, buzzing, blinking
- // Some button handling suppressed if we are dismissing an external notification (see below)
- bool dismissedExt = dismissExternalNotification();
-
- // Check which system applet wants to handle the button press (if any)
- SystemApplet *consumer = nullptr;
- for (SystemApplet *sa : inkhud->systemApplets) {
- if (sa->handleInput) {
- consumer = sa;
- break;
- }
- }
-
- // If no system applet is handling input, default behavior instead is to cycle applets
- if (consumer)
- consumer->onNavLeft();
- else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module
- Applet *userConsumer = inkhud->getActiveApplet();
-
- if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_LEFT))
- userConsumer->onNavLeft();
- else
- inkhud->prevApplet();
- }
- }
+ if (settings->joystick.enabled)
+ onTouchNavLeft();
}
void InkHUD::Events::onNavRight()
{
- if (settings->joystick.enabled) {
- // Audio feedback (via buzzer)
- // Short tone
- playChirp();
- // Cancel any beeping, buzzing, blinking
- // Some button handling suppressed if we are dismissing an external notification (see below)
- bool dismissedExt = dismissExternalNotification();
+ if (settings->joystick.enabled)
+ onTouchNavRight();
+}
- // Check which system applet wants to handle the button press (if any)
- SystemApplet *consumer = nullptr;
- for (SystemApplet *sa : inkhud->systemApplets) {
- if (sa->handleInput) {
- consumer = sa;
- break;
- }
- }
+void InkHUD::Events::onTouchNavUp()
+{
+ noteInkHUDUserInteraction();
- // If no system applet is handling input, default behavior instead is to cycle applets
- if (consumer)
- consumer->onNavRight();
- else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module
- Applet *userConsumer = inkhud->getActiveApplet();
+ // Audio feedback (via buzzer)
+ // Short tone
+ playChirp();
+ // Cancel any beeping, buzzing, blinking
+ // Some button handling suppressed if we are dismissing an external notification (see below)
+ bool dismissedExt = dismissExternalNotification();
- if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_RIGHT))
- userConsumer->onNavRight();
- else
- inkhud->nextApplet();
+ // Check which system applet wants to handle the button press (if any)
+ SystemApplet *consumer = nullptr;
+ for (SystemApplet *sa : inkhud->systemApplets) {
+ if (sa->handleInput) {
+ consumer = sa;
+ break;
}
}
+
+ if (consumer)
+ consumer->onNavUp();
+ else if (!dismissedExt) {
+ Applet *userConsumer = inkhud->getActiveApplet();
+
+ if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_UP))
+ userConsumer->onNavUp();
+ }
+}
+
+void InkHUD::Events::onTouchNavDown()
+{
+ noteInkHUDUserInteraction();
+
+ // Audio feedback (via buzzer)
+ // Short tone
+ playChirp();
+ // Cancel any beeping, buzzing, blinking
+ // Some button handling suppressed if we are dismissing an external notification (see below)
+ bool dismissedExt = dismissExternalNotification();
+
+ // Check which system applet wants to handle the button press (if any)
+ SystemApplet *consumer = nullptr;
+ for (SystemApplet *sa : inkhud->systemApplets) {
+ if (sa->handleInput) {
+ consumer = sa;
+ break;
+ }
+ }
+
+ if (consumer)
+ consumer->onNavDown();
+ else if (!dismissedExt) {
+ Applet *userConsumer = inkhud->getActiveApplet();
+
+ if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_DOWN))
+ userConsumer->onNavDown();
+ }
+}
+
+void InkHUD::Events::onTouchNavLeft()
+{
+ noteInkHUDUserInteraction();
+
+ // Audio feedback (via buzzer)
+ // Short tone
+ playChirp();
+ // Cancel any beeping, buzzing, blinking
+ // Some button handling suppressed if we are dismissing an external notification (see below)
+ bool dismissedExt = dismissExternalNotification();
+
+ // Check which system applet wants to handle the button press (if any)
+ SystemApplet *consumer = nullptr;
+ for (SystemApplet *sa : inkhud->systemApplets) {
+ if (sa->handleInput) {
+ consumer = sa;
+ break;
+ }
+ }
+
+ // If no system applet is handling input, default behavior instead is to cycle applets
+ if (consumer)
+ consumer->onNavLeft();
+ else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module
+ Applet *userConsumer = inkhud->getActiveApplet();
+
+ if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_LEFT))
+ userConsumer->onNavLeft();
+ else
+ inkhud->prevApplet();
+ }
+}
+
+void InkHUD::Events::onTouchNavRight()
+{
+ noteInkHUDUserInteraction();
+
+ // Audio feedback (via buzzer)
+ // Short tone
+ playChirp();
+ // Cancel any beeping, buzzing, blinking
+ // Some button handling suppressed if we are dismissing an external notification (see below)
+ bool dismissedExt = dismissExternalNotification();
+
+ // Check which system applet wants to handle the button press (if any)
+ SystemApplet *consumer = nullptr;
+ for (SystemApplet *sa : inkhud->systemApplets) {
+ if (sa->handleInput) {
+ consumer = sa;
+ break;
+ }
+ }
+
+ // If no system applet is handling input, default behavior instead is to cycle applets
+ if (consumer)
+ consumer->onNavRight();
+ else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module
+ Applet *userConsumer = inkhud->getActiveApplet();
+
+ if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_RIGHT))
+ userConsumer->onNavRight();
+ else
+ inkhud->nextApplet();
+ }
+}
+
+void InkHUD::Events::onTouchTap(uint16_t x, uint16_t y, bool longPress)
+{
+ const bool touchEnabledBuild = inkhud->hasTouchEnabledProvider();
+
+ // A long-press used to open the menu can be followed by a synthetic/queued tap at release.
+ // Ignore that brief follow-up window so touch-opened menus do not auto-select an item.
+ if (touchEnabledBuild && !longPress && suppressTouchTapUntilMs != 0) {
+ if ((int32_t)(millis() - suppressTouchTapUntilMs) < 0) {
+ noteInkHUDUserInteraction();
+ return;
+ }
+ suppressTouchTapUntilMs = 0;
+ }
+
+ // Give system applets (menu, keyboard, etc) first chance to consume direct touch input.
+ for (SystemApplet *sa : inkhud->systemApplets) {
+ if (sa->handleInput && sa->onTouchPoint(x, y, longPress)) {
+ noteInkHUDUserInteraction();
+ return;
+ }
+ }
+
+ // In split layouts, tapping a different tile changes focus.
+ // Consume this tap so selection does not also trigger button fallback behavior.
+ if (inkhud->selectTileAt(x, y)) {
+ noteInkHUDUserInteraction();
+ return;
+ }
+
+ // Allow foreground user applet to consume direct touch input if it wants.
+ Applet *userConsumer = inkhud->getActiveApplet();
+ if (userConsumer != nullptr && userConsumer->onTouchPoint(x, y, longPress)) {
+ noteInkHUDUserInteraction();
+ return;
+ }
+
+ // Fallback to existing button semantics so non-touch-aware applets keep working unchanged.
+ if (longPress) {
+ onButtonLong();
+
+ // Only arm suppression if the long-press actually opened menu foreground.
+ SystemApplet *menu = inkhud->getSystemApplet("Menu");
+ if (touchEnabledBuild && menu && menu->isForeground()) {
+ suppressTouchTapUntilMs = millis() + TOUCH_MENU_OPEN_TAP_SUPPRESS_MS;
+ }
+ } else
+ onButtonShort();
}
void InkHUD::Events::onFreeText(char c)
{
+ noteInkHUDUserInteraction();
+
// Trigger the first system applet that wants to handle the new character
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleFreeText) {
@@ -300,6 +420,8 @@ void InkHUD::Events::onFreeText(char c)
void InkHUD::Events::onFreeTextDone()
{
+ noteInkHUDUserInteraction();
+
// Trigger the first system applet that wants to handle it
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleFreeText) {
@@ -311,6 +433,8 @@ void InkHUD::Events::onFreeTextDone()
void InkHUD::Events::onFreeTextCancel()
{
+ noteInkHUDUserInteraction();
+
// Trigger the first system applet that wants to handle it
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleFreeText) {
@@ -487,4 +611,4 @@ bool InkHUD::Events::dismissExternalNotification()
return true;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/niche/InkHUD/Events.h b/src/graphics/niche/InkHUD/Events.h
index 873f53fd5..ceb298b3b 100644
--- a/src/graphics/niche/InkHUD/Events.h
+++ b/src/graphics/niche/InkHUD/Events.h
@@ -17,6 +17,7 @@ however this class handles general events which concern InkHUD as a whole, e.g.
#include "./InkHUD.h"
#include "./Persistence.h"
+#include
namespace NicheGraphics::InkHUD
{
@@ -30,12 +31,17 @@ class Events
void onButtonShort(); // User button: short press
void onButtonLong(); // User button: long press
void applyingChanges();
- void onExitShort(); // Exit button: short press
- void onExitLong(); // Exit button: long press
- void onNavUp(); // Navigate up
- void onNavDown(); // Navigate down
- void onNavLeft(); // Navigate left
- void onNavRight(); // Navigate right
+ void onExitShort(); // Exit button: short press
+ void onExitLong(); // Exit button: long press
+ void onNavUp(); // Navigate up
+ void onNavDown(); // Navigate down
+ void onNavLeft(); // Navigate left
+ void onNavRight(); // Navigate right
+ void onTouchNavUp(); // Navigate up from touch input
+ void onTouchNavDown(); // Navigate down from touch input
+ void onTouchNavLeft(); // Navigate left from touch input
+ void onTouchNavRight(); // Navigate right from touch input
+ void onTouchTap(uint16_t x, uint16_t y, bool longPress); // Touch tap/long-press with coordinates
// Free text typing events
void onFreeText(char c); // New freetext character input
@@ -79,8 +85,11 @@ class Events
// If set, InkHUD's data will be erased during onReboot
bool eraseOnReboot = false;
+
+ // Suppress follow-up tap generated immediately after a touch long-press opens menu.
+ uint32_t suppressTouchTapUntilMs = 0;
};
} // namespace NicheGraphics::InkHUD
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/niche/InkHUD/InkHUD.cpp b/src/graphics/niche/InkHUD/InkHUD.cpp
index edffda6b7..9ed88af5f 100644
--- a/src/graphics/niche/InkHUD/InkHUD.cpp
+++ b/src/graphics/niche/InkHUD/InkHUD.cpp
@@ -73,6 +73,24 @@ void InkHUD::InkHUD::begin()
// LogoApplet shows boot screen here
}
+void InkHUD::InkHUD::setTouchEnabledProvider(TouchEnabledProvider provider)
+{
+ touchEnabledProvider = provider;
+}
+
+bool InkHUD::InkHUD::hasTouchEnabledProvider() const
+{
+ return touchEnabledProvider != nullptr;
+}
+
+bool InkHUD::InkHUD::isTouchEnabled() const
+{
+ if (!touchEnabledProvider)
+ return true;
+
+ return touchEnabledProvider();
+}
+
// Call this when your user button gets a short press
// Should be connected to an input source in nicheGraphics.h (NicheGraphics::Inputs::TwoButton?)
void InkHUD::InkHUD::shortpress()
@@ -175,6 +193,92 @@ void InkHUD::InkHUD::navRight()
}
}
+// Call this when touch input needs joystick-like up navigation independent of joystick-enabled mode
+void InkHUD::InkHUD::touchNavUp()
+{
+ switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
+ case 1: // 90 deg
+ events->onTouchNavLeft();
+ break;
+ case 2: // 180 deg
+ events->onTouchNavDown();
+ break;
+ case 3: // 270 deg
+ events->onTouchNavRight();
+ break;
+ default: // 0 deg
+ events->onTouchNavUp();
+ break;
+ }
+}
+
+// Call this when touch input needs joystick-like down navigation independent of joystick-enabled mode
+void InkHUD::InkHUD::touchNavDown()
+{
+ switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
+ case 1: // 90 deg
+ events->onTouchNavRight();
+ break;
+ case 2: // 180 deg
+ events->onTouchNavUp();
+ break;
+ case 3: // 270 deg
+ events->onTouchNavLeft();
+ break;
+ default: // 0 deg
+ events->onTouchNavDown();
+ break;
+ }
+}
+
+// Call this when touch input needs joystick-like left navigation independent of joystick-enabled mode
+void InkHUD::InkHUD::touchNavLeft()
+{
+ switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
+ case 1: // 90 deg
+ events->onTouchNavDown();
+ break;
+ case 2: // 180 deg
+ events->onTouchNavRight();
+ break;
+ case 3: // 270 deg
+ events->onTouchNavUp();
+ break;
+ default: // 0 deg
+ events->onTouchNavLeft();
+ break;
+ }
+}
+
+// Call this when touch input needs joystick-like right navigation independent of joystick-enabled mode
+void InkHUD::InkHUD::touchNavRight()
+{
+ switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
+ case 1: // 90 deg
+ events->onTouchNavUp();
+ break;
+ case 2: // 180 deg
+ events->onTouchNavLeft();
+ break;
+ case 3: // 270 deg
+ events->onTouchNavDown();
+ break;
+ default: // 0 deg
+ events->onTouchNavRight();
+ break;
+ }
+}
+
+void InkHUD::InkHUD::touchTap(uint16_t x, uint16_t y)
+{
+ events->onTouchTap(x, y, false);
+}
+
+void InkHUD::InkHUD::touchLongPress(uint16_t x, uint16_t y)
+{
+ events->onTouchTap(x, y, true);
+}
+
// Call this for keyboard input
// The Keyboard Applet also calls this
void InkHUD::InkHUD::freeText(char c)
@@ -223,6 +327,12 @@ void InkHUD::InkHUD::openMenu()
windowManager->openMenu();
}
+// Show touch-friendly app switcher (on the focused tile)
+void InkHUD::InkHUD::openAppSwitcher()
+{
+ windowManager->openAppSwitcher();
+}
+
// Bring AlignStick applet to the foreground
void InkHUD::InkHUD::openAlignStick()
{
@@ -255,6 +365,16 @@ void InkHUD::InkHUD::prevTile()
windowManager->prevTile();
}
+bool InkHUD::InkHUD::showApplet(uint8_t appletIndex)
+{
+ return windowManager->showApplet(appletIndex);
+}
+
+bool InkHUD::InkHUD::selectTileAt(uint16_t x, uint16_t y)
+{
+ return windowManager->selectTileAt(x, y);
+}
+
// Rotate the display image by 90 degrees
void InkHUD::InkHUD::rotate()
{
@@ -376,4 +496,4 @@ void InkHUD::InkHUD::drawPixel(int16_t x, int16_t y, Color c)
renderer->handlePixel(x, y, c);
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/niche/InkHUD/InkHUD.h b/src/graphics/niche/InkHUD/InkHUD.h
index abd53951a..0c1682637 100644
--- a/src/graphics/niche/InkHUD/InkHUD.h
+++ b/src/graphics/niche/InkHUD/InkHUD.h
@@ -39,6 +39,8 @@ class WindowManager;
class InkHUD
{
public:
+ using TouchEnabledProvider = bool (*)();
+
static InkHUD *getInstance(); // Access to this singleton class
// Configuration
@@ -51,6 +53,11 @@ class InkHUD
void begin();
+ // Optional touch-state provider for reusable touch status indicators.
+ void setTouchEnabledProvider(TouchEnabledProvider provider);
+ bool hasTouchEnabledProvider() const;
+ bool isTouchEnabled() const;
+
// Handle user-button press
// - connected to an input source, in variant nicheGraphics.h
@@ -62,6 +69,12 @@ class InkHUD
void navDown();
void navLeft();
void navRight();
+ void touchNavUp();
+ void touchNavDown();
+ void touchNavLeft();
+ void touchNavRight();
+ void touchTap(uint16_t x, uint16_t y);
+ void touchLongPress(uint16_t x, uint16_t y);
// Freetext handlers
void freeText(char c);
@@ -76,11 +89,14 @@ class InkHUD
void prevApplet();
NicheGraphics::InkHUD::Applet *getActiveApplet();
void openMenu();
+ void openAppSwitcher();
void openAlignStick();
void openKeyboard();
void closeKeyboard();
void nextTile();
void prevTile();
+ bool showApplet(uint8_t appletIndex);
+ bool selectTileAt(uint16_t x, uint16_t y);
void rotate();
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
void toggleBatteryIcon();
@@ -129,6 +145,7 @@ class InkHUD
Events *events = nullptr; // Handle non-specific firmware events
Renderer *renderer = nullptr; // Co-ordinate display updates
WindowManager *windowManager = nullptr; // Multiplexing of applets
+ TouchEnabledProvider touchEnabledProvider = nullptr;
};
} // namespace NicheGraphics::InkHUD
diff --git a/src/graphics/niche/InkHUD/Persistence.h b/src/graphics/niche/InkHUD/Persistence.h
index 5054b7234..187af2129 100644
--- a/src/graphics/niche/InkHUD/Persistence.h
+++ b/src/graphics/niche/InkHUD/Persistence.h
@@ -142,4 +142,4 @@ class Persistence
} // namespace NicheGraphics::InkHUD
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp
index c4a0813d8..804ae8729 100644
--- a/src/graphics/niche/InkHUD/WindowManager.cpp
+++ b/src/graphics/niche/InkHUD/WindowManager.cpp
@@ -3,11 +3,13 @@
#include "./WindowManager.h"
#include "./Applets/System/AlignStick/AlignStickApplet.h"
+#include "./Applets/System/AppSwitcher/AppSwitcherApplet.h"
#include "./Applets/System/BatteryIcon/BatteryIconApplet.h"
#include "./Applets/System/Keyboard/KeyboardApplet.h"
#include "./Applets/System/Logo/LogoApplet.h"
#include "./Applets/System/Menu/MenuApplet.h"
#include "./Applets/System/Notification/NotificationApplet.h"
+#include "./Applets/System/Notification/TouchStatusApplet.h"
#include "./Applets/System/Pairing/PairingApplet.h"
#include "./Applets/System/Placeholder/PlaceholderApplet.h"
#include "./Applets/System/Tips/TipsApplet.h"
@@ -15,6 +17,14 @@
using namespace NicheGraphics;
+namespace
+{
+bool supportsOnScreenKeyboard(const InkHUD::InkHUD *inkhud, const InkHUD::Persistence::Settings *settings)
+{
+ return !inkhud->twoWayRocker && (settings->joystick.enabled || inkhud->hasTouchEnabledProvider());
+}
+} // namespace
+
InkHUD::WindowManager::WindowManager()
{
// Convenient references
@@ -132,6 +142,38 @@ void InkHUD::WindowManager::prevTile()
userTiles.at(settings->userTiles.focused)->requestHighlight();
}
+// Focus the user tile containing a touch coordinate.
+// Returns true only when the focused tile changes.
+bool InkHUD::WindowManager::selectTileAt(uint16_t x, uint16_t y)
+{
+ if (userTiles.size() < 2)
+ return false;
+
+ const int32_t tx = x;
+ const int32_t ty = y;
+
+ for (uint8_t i = 0; i < userTiles.size(); i++) {
+ Tile *tile = userTiles.at(i);
+
+ const int32_t left = tile->getLeft();
+ const int32_t top = tile->getTop();
+ const int32_t right = left + tile->getWidth();
+ const int32_t bottom = top + tile->getHeight();
+
+ if (tx < left || tx >= right || ty < top || ty >= bottom)
+ continue;
+
+ if (settings->userTiles.focused == i)
+ return false;
+
+ settings->userTiles.focused = i;
+ refocusTile();
+ return true;
+ }
+
+ return false;
+}
+
// Show the menu (on the the focused tile)
// The applet previously displayed there will be restored once the menu closes
void InkHUD::WindowManager::openMenu()
@@ -140,6 +182,18 @@ void InkHUD::WindowManager::openMenu()
menu->show(userTiles.at(settings->userTiles.focused));
}
+// Show touch-only app switcher on the focused tile
+void InkHUD::WindowManager::openAppSwitcher()
+{
+ if (!inkhud->hasTouchEnabledProvider())
+ return;
+
+ AppSwitcherApplet *switcher = static_cast(inkhud->getSystemApplet("AppSwitcher"));
+ if (switcher) {
+ switcher->show(userTiles.at(settings->userTiles.focused));
+ }
+}
+
// Bring the AlignStick applet to the foreground
void InkHUD::WindowManager::openAlignStick()
{
@@ -151,7 +205,7 @@ void InkHUD::WindowManager::openAlignStick()
void InkHUD::WindowManager::openKeyboard()
{
- if (!settings->joystick.enabled || inkhud->twoWayRocker)
+ if (!supportsOnScreenKeyboard(inkhud, settings))
return;
KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard");
@@ -165,7 +219,7 @@ void InkHUD::WindowManager::openKeyboard()
void InkHUD::WindowManager::closeKeyboard()
{
- if (!settings->joystick.enabled || inkhud->twoWayRocker)
+ if (!supportsOnScreenKeyboard(inkhud, settings))
return;
KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard");
@@ -279,6 +333,41 @@ void InkHUD::WindowManager::prevApplet()
inkhud->forceUpdate(EInk::UpdateTypes::FAST); // bringToForeground already requested, but we're manually forcing FAST
}
+// Show a specific applet on the focused tile, or focus the tile where it is already shown.
+bool InkHUD::WindowManager::showApplet(uint8_t appletIndex)
+{
+ if (appletIndex >= inkhud->userApplets.size())
+ return false;
+
+ Applet *target = inkhud->userApplets.at(appletIndex);
+ if (!target || !target->isActive())
+ return false;
+
+ // If target is already visible on another tile, just focus that tile.
+ for (uint8_t i = 0; i < userTiles.size(); i++) {
+ if (userTiles.at(i)->getAssignedApplet() == target) {
+ settings->userTiles.focused = i;
+ refocusTile();
+ if (!settings->optionalMenuItems.nextTile)
+ userTiles.at(settings->userTiles.focused)->requestHighlight();
+ inkhud->forceUpdate(EInk::UpdateTypes::FAST);
+ return true;
+ }
+ }
+
+ // Otherwise replace the focused tile's applet.
+ Tile *focused = userTiles.at(settings->userTiles.focused);
+ Applet *current = focused->getAssignedApplet();
+ if (current && current != target)
+ current->sendToBackground();
+
+ focused->assignApplet(target);
+ target->bringToForeground();
+ settings->userTiles.displayedUserApplet[settings->userTiles.focused] = appletIndex;
+ inkhud->forceUpdate(EInk::UpdateTypes::FAST);
+ return true;
+}
+
// Returns active applet
NicheGraphics::InkHUD::Applet *InkHUD::WindowManager::getActiveApplet()
{
@@ -485,14 +574,21 @@ void InkHUD::WindowManager::createSystemApplets()
addSystemApplet("Tips", new TipsApplet, new Tile);
if (settings->joystick.enabled && !inkhud->twoWayRocker) {
addSystemApplet("AlignStick", new AlignStickApplet, new Tile);
+ }
+ if (supportsOnScreenKeyboard(inkhud, settings)) {
addSystemApplet("Keyboard", new KeyboardApplet, new Tile);
}
+ if (inkhud->hasTouchEnabledProvider()) {
+ addSystemApplet("AppSwitcher", new AppSwitcherApplet, nullptr);
+ }
addSystemApplet("Menu", new MenuApplet, nullptr);
// Battery and notifications *behind* the menu
addSystemApplet("Notification", new NotificationApplet, new Tile);
addSystemApplet("BatteryIcon", new BatteryIconApplet, new Tile);
+ if (inkhud->hasTouchEnabledProvider())
+ addSystemApplet("TouchStatus", new TouchStatusApplet, new Tile);
// Special handling only, via Rendering::renderPlaceholders
addSystemApplet("Placeholder", new PlaceholderApplet, nullptr);
@@ -511,6 +607,8 @@ void InkHUD::WindowManager::placeSystemTiles()
inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
if (settings->joystick.enabled && !inkhud->twoWayRocker) {
inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
+ }
+ if (supportsOnScreenKeyboard(inkhud, settings)) {
const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight();
inkhud->getSystemApplet("Keyboard")
->getTile()
@@ -527,6 +625,17 @@ void InkHUD::WindowManager::placeSystemTiles()
batteryIconWidth + 1, // width
batteryIconHeight + 2); // height
+ if (inkhud->hasTouchEnabledProvider()) {
+ const uint16_t touchStatusH = Applet::fontSmall.lineHeight() + 4;
+ inkhud->getSystemApplet("TouchStatus")
+ ->getTile()
+ ->setRegion(0, inkhud->height() - touchStatusH, inkhud->width(), touchStatusH);
+ if (inkhud->isTouchEnabled())
+ inkhud->getSystemApplet("TouchStatus")->sendToBackground();
+ else
+ inkhud->getSystemApplet("TouchStatus")->bringToForeground();
+ }
+
// Note: the tiles of placeholder and menu applets are manipulated specially
// - menuApplet borrows user tiles
// - placeholder applet is temporarily assigned to each user tile of WindowManager::getEmptyTiles
diff --git a/src/graphics/niche/InkHUD/WindowManager.h b/src/graphics/niche/InkHUD/WindowManager.h
index a11688cf5..1ab33c085 100644
--- a/src/graphics/niche/InkHUD/WindowManager.h
+++ b/src/graphics/niche/InkHUD/WindowManager.h
@@ -29,13 +29,16 @@ class WindowManager
void nextTile();
void prevTile();
+ bool selectTileAt(uint16_t x, uint16_t y);
Applet *getActiveApplet();
void openMenu();
void openAlignStick();
+ void openAppSwitcher();
void openKeyboard();
void closeKeyboard();
void nextApplet();
void prevApplet();
+ bool showApplet(uint8_t appletIndex);
void rotate();
void toggleBatteryIcon();
@@ -76,4 +79,4 @@ class WindowManager
} // namespace NicheGraphics::InkHUD
-#endif
\ No newline at end of file
+#endif
diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp
index df8de4905..3b0a46e88 100644
--- a/src/input/ButtonThread.cpp
+++ b/src/input/ButtonThread.cpp
@@ -143,6 +143,10 @@ int32_t ButtonThread::runOnce()
leadUpSequenceActive = false;
resetLeadUpSequence();
}
+#ifdef INPUT_DEBUG
+ if (buttonCurrentlyPressed)
+ LOG_WARN("Button held for %u ms", millis() - buttonPressStartTime);
+#endif
// Progressive lead-up sound system
if (!_suppressLeadUp && buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
@@ -311,7 +315,8 @@ int32_t ButtonThread::runOnce()
void ButtonThread::attachButtonInterrupts()
{
// Interrupt for user button, during normal use. Improves responsiveness.
- attachInterrupt(_pinNum, _intRoutine, CHANGE);
+ if (_intRoutine != nullptr)
+ attachInterrupt(_pinNum, _intRoutine, CHANGE);
}
/*
@@ -320,7 +325,8 @@ void ButtonThread::attachButtonInterrupts()
*/
void ButtonThread::detachButtonInterrupts()
{
- detachInterrupt(_pinNum);
+ if (_intRoutine != nullptr)
+ detachInterrupt(_pinNum);
}
#ifdef ARCH_ESP32
diff --git a/src/input/TouchScreenBase.cpp b/src/input/TouchScreenBase.cpp
index 4fd275a40..0afb13faf 100644
--- a/src/input/TouchScreenBase.cpp
+++ b/src/input/TouchScreenBase.cpp
@@ -9,6 +9,35 @@
#define TIME_LONG_PRESS 400
#endif
+// Touch sampling cadence (milliseconds).
+// Can be overridden by board variants for faster touch panels.
+#ifndef TOUCH_POLL_INTERVAL_IDLE
+#define TOUCH_POLL_INTERVAL_IDLE 100
+#endif
+
+#ifndef TOUCH_POLL_INTERVAL_ACTIVE
+#define TOUCH_POLL_INTERVAL_ACTIVE 20
+#endif
+
+#ifndef TOUCH_POLL_INTERVAL_RELEASE
+#define TOUCH_POLL_INTERVAL_RELEASE 50
+#endif
+
+// Faster cadence used for keyboard-like tap-heavy UIs.
+#ifndef TOUCH_POLL_INTERVAL_ACTIVE_FAST
+#define TOUCH_POLL_INTERVAL_ACTIVE_FAST TOUCH_POLL_INTERVAL_ACTIVE
+#endif
+
+#ifndef TOUCH_POLL_INTERVAL_RELEASE_FAST
+#define TOUCH_POLL_INTERVAL_RELEASE_FAST TOUCH_POLL_INTERVAL_RELEASE
+#endif
+
+// Ignore very short "finger lifted" glitches from noisy touch controllers.
+// A release is only accepted once we've seen no-touch for at least this duration.
+#ifndef TOUCH_RELEASE_GRACE_MS
+#define TOUCH_RELEASE_GRACE_MS 35
+#endif
+
// move a minimum distance over the screen to detect a "swipe"
#ifndef TOUCH_THRESHOLD_X
#define TOUCH_THRESHOLD_X 30
@@ -20,7 +49,7 @@
TouchScreenBase::TouchScreenBase(const char *name, uint16_t width, uint16_t height)
: concurrency::OSThread(name), _display_width(width), _display_height(height), _first_x(0), _last_x(0), _first_y(0),
- _last_y(0), _start(0), _tapped(false), _originName(name)
+ _last_y(0), _start(0), _lastTouchSeenMs(0), _tapped(false), _originName(name)
{
}
@@ -28,7 +57,7 @@ void TouchScreenBase::init(bool hasTouch)
{
if (hasTouch) {
LOG_INFO("TouchScreen initialized %d %d", TOUCH_THRESHOLD_X, TOUCH_THRESHOLD_Y);
- this->setInterval(100);
+ this->setInterval(TOUCH_POLL_INTERVAL_IDLE);
} else {
disable();
this->setInterval(UINT_MAX);
@@ -39,6 +68,8 @@ int32_t TouchScreenBase::runOnce()
{
TouchEvent e;
e.touchEvent = static_cast(TOUCH_ACTION_NONE);
+ const bool fastTapMode = fastTapModeEnabled();
+ const bool allowLongPress = longPressEnabled();
// process touch events
int16_t x, y;
@@ -46,9 +77,13 @@ int32_t TouchScreenBase::runOnce()
if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turning off the screen
touched = false;
if (touched) {
- this->setInterval(20);
+ _lastTouchSeenMs = millis();
+ this->setInterval(fastTapMode ? TOUCH_POLL_INTERVAL_ACTIVE_FAST : TOUCH_POLL_INTERVAL_ACTIVE);
_last_x = x;
_last_y = y;
+ } else if (_touchedOld && ((uint32_t)millis() - _lastTouchSeenMs) < TOUCH_RELEASE_GRACE_MS) {
+ // Treat brief no-touch samples as continuous touch to preserve long-press detection.
+ touched = true;
}
if (touched != _touchedOld) {
if (touched) {
@@ -62,7 +97,7 @@ int32_t TouchScreenBase::runOnce()
time_t duration = millis() - _start;
x = _last_x;
y = _last_y;
- this->setInterval(50);
+ this->setInterval(fastTapMode ? TOUCH_POLL_INTERVAL_RELEASE_FAST : TOUCH_POLL_INTERVAL_RELEASE);
// compute distance
int16_t dx = x - _first_x;
@@ -92,7 +127,7 @@ int32_t TouchScreenBase::runOnce()
}
// tap
else {
- if (duration > 0 && duration < TIME_LONG_PRESS) {
+ if (duration > 0 && (duration < TIME_LONG_PRESS || !allowLongPress)) {
if (_tapped) {
_tapped = false;
} else {
@@ -132,7 +167,7 @@ int32_t TouchScreenBase::runOnce()
#endif
// fire LONG_PRESS event without the need for release
- if (touched && (time_t(millis()) - _start) > TIME_LONG_PRESS) {
+ if (allowLongPress && touched && (time_t(millis()) - _start) > TIME_LONG_PRESS) {
// tricky: prevent reoccurring events and another touch event when releasing
_start = millis() + 30000;
e.touchEvent = static_cast(TOUCH_ACTION_LONG_PRESS);
@@ -157,3 +192,13 @@ void TouchScreenBase::hapticFeedback()
drv.go();
#endif
}
+
+bool TouchScreenBase::fastTapModeEnabled() const
+{
+ return false;
+}
+
+bool TouchScreenBase::longPressEnabled() const
+{
+ return true;
+}
diff --git a/src/input/TouchScreenBase.h b/src/input/TouchScreenBase.h
index 90314cf02..e08c4832b 100644
--- a/src/input/TouchScreenBase.h
+++ b/src/input/TouchScreenBase.h
@@ -35,6 +35,8 @@ class TouchScreenBase : public Observable, public concurrenc
virtual bool getTouch(int16_t &x, int16_t &y) = 0;
virtual void onEvent(const TouchEvent &event) = 0;
+ virtual bool fastTapModeEnabled() const;
+ virtual bool longPressEnabled() const;
volatile TouchScreenBaseStateType _state = TOUCH_EVENT_CLEARED;
volatile TouchScreenBaseEventType _action = TOUCH_ACTION_NONE;
@@ -49,6 +51,7 @@ class TouchScreenBase : public Observable, public concurrenc
int16_t _first_x, _last_x; // horizontal swipe direction
int16_t _first_y, _last_y; // vertical swipe direction
time_t _start; // for LONG_PRESS
+ uint32_t _lastTouchSeenMs; // helps suppress brief touch-controller dropouts
bool _tapped; // for DOUBLE_TAP
const char *_originName;
diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp
index 69dcab04e..0f1c9d023 100644
--- a/src/input/TouchScreenImpl1.cpp
+++ b/src/input/TouchScreenImpl1.cpp
@@ -3,6 +3,12 @@
#include "PowerFSM.h"
#include "configuration.h"
#include "modules/ExternalNotificationModule.h"
+#include
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/niche/InkHUD/SystemApplet.h"
+#endif
#if ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
@@ -29,7 +35,8 @@ void TouchScreenImpl1::init()
return;
#else
TouchScreenBase::init(true);
- inputBroker->registerSource(this);
+ if (inputBroker)
+ inputBroker->registerSource(this);
#endif
}
@@ -38,6 +45,31 @@ bool TouchScreenImpl1::getTouch(int16_t &x, int16_t &y)
return _getTouch(&x, &y);
}
+bool TouchScreenImpl1::fastTapModeEnabled() const
+{
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ const auto *inkhud = NicheGraphics::InkHUD::InkHUD::getInstance();
+ if (!inkhud) {
+ return false;
+ }
+
+ for (auto *sa : inkhud->systemApplets) {
+ if (!sa || !sa->name) {
+ continue;
+ }
+ if (strcmp(sa->name, "Keyboard") == 0) {
+ return sa->isForeground();
+ }
+ }
+#endif
+ return false;
+}
+
+bool TouchScreenImpl1::longPressEnabled() const
+{
+ return !fastTapModeEnabled();
+}
+
/**
* @brief forward touchscreen event
*
@@ -82,4 +114,4 @@ void TouchScreenImpl1::onEvent(const TouchEvent &event)
return;
}
this->notifyObservers(&e);
-}
\ No newline at end of file
+}
diff --git a/src/input/TouchScreenImpl1.h b/src/input/TouchScreenImpl1.h
index 0c5338459..3629982ef 100644
--- a/src/input/TouchScreenImpl1.h
+++ b/src/input/TouchScreenImpl1.h
@@ -10,6 +10,8 @@ class TouchScreenImpl1 : public TouchScreenBase
protected:
virtual bool getTouch(int16_t &x, int16_t &y);
virtual void onEvent(const TouchEvent &event);
+ bool fastTapModeEnabled() const override;
+ bool longPressEnabled() const override;
bool (*_getTouch)(int16_t *, int16_t *);
};
diff --git a/src/main.cpp b/src/main.cpp
index bf0f52626..9b83602d5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -340,138 +340,9 @@ void setup()
#ifdef BLE_LED
pinMode(BLE_LED, OUTPUT);
-#ifdef BLE_LED_INVERTED
- digitalWrite(BLE_LED, HIGH);
-#else
- digitalWrite(BLE_LED, LOW);
-#endif
-#endif
-
-#if defined(T_DECK)
- // GPIO10 manages all peripheral power supplies
- // 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);
- // T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus
- // We need to initialize all CS pins in advance otherwise there will be SPI communication issues
- // e.g. when detecting the SD card
- pinMode(LORA_CS, OUTPUT);
- digitalWrite(LORA_CS, HIGH);
- pinMode(SDCARD_CS, OUTPUT);
- digitalWrite(SDCARD_CS, HIGH);
- pinMode(TFT_CS, OUTPUT);
- digitalWrite(TFT_CS, HIGH);
- delay(100);
-#elif defined(T_DECK_PRO)
- pinMode(LORA_EN, OUTPUT);
- digitalWrite(LORA_EN, HIGH);
- pinMode(LORA_CS, OUTPUT);
- digitalWrite(LORA_CS, HIGH);
- pinMode(SDCARD_CS, OUTPUT);
- digitalWrite(SDCARD_CS, HIGH);
- pinMode(PIN_EINK_CS, OUTPUT);
- digitalWrite(PIN_EINK_CS, HIGH);
-#if PIN_EINK_RES >= 0
- pinMode(PIN_EINK_RES, OUTPUT);
- digitalWrite(PIN_EINK_RES, HIGH);
-#endif
- pinMode(CST328_PIN_RST, OUTPUT);
- digitalWrite(CST328_PIN_RST, HIGH);
-#elif defined(T_LORA_PAGER)
- pinMode(LORA_CS, OUTPUT);
- digitalWrite(LORA_CS, HIGH);
- pinMode(SDCARD_CS, OUTPUT);
- digitalWrite(SDCARD_CS, HIGH);
- pinMode(TFT_CS, OUTPUT);
- digitalWrite(TFT_CS, HIGH);
- pinMode(KB_INT, INPUT_PULLUP);
- // io expander
- io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
- io.pinMode(EXPANDS_DRV_EN, OUTPUT);
- io.digitalWrite(EXPANDS_DRV_EN, HIGH);
- io.pinMode(EXPANDS_AMP_EN, OUTPUT);
- io.digitalWrite(EXPANDS_AMP_EN, LOW);
- io.pinMode(EXPANDS_LORA_EN, OUTPUT);
- io.digitalWrite(EXPANDS_LORA_EN, HIGH);
- io.pinMode(EXPANDS_GPS_EN, OUTPUT);
- io.digitalWrite(EXPANDS_GPS_EN, HIGH);
- io.pinMode(EXPANDS_KB_EN, OUTPUT);
- io.digitalWrite(EXPANDS_KB_EN, HIGH);
- io.pinMode(EXPANDS_SD_EN, OUTPUT);
- io.digitalWrite(EXPANDS_SD_EN, HIGH);
- io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
- io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
- io.pinMode(EXPANDS_SD_PULLEN, INPUT);
-#elif defined(HACKADAY_COMMUNICATOR)
- pinMode(KB_INT, INPUT);
digitalWrite(BLE_LED, LED_STATE_OFF);
#endif
-#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);
- // T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus
- // We need to initialize all CS pins in advance otherwise there will be SPI communication issues
- // e.g. when detecting the SD card
- pinMode(LORA_CS, OUTPUT);
- digitalWrite(LORA_CS, HIGH);
- pinMode(SDCARD_CS, OUTPUT);
- digitalWrite(SDCARD_CS, HIGH);
- pinMode(TFT_CS, OUTPUT);
- digitalWrite(TFT_CS, HIGH);
- delay(100);
-#elif defined(T_DECK_PRO)
- pinMode(LORA_EN, OUTPUT);
- digitalWrite(LORA_EN, HIGH);
- pinMode(LORA_CS, OUTPUT);
- digitalWrite(LORA_CS, HIGH);
- pinMode(SDCARD_CS, OUTPUT);
- digitalWrite(SDCARD_CS, HIGH);
- pinMode(PIN_EINK_CS, OUTPUT);
- digitalWrite(PIN_EINK_CS, HIGH);
-#if PIN_EINK_RES >= 0
- pinMode(PIN_EINK_RES, OUTPUT);
- digitalWrite(PIN_EINK_RES, HIGH);
-#endif
- pinMode(CST328_PIN_RST, OUTPUT);
- digitalWrite(CST328_PIN_RST, HIGH);
-#elif defined(T_LORA_PAGER)
- pinMode(LORA_CS, OUTPUT);
- digitalWrite(LORA_CS, HIGH);
- pinMode(SDCARD_CS, OUTPUT);
- digitalWrite(SDCARD_CS, HIGH);
- pinMode(TFT_CS, OUTPUT);
- digitalWrite(TFT_CS, HIGH);
- pinMode(KB_INT, INPUT_PULLUP);
- // io expander
- io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
- io.pinMode(EXPANDS_DRV_EN, OUTPUT);
- io.digitalWrite(EXPANDS_DRV_EN, HIGH);
- io.pinMode(EXPANDS_AMP_EN, OUTPUT);
- io.digitalWrite(EXPANDS_AMP_EN, LOW);
- io.pinMode(EXPANDS_LORA_EN, OUTPUT);
- io.digitalWrite(EXPANDS_LORA_EN, HIGH);
- io.pinMode(EXPANDS_GPS_EN, OUTPUT);
- io.digitalWrite(EXPANDS_GPS_EN, HIGH);
- io.pinMode(EXPANDS_KB_EN, OUTPUT);
- io.digitalWrite(EXPANDS_KB_EN, HIGH);
- io.pinMode(EXPANDS_SD_EN, OUTPUT);
- io.digitalWrite(EXPANDS_SD_EN, HIGH);
- io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
- io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
- io.pinMode(EXPANDS_SD_PULLEN, INPUT);
-#elif defined(HACKADAY_COMMUNICATOR)
- pinMode(KB_INT, INPUT);
-#endif
-
concurrency::hasBeenSetup = true;
#if HAS_SCREEN
meshtastic_Config_DisplayConfig_OledType screen_model =
diff --git a/src/mesh/HardwareRNG.cpp b/src/mesh/HardwareRNG.cpp
index f5a805487..b79b0d012 100644
--- a/src/mesh/HardwareRNG.cpp
+++ b/src/mesh/HardwareRNG.cpp
@@ -48,8 +48,11 @@ bool mixWithLoRaEntropy(uint8_t *buffer, size_t length)
// and return false so callers know no extra mixing occurred.
RadioLibInterface *radio = RadioLibInterface::instance;
if (!radio) {
- // Intentionally silent: this path runs during portduinoSetup() before the
- // console/SerialConsole is initialized, so LOG_* here would dereference a null pointer.
+ // This path can run during portduinoSetup() before the console is initialized,
+ // both for unit-test binaries and the simulator's meshtasticd; LOG_* dereferences `console`.
+ if (console) {
+ LOG_ERROR("No radio instance available to provide entropy");
+ }
return false;
}
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 13f948a7b..e8613d457 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -101,9 +101,12 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
if (origTx) {
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came
// directly from the destination
- bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
+ // Single lookup for both relayer checks on the same (request_id, to) pair
+ bool wasAlreadyRelayer = false;
bool weWereSoleRelayer = false;
- bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
+ bool weWereRelayer = false;
+ checkRelayers(p->relay_node, ourRelayID, p->decoded.request_id, p->to, &wasAlreadyRelayer, &weWereRelayer,
+ &weWereSoleRelayer);
if ((weWereRelayer && wasAlreadyRelayer) || (getHopsAway(*p) == 0 && weWereSoleRelayer)) {
if (origTx->next_hop != p->relay_node) { // Not already set
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply (was relayer %d we were sole %d)", p->from,
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 845a936d4..8289f0078 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -1,6 +1,7 @@
#include "PacketHistory.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
+#include "meshUtils.h"
#ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
@@ -23,6 +24,14 @@ PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPa
size = PACKETHISTORY_MAX; // Use default size if invalid
}
+#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
+ // Ensure capacity fits in uint16_t hash index (HASH_EMPTY = 0xFFFF is the sentinel)
+ if (size >= HASH_EMPTY) {
+ LOG_WARN("Packet History - Clamping size %d to %d (hash index limit)", size, HASH_EMPTY - 1);
+ size = HASH_EMPTY - 1;
+ }
+#endif
+
// Allocate memory for the recent packets array
recentPacketsCapacity = size;
recentPackets = new PacketRecord[recentPacketsCapacity];
@@ -35,6 +44,20 @@ PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPa
// Initialize the recent packets array to zero
memset(recentPackets, 0, sizeof(PacketRecord) * recentPacketsCapacity);
+
+#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
+ // Allocate hash index with load factor <= 0.5 for short probe chains
+ hashCapacity = nextPowerOf2(recentPacketsCapacity * 2);
+ hashMask = hashCapacity - 1;
+ hashIndex = new uint16_t[hashCapacity];
+ if (!hashIndex) {
+ LOG_ERROR("Packet History - Hash index allocation failed for %d entries", hashCapacity);
+ hashCapacity = 0;
+ hashMask = 0;
+ return;
+ }
+ memset(hashIndex, 0xFF, sizeof(uint16_t) * hashCapacity); // Fill with HASH_EMPTY (0xFFFF)
+#endif
}
PacketHistory::~PacketHistory()
@@ -42,6 +65,12 @@ PacketHistory::~PacketHistory()
recentPacketsCapacity = 0;
delete[] recentPackets;
recentPackets = NULL;
+#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
+ delete[] hashIndex;
+ hashIndex = NULL;
+ hashCapacity = 0;
+ hashMask = 0;
+#endif
}
/** Update recentPackets and return true if we have already seen this packet */
@@ -194,7 +223,78 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
return seenRecently;
}
-/** Find a packet record in history.
+#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
+// Hash function for (sender, id) pairs. Uses xor-shift mixing for good distribution.
+uint32_t PacketHistory::hashSlot(NodeNum sender, PacketId id) const
+{
+ uint32_t h = sender ^ (id * 0x9E3779B9); // Fibonacci hashing constant
+ h ^= h >> 16;
+ h *= 0x45d9f3b;
+ h ^= h >> 16;
+ return h & hashMask;
+}
+
+void PacketHistory::hashInsert(NodeNum sender, PacketId id, uint16_t slotIdx)
+{
+ if (!hashIndex)
+ return;
+ uint32_t bucket = hashSlot(sender, id);
+ // Guard against infinite loop if hash table is corrupted (no HASH_EMPTY slots)
+ for (uint32_t i = 0; i < hashCapacity; i++) {
+ if (hashIndex[bucket] == HASH_EMPTY) {
+ hashIndex[bucket] = slotIdx;
+ return;
+ }
+ bucket = (bucket + 1) & hashMask;
+ }
+ LOG_ERROR("Packet History - hashInsert: table full or corrupted, rebuilding");
+ hashRebuild();
+}
+
+void PacketHistory::hashRemove(NodeNum sender, PacketId id)
+{
+ if (!hashIndex)
+ return;
+ uint32_t bucket = hashSlot(sender, id);
+ for (uint32_t i = 0; i < hashCapacity; i++) {
+ if (hashIndex[bucket] == HASH_EMPTY)
+ return;
+ uint16_t idx = hashIndex[bucket];
+ if (idx < recentPacketsCapacity && recentPackets[idx].sender == sender && recentPackets[idx].id == id) {
+ // Found it — delete and re-insert subsequent entries to maintain probe chain integrity
+ hashIndex[bucket] = HASH_EMPTY;
+ uint32_t next = (bucket + 1) & hashMask;
+ for (uint32_t j = 0; j < hashCapacity; j++) {
+ if (hashIndex[next] == HASH_EMPTY)
+ break;
+ uint16_t displaced = hashIndex[next];
+ hashIndex[next] = HASH_EMPTY;
+ if (displaced < recentPacketsCapacity) {
+ const auto &rec = recentPackets[displaced];
+ hashInsert(rec.sender, rec.id, displaced);
+ }
+ next = (next + 1) & hashMask;
+ }
+ return;
+ }
+ bucket = (bucket + 1) & hashMask;
+ }
+}
+
+void PacketHistory::hashRebuild()
+{
+ if (!hashIndex)
+ return;
+ memset(hashIndex, 0xFF, sizeof(uint16_t) * hashCapacity);
+ for (uint32_t i = 0; i < recentPacketsCapacity; i++) {
+ if (recentPackets[i].rxTimeMsec != 0)
+ hashInsert(recentPackets[i].sender, recentPackets[i].id, (uint16_t)i);
+ }
+}
+#endif
+
+/** Find a packet record in history using the hash index for O(1) average lookup.
+ * Falls back to linear scan if hash index is unavailable.
* @return pointer to PacketRecord if found, NULL if not found */
PacketHistory::PacketRecord *PacketHistory::find(NodeNum sender, PacketId id)
{
@@ -205,23 +305,40 @@ PacketHistory::PacketRecord *PacketHistory::find(NodeNum sender, PacketId id)
return NULL;
}
- PacketRecord *it = NULL;
- for (it = recentPackets; it < (recentPackets + recentPacketsCapacity); ++it) {
- if (it->id == id && it->sender == sender) {
+#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
+ // Use hash index for O(1) lookup when available
+ if (hashIndex) {
+ uint32_t bucket = hashSlot(sender, id);
+ for (uint32_t i = 0; i < hashCapacity; i++) {
+ if (hashIndex[bucket] == HASH_EMPTY)
+ break;
+ uint16_t idx = hashIndex[bucket];
+ if (idx < recentPacketsCapacity && recentPackets[idx].id == id && recentPackets[idx].sender == sender) {
#if VERBOSE_PACKET_HISTORY
- LOG_DEBUG("Packet History - find: s=%08x id=%08x FOUND nh=%02x rby=%02x %02x %02x age=%d slot=%d/%d", it->sender,
- it->id, it->next_hop, it->relayed_by[0], it->relayed_by[1], it->relayed_by[2], millis() - (it->rxTimeMsec),
- it - recentPackets, recentPacketsCapacity);
+ LOG_DEBUG("Packet History - find: s=%08x id=%08x FOUND nh=%02x rby=%02x %02x %02x age=%d slot=%d/%d",
+ recentPackets[idx].sender, recentPackets[idx].id, recentPackets[idx].next_hop,
+ recentPackets[idx].relayed_by[0], recentPackets[idx].relayed_by[1], recentPackets[idx].relayed_by[2],
+ millis() - (recentPackets[idx].rxTimeMsec), idx, recentPacketsCapacity);
#endif
- // only the first match is returned, so be careful not to create duplicate entries
- return it; // Return pointer to the found record
+ return &recentPackets[idx];
+ }
+ bucket = (bucket + 1) & hashMask;
+ }
+#if VERBOSE_PACKET_HISTORY
+ LOG_DEBUG("Packet History - find: s=%08x id=%08x NOT FOUND", sender, id);
+#endif
+ return NULL;
+ }
+#endif
+
+ // Linear scan (sole path when hash excluded, fallback when hash allocation failed)
+ for (PacketRecord *it = recentPackets; it < (recentPackets + recentPacketsCapacity); ++it) {
+ if (it->id == id && it->sender == sender) {
+ return it;
}
}
-#if VERBOSE_PACKET_HISTORY
- LOG_DEBUG("Packet History - find: s=%08x id=%08x NOT FOUND", sender, id);
-#endif
- return NULL; // Not found
+ return NULL;
}
/** Insert/Replace oldest PacketRecord in recentPackets. */
@@ -327,8 +444,22 @@ void PacketHistory::insert(const PacketRecord &r)
return; // Return early if we can't update the history
}
+#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
+ // Maintain hash index: remove old entry if evicting a different packet, then insert new entry
+ bool isMatchingSlot = (tu->id == r.id && tu->sender == r.sender);
+ if (!isMatchingSlot && tu->rxTimeMsec != 0) {
+ hashRemove(tu->sender, tu->id);
+ }
+
*tu = r; // store the packet
+ if (!isMatchingSlot) {
+ hashInsert(r.sender, r.id, (uint16_t)(tu - recentPackets));
+ }
+#else
+ *tu = r; // store the packet
+#endif
+
#if VERBOSE_PACKET_HISTORY
LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d AFTER",
tu - recentPackets, recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1],
@@ -396,6 +527,31 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, boo
return found;
}
+// Check two relayers against the same packet record with a single find() call,
+// avoiding redundant O(N) lookups when both are checked for the same (id, sender) pair.
+void PacketHistory::checkRelayers(uint8_t relayer1, uint8_t relayer2, uint32_t id, NodeNum sender, bool *r1Result, bool *r2Result,
+ bool *r2WasSole)
+{
+ *r1Result = false;
+ *r2Result = false;
+ if (r2WasSole)
+ *r2WasSole = false;
+
+ if (!initOk()) {
+ LOG_ERROR("PacketHistory - checkRelayers: NOT INITIALIZED!");
+ return;
+ }
+
+ const PacketRecord *found = find(sender, id);
+ if (!found)
+ return;
+
+ if (relayer1 != 0)
+ *r1Result = wasRelayer(relayer1, *found);
+ if (relayer2 != 0)
+ *r2Result = wasRelayer(relayer2, *found, r2WasSole);
+}
+
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h
index 9b6a93280..a11e2d038 100644
--- a/src/mesh/PacketHistory.h
+++ b/src/mesh/PacketHistory.h
@@ -28,6 +28,22 @@ class PacketHistory
0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
PacketRecord *recentPackets = NULL; // Simple and fixed in size. Debloat.
+#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
+ // Open-addressing hash table for O(1) lookup in find(), replacing the O(N) linear scan.
+ // Maps (sender, id) -> index into recentPackets[]. Uses linear probing with a load factor <= 0.5.
+ // The load factor invariant holds permanently: hashCapacity = 2 * nextPowerOf2(recentPacketsCapacity),
+ // and at most recentPacketsCapacity entries can ever be live (one per recentPackets[] slot).
+ static constexpr uint16_t HASH_EMPTY = 0xFFFF;
+ uint16_t *hashIndex = NULL;
+ uint32_t hashCapacity = 0; // Always a power of 2
+ uint32_t hashMask = 0; // hashCapacity - 1, for fast modular indexing
+
+ uint32_t hashSlot(NodeNum sender, PacketId id) const;
+ void hashInsert(NodeNum sender, PacketId id, uint16_t slotIdx);
+ void hashRemove(NodeNum sender, PacketId id);
+ void hashRebuild();
+#endif
+
/** Find a packet record in history.
* @param sender NodeNum
* @param id PacketId
@@ -70,6 +86,16 @@ class PacketHistory
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole = nullptr);
+ /**
+ * Check two relayers against the same packet record with a single lookup.
+ * Avoids redundant find() calls when checking multiple relayers for the same (id, sender) pair.
+ * @param r1Result set to true if relayer1 was a relayer
+ * @param r2Result set to true if relayer2 was a relayer
+ * @param r2WasSole if not nullptr, set to true if relayer2 was the sole relayer
+ */
+ void checkRelayers(uint8_t relayer1, uint8_t relayer2, uint32_t id, NodeNum sender, bool *r1Result, bool *r2Result,
+ bool *r2WasSole = nullptr);
+
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 714e61108..cb25efb77 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -470,8 +470,13 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_traffic_management_tag;
fromRadioScratch.moduleConfig.payload_variant.traffic_management = moduleConfig.traffic_management;
break;
+ case meshtastic_ModuleConfig_tak_tag:
+ LOG_DEBUG("Send module config: tak");
+ fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_tak_tag;
+ fromRadioScratch.moduleConfig.payload_variant.tak = moduleConfig.tak;
+ break;
default:
- LOG_ERROR("Unknown module config type %d", config_state);
+ LOG_DEBUG("Unhandled module config type %d", config_state);
}
config_state++;
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index 7ef707e0d..6024d06b6 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -46,6 +46,16 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c
#endif
}
+RadioLibInterface::~RadioLibInterface()
+{
+ // If the static `instance` pointer still references us, clear it.
+ // A later successful init() may have replaced `instance` with a newer
+ // interface — don't clobber that case.
+ if (instance == this) {
+ instance = nullptr;
+ }
+}
+
#ifdef ARCH_ESP32
// ESP32 doesn't use that flag
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index 310ca76bb..2859558ed 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -136,6 +136,13 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy, PhysicalLayer *iface = NULL);
+ /**
+ * Clear the static `instance` pointer if it still points at us, so callers
+ * that check `RadioLibInterface::instance != nullptr` don't dereference a
+ * freed object after a failed init() + unique_ptr reset.
+ */
+ virtual ~RadioLibInterface();
+
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
/**
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 836cd1a22..ffeb7c539 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -499,9 +499,9 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
meshtastic_Data decodedtmp;
memset(&decodedtmp, 0, sizeof(decodedtmp));
if (!pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp)) {
- LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
+ LOG_DEBUG("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)", p->id);
} else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
- LOG_ERROR("Invalid portnum (bad psk?)!");
+ LOG_DEBUG("Invalid portnum (bad psk?)");
#if !(MESHTASTIC_EXCLUDE_PKI)
} else if (!owner.is_licensed && isToUs(p) && decodedtmp.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
LOG_WARN("Rejecting legacy DM");
@@ -736,9 +736,13 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
// Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
- // Store a copy of encrypted packet for MQTT
+ // Store a copy of the encrypted packet for MQTT.
+ // Local, not a class member: handleReceived re-enters itself when a module
+ // reply broadcast goes through MeshService::sendToMesh -> Router::sendLocal,
+ // and a member would be silently overwritten without release on the inner
+ // call. Each invocation now owns its own copy (issue #9632, #10101, #8729).
DEBUG_HEAP_BEFORE;
- p_encrypted = packetPool.allocCopy(*p);
+ meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted);
// Take those raw bytes and convert them back into a well structured protobuf we can understand
@@ -832,8 +836,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
#endif
}
- packetPool.release(p_encrypted); // Release the encrypted packet
- p_encrypted = nullptr;
+ packetPool.release(p_encrypted); // Release the encrypted packet (release() handles nullptr)
}
void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
diff --git a/src/mesh/Router.h b/src/mesh/Router.h
index 0f342d57b..bd4188693 100644
--- a/src/mesh/Router.h
+++ b/src/mesh/Router.h
@@ -92,9 +92,6 @@ class Router : protected concurrency::OSThread, protected PacketHistory
before us */
uint32_t rxDupe = 0, txRelayCanceled = 0;
- // pointer to the encrypted packet
- meshtastic_MeshPacket *p_encrypted = nullptr;
-
protected:
friend class RoutingModule;
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index bcb08f2c5..44c4a805a 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -455,6 +455,15 @@ template void SX126xInterface::resetAGC()
// RX boosted gain mode
lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
+ // Re-apply the undocumented 0x8B5 RX sensitivity patch that was set in init().
+ // The CALIBRATE_ALL (0x7F) command above clears bit 0 of register 0x8B5, which
+ // silently removes the RX sensitivity improvement introduced in #9571 / #9777.
+ // Without this re-apply, every SX1262 node loses its RX boost ~60s after boot
+ // and never recovers until reboot. See empirical evidence in the PR description.
+ if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) != RADIOLIB_ERR_NONE) {
+ LOG_WARN("SX126x resetAGC: failed to re-apply 0x8B5 RX sensitivity patch");
+ }
+
// 6. Resume receiving
startReceive();
}
diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp
index 75195bd42..3798daf28 100644
--- a/src/mesh/TypeConversions.cpp
+++ b/src/mesh/TypeConversions.cpp
@@ -1,6 +1,7 @@
#include "TypeConversions.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
+#include "meshUtils.h"
meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite)
{
@@ -81,7 +82,11 @@ meshtastic_UserLite TypeConversions::ConvertToUserLite(meshtastic_User user)
meshtastic_UserLite lite = meshtastic_UserLite_init_default;
strncpy(lite.long_name, user.long_name, sizeof(lite.long_name));
+ lite.long_name[sizeof(lite.long_name) - 1] = '\0';
+ sanitizeUtf8(lite.long_name, sizeof(lite.long_name));
strncpy(lite.short_name, user.short_name, sizeof(lite.short_name));
+ lite.short_name[sizeof(lite.short_name) - 1] = '\0';
+ sanitizeUtf8(lite.short_name, sizeof(lite.short_name));
lite.hw_model = user.hw_model;
lite.role = user.role;
lite.is_licensed = user.is_licensed;
@@ -99,7 +104,11 @@ meshtastic_User TypeConversions::ConvertToUser(uint32_t nodeNum, meshtastic_User
snprintf(user.id, sizeof(user.id), "!%08x", nodeNum);
strncpy(user.long_name, lite.long_name, sizeof(user.long_name));
+ user.long_name[sizeof(user.long_name) - 1] = '\0';
+ sanitizeUtf8(user.long_name, sizeof(user.long_name));
strncpy(user.short_name, lite.short_name, sizeof(user.short_name));
+ user.short_name[sizeof(user.short_name) - 1] = '\0';
+ sanitizeUtf8(user.short_name, sizeof(user.short_name));
user.hw_model = lite.hw_model;
user.role = lite.role;
user.is_licensed = lite.is_licensed;
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index c82dd5ff5..0e14334d5 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -285,7 +285,18 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode {
/* Nepal 865MHz */
meshtastic_Config_LoRaConfig_RegionCode_NP_865 = 25,
/* Brazil 902MHz */
- meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26
+ meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26,
+ /* ITU Region 1 Amateur Radio 2m band (144-146 MHz) */
+ meshtastic_Config_LoRaConfig_RegionCode_ITU1_2M = 27,
+ /* ITU Region 2 / 3 Amateur Radio 2m band (144-148 MHz) */
+ meshtastic_Config_LoRaConfig_RegionCode_ITU23_2M = 28,
+ /* EU 866MHz band (Band no. 47b of 2006/771/EC and subsequent amendments) for Non-specific short-range devices (SRD) */
+ meshtastic_Config_LoRaConfig_RegionCode_EU_866 = 29,
+ /* EU 874MHz and 917MHz bands (Band no. 1 and 4 of 2022/172/EC and subsequent amendments) for Non-specific short-range devices (SRD) */
+ meshtastic_Config_LoRaConfig_RegionCode_EU_874 = 30,
+ meshtastic_Config_LoRaConfig_RegionCode_EU_917 = 31,
+ /* EU 868MHz band, with narrow presets */
+ meshtastic_Config_LoRaConfig_RegionCode_EU_N_868 = 32
} meshtastic_Config_LoRaConfig_RegionCode;
/* Standard predefined channel settings
@@ -315,7 +326,24 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset {
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO = 8,
/* Long Range - Turbo
This preset performs similarly to LongFast, but with 500Khz bandwidth. */
- meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO = 9
+ meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO = 9,
+ /* Lite Fast
+ Medium range preset optimized for EU 866MHz SRD band with 125kHz bandwidth.
+ Comparable link budget to MEDIUM_FAST but compliant with Band no. 47b of 2006/771/EC. */
+ meshtastic_Config_LoRaConfig_ModemPreset_LITE_FAST = 10,
+ /* Lite Slow
+ Medium-to-moderate range preset optimized for EU 866MHz SRD band with 125kHz bandwidth.
+ Comparable link budget to LONG_FAST but compliant with Band no. 47b of 2006/771/EC. */
+ meshtastic_Config_LoRaConfig_ModemPreset_LITE_SLOW = 11,
+ /* Narrow Fast
+ Medium-to-moderate range preset optimized for EU 868MHz band with 62.5kHz bandwidth.
+ Comparable link budget to SHORT_SLOW, but with half the data rate.
+ Intended to avoid interference with other devices. */
+ meshtastic_Config_LoRaConfig_ModemPreset_NARROW_FAST = 12,
+ /* Narrow Slow
+ Moderate range preset optimized for EU 868MHz band with 62.5kHz bandwidth.
+ Comparable link budget and data rate to LONG_FAST. */
+ meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW = 13
} meshtastic_Config_LoRaConfig_ModemPreset;
typedef enum _meshtastic_Config_LoRaConfig_FEM_LNA_Mode {
@@ -702,12 +730,12 @@ extern "C" {
#define _meshtastic_Config_DisplayConfig_CompassOrientation_ARRAYSIZE ((meshtastic_Config_DisplayConfig_CompassOrientation)(meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED+1))
#define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET
-#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_BR_902
-#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_BR_902+1))
+#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_EU_N_868
+#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_EU_N_868+1))
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
-#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO
-#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO+1))
+#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW
+#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW+1))
#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED
#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MAX meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index d7ff32cb4..f22825030 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -315,6 +315,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_THINKNODE_M7 = 129,
meshtastic_HardwareModel_THINKNODE_M8 = 130,
meshtastic_HardwareModel_THINKNODE_M9 = 131,
+ /* The Heltec-V4-R8 uses an ESP32S3R8 chip, plus an SX1262. */
+ meshtastic_HardwareModel_HELTEC_V4_R8 = 132,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp
index 1a4497101..f2ee20589 100644
--- a/src/meshUtils.cpp
+++ b/src/meshUtils.cpp
@@ -117,4 +117,93 @@ size_t pb_string_length(const char *str, size_t max_len)
}
}
return len;
+}
+
+bool sanitizeUtf8(char *buf, size_t bufSize)
+{
+ if (!buf || bufSize == 0)
+ return false;
+
+ // Ensure null-terminated within buffer; report if we had to enforce it
+ bool replaced = (buf[bufSize - 1] != '\0');
+ buf[bufSize - 1] = '\0';
+
+ size_t i = 0;
+ size_t len = strlen(buf);
+
+ while (i < len) {
+ uint8_t b = (uint8_t)buf[i];
+
+ // Determine expected sequence length from lead byte
+ size_t seqLen;
+ uint32_t minCodepoint;
+ if (b <= 0x7F) {
+ // ASCII — valid single byte
+ i++;
+ continue;
+ } else if ((b & 0xE0) == 0xC0) {
+ seqLen = 2;
+ minCodepoint = 0x80; // Reject overlong
+ } else if ((b & 0xF0) == 0xE0) {
+ seqLen = 3;
+ minCodepoint = 0x800;
+ } else if ((b & 0xF8) == 0xF0) {
+ seqLen = 4;
+ minCodepoint = 0x10000;
+ } else {
+ // Invalid lead byte (0x80-0xBF or 0xF8+)
+ buf[i] = '?';
+ replaced = true;
+ i++;
+ continue;
+ }
+
+ // Check that we have enough bytes remaining
+ if (i + seqLen > len) {
+ // Truncated sequence at end of string — replace remaining bytes
+ for (size_t j = i; j < len; j++) {
+ buf[j] = '?';
+ }
+ replaced = true;
+ break;
+ }
+
+ // Validate continuation bytes (must be 10xxxxxx)
+ bool valid = true;
+ for (size_t j = 1; j < seqLen; j++) {
+ if (((uint8_t)buf[i + j] & 0xC0) != 0x80) {
+ valid = false;
+ break;
+ }
+ }
+
+ if (valid) {
+ // Decode codepoint to check for overlong encodings and surrogates
+ uint32_t cp = 0;
+ if (seqLen == 2)
+ cp = b & 0x1F;
+ else if (seqLen == 3)
+ cp = b & 0x0F;
+ else
+ cp = b & 0x07;
+ for (size_t j = 1; j < seqLen; j++)
+ cp = (cp << 6) | ((uint8_t)buf[i + j] & 0x3F);
+
+ if (cp < minCodepoint || cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) {
+ // Overlong encoding, out of Unicode range, or surrogate half
+ valid = false;
+ }
+ }
+
+ if (valid) {
+ i += seqLen;
+ } else {
+ // Replace only the lead byte; continuation bytes will be caught on next iteration
+ buf[i] = '?';
+ replaced = true;
+ i++;
+ }
+ }
+
+ return replaced;
}
\ No newline at end of file
diff --git a/src/meshUtils.h b/src/meshUtils.h
index da3a4593b..4c450b3c4 100644
--- a/src/meshUtils.h
+++ b/src/meshUtils.h
@@ -11,6 +11,24 @@ template constexpr const T &clamp(const T &v, const T &lo, const T &hi
return (v < lo) ? lo : (hi < v) ? hi : v;
}
+/// Return the smallest power of 2 >= n (undefined for n > 2^31)
+static inline uint32_t nextPowerOf2(uint32_t n)
+{
+ if (n <= 1)
+ return 1;
+#if defined(__GNUC__)
+ return 1U << (32 - __builtin_clz(n - 1));
+#else
+ n--;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+ return n + 1;
+#endif
+}
+
#if HAS_SCREEN
#define IF_SCREEN(X) \
if (screen) { \
@@ -38,6 +56,10 @@ const std::string vformat(const char *const zcFormat, ...);
// Get actual string length for nanopb char array fields.
size_t pb_string_length(const char *str, size_t max_len);
+// Sanitize a fixed-size char buffer in-place by replacing invalid UTF-8 sequences with '?'.
+// Ensures the result is null-terminated within bufSize. Returns true if any bytes were replaced.
+bool sanitizeUtf8(char *buf, size_t bufSize);
+
/// Calculate 2^n without calling pow() - used for spreading factor and other calculations
inline uint32_t pow_of_2(uint32_t n)
{
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 852a257e5..865ac38f5 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -626,10 +626,14 @@ void AdminModule::handleSetOwner(const meshtastic_User &o)
if (*o.long_name) {
changed |= strcmp(owner.long_name, o.long_name);
strncpy(owner.long_name, o.long_name, sizeof(owner.long_name));
+ owner.long_name[sizeof(owner.long_name) - 1] = '\0';
+ sanitizeUtf8(owner.long_name, sizeof(owner.long_name));
}
if (*o.short_name) {
changed |= strcmp(owner.short_name, o.short_name);
strncpy(owner.short_name, o.short_name, sizeof(owner.short_name));
+ owner.short_name[sizeof(owner.short_name) - 1] = '\0';
+ sanitizeUtf8(owner.short_name, sizeof(owner.short_name));
}
snprintf(owner.id, sizeof(owner.id), "!%08x", nodeDB->getNodeNum());
@@ -1430,7 +1434,11 @@ void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)
// Set call sign and override lora limitations for licensed use
strncpy(owner.long_name, p.call_sign, sizeof(owner.long_name));
+ owner.long_name[sizeof(owner.long_name) - 1] = '\0';
+ sanitizeUtf8(owner.long_name, sizeof(owner.long_name));
strncpy(owner.short_name, p.short_name, sizeof(owner.short_name));
+ owner.short_name[sizeof(owner.short_name) - 1] = '\0';
+ sanitizeUtf8(owner.short_name, sizeof(owner.short_name));
owner.is_licensed = true;
config.lora.override_duty_cycle = true;
config.lora.tx_power = p.tx_power;
diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp
index c435beb67..d9bbda7b9 100644
--- a/src/modules/PositionModule.cpp
+++ b/src/modules/PositionModule.cpp
@@ -492,15 +492,24 @@ void PositionModule::sendLostAndFoundText()
{
meshtastic_MeshPacket *p = allocDataPacket();
p->to = NODENUM_BROADCAST;
- char *message = new char[60];
- sprintf(message, "🚨I'm lost! Lat / Lon: %f, %f\a", (lastGpsLatitude * 1e-7), (lastGpsLongitude * 1e-7));
+ char message[128];
+ int written = snprintf(message, sizeof(message), "🚨I'm lost! Lat / Lon: %f, %f\a", (lastGpsLatitude * 1e-7),
+ (lastGpsLongitude * 1e-7));
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
p->want_ack = false;
- p->decoded.payload.size = strlen(message);
- memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size);
+ if (written < 0) {
+ // snprintf encoding error — send an empty payload rather than uninitialized bytes.
+ p->decoded.payload.size = 0;
+ } else {
+ // Clamp to buffer capacity (snprintf returns "would-have-written" which can exceed the buffer).
+ const size_t msg_len = std::min(static_cast(written), sizeof(message) - 1);
+ p->decoded.payload.size = msg_len;
+ if (msg_len > 0) {
+ memcpy(p->decoded.payload.bytes, message, msg_len);
+ }
+ }
service->sendToMesh(p, RX_SRC_LOCAL, true);
- delete[] message;
}
// Helper: return imprecise (truncated + centered) lat/lon as int32 using current precision
@@ -580,4 +589,4 @@ void PositionModule::handleNewPosition()
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp
index 6df0e18f0..6c2efe83f 100644
--- a/src/modules/StoreForwardModule.cpp
+++ b/src/modules/StoreForwardModule.cpp
@@ -206,7 +206,7 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit;
this->packetHistory[this->packetHistoryTotalCount].via_mqtt = mp.via_mqtt;
this->packetHistory[this->packetHistoryTotalCount].transport_mechanism = mp.transport_mechanism;
- memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
+ memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, p.payload.size);
this->packetHistoryTotalCount++;
}
diff --git a/src/motion/AccelerometerThread.h b/src/motion/AccelerometerThread.h
old mode 100644
new mode 100755
diff --git a/src/motion/BMA423Sensor.cpp b/src/motion/BMA423Sensor.cpp
old mode 100644
new mode 100755
diff --git a/src/motion/BMA423Sensor.h b/src/motion/BMA423Sensor.h
old mode 100644
new mode 100755
diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp
old mode 100644
new mode 100755
diff --git a/src/motion/BMX160Sensor.h b/src/motion/BMX160Sensor.h
old mode 100644
new mode 100755
diff --git a/src/motion/ICM20948Sensor.cpp b/src/motion/ICM20948Sensor.cpp
old mode 100644
new mode 100755
diff --git a/src/motion/ICM20948Sensor.h b/src/motion/ICM20948Sensor.h
old mode 100644
new mode 100755
diff --git a/src/motion/LIS3DHSensor.cpp b/src/motion/LIS3DHSensor.cpp
old mode 100644
new mode 100755
diff --git a/src/motion/LIS3DHSensor.h b/src/motion/LIS3DHSensor.h
old mode 100644
new mode 100755
diff --git a/src/motion/LSM6DS3Sensor.cpp b/src/motion/LSM6DS3Sensor.cpp
old mode 100644
new mode 100755
diff --git a/src/motion/LSM6DS3Sensor.h b/src/motion/LSM6DS3Sensor.h
old mode 100644
new mode 100755
diff --git a/src/motion/MPU6050Sensor.cpp b/src/motion/MPU6050Sensor.cpp
old mode 100644
new mode 100755
diff --git a/src/motion/MPU6050Sensor.h b/src/motion/MPU6050Sensor.h
old mode 100644
new mode 100755
diff --git a/src/motion/MotionSensor.cpp b/src/motion/MotionSensor.cpp
old mode 100644
new mode 100755
diff --git a/src/motion/MotionSensor.h b/src/motion/MotionSensor.h
old mode 100644
new mode 100755
diff --git a/src/motion/STK8XXXSensor.cpp b/src/motion/STK8XXXSensor.cpp
old mode 100644
new mode 100755
diff --git a/src/motion/STK8XXXSensor.h b/src/motion/STK8XXXSensor.h
old mode 100644
new mode 100755
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 3bb4ce817..5a4d150ae 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -323,7 +323,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
/**
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
*/
- virtual void onNowHasData(uint32_t fromRadioNum)
+ virtual void onNowHasData(uint32_t fromRadioNum) override
{
PhoneAPI::onNowHasData(fromRadioNum);
@@ -350,7 +350,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
}
/// Check the current underlying physical link to see if the client is currently connected
- virtual bool checkIsConnected() { return bleServer && bleServer->getConnectedCount() > 0; }
+ virtual bool checkIsConnected() override { return bleServer && bleServer->getConnectedCount() > 0; }
void requestHighThroughputConnection(uint16_t conn_handle)
{
@@ -412,9 +412,9 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
{
#ifdef NIMBLE_TWO
- virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
+ virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override
#else
- virtual void onWrite(NimBLECharacteristic *pCharacteristic)
+ virtual void onWrite(NimBLECharacteristic *pCharacteristic) override
#endif
{
@@ -464,9 +464,9 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
{
#ifdef NIMBLE_TWO
- virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
+ virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override
#else
- virtual void onRead(NimBLECharacteristic *pCharacteristic)
+ virtual void onRead(NimBLECharacteristic *pCharacteristic) override
#endif
{
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
@@ -582,9 +582,9 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
private:
NimbleBluetooth *ble;
- virtual uint32_t onPassKeyDisplay()
+ virtual uint32_t onPassKeyDisplay() override
#else
- virtual uint32_t onPassKeyRequest()
+ virtual uint32_t onPassKeyRequest() override
#endif
{
uint32_t passkey = config.bluetooth.fixed_pin;
@@ -635,9 +635,9 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
}
#ifdef NIMBLE_TWO
- virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo)
+ virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo) override
#else
- virtual void onAuthenticationComplete(ble_gap_conn_desc *desc)
+ virtual void onAuthenticationComplete(ble_gap_conn_desc *desc) override
#endif
{
LOG_INFO("BLE authentication complete");
@@ -655,7 +655,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
}
#ifdef NIMBLE_TWO
- virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo)
+ virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override
{
LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
@@ -683,11 +683,11 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
#endif
#ifdef NIMBLE_TWO
- virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
+ virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override
{
LOG_INFO("BLE disconnect reason: %d", reason);
#else
- virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc)
+ virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc) override
{
LOG_INFO("BLE disconnect");
#endif
@@ -989,11 +989,4 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
#endif
}
-void clearNVS()
-{
- NimBLEDevice::deleteAllBonds();
-#ifdef ARCH_ESP32
- ESP.restart();
-#endif
-}
#endif
diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h
index 458fa4a67..a7b14ff73 100644
--- a/src/nimble/NimbleBluetooth.h
+++ b/src/nimble/NimbleBluetooth.h
@@ -24,5 +24,4 @@ class NimbleBluetooth : BluetoothApi
#endif
};
-void setBluetoothEnable(bool enable);
-void clearNVS();
\ No newline at end of file
+void setBluetoothEnable(bool enable);
\ No newline at end of file
diff --git a/variants/esp32s3/m5stack_cardputer_adv/variant.cpp b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp
similarity index 97%
rename from variants/esp32s3/m5stack_cardputer_adv/variant.cpp
rename to src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp
index 2bbe8e2e3..7ec9dca80 100644
--- a/variants/esp32s3/m5stack_cardputer_adv/variant.cpp
+++ b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp
@@ -1,6 +1,9 @@
-#include "AudioBoard.h"
#include "configuration.h"
+#ifdef M5STACK_CARDPUTER_ADV
+
+#include "AudioBoard.h"
+
DriverPins PinsAudioBoardES8311;
AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311);
@@ -38,3 +41,5 @@ void lateInitVariant()
es8311_write_reg(0x32, 0xBF); // DAC volume (0dB)
es8311_write_reg(0x37, 0x08); // EQ bypass
}
+
+#endif
diff --git a/src/platform/extra_variants/t5s3_epaper/variant.cpp b/src/platform/extra_variants/t5s3_epaper/variant.cpp
new file mode 100644
index 000000000..a829bb980
--- /dev/null
+++ b/src/platform/extra_variants/t5s3_epaper/variant.cpp
@@ -0,0 +1,709 @@
+#include "configuration.h"
+
+#ifdef T5_S3_EPAPER_PRO
+
+#include "Observer.h"
+#include "TouchDrvGT911.hpp"
+#include "Wire.h"
+#include "buzz.h"
+#include "concurrency/OSThread.h"
+#include "input/InputBroker.h"
+#include "input/TouchScreenImpl1.h"
+#include "main.h"
+#include "sleep.h"
+#include
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+#include "graphics/niche/InkHUD/InkHUD.h"
+#include "graphics/niche/InkHUD/Persistence.h"
+#include "graphics/niche/InkHUD/SystemApplet.h"
+
+// Bridges touch events from TouchScreenImpl1 directly into InkHUD,
+// bypassing the InputBroker (which is excluded in InkHUD builds).
+// Routing mirrors the mini-epaper-s3 two-way rocker pattern:
+// - Nav left/right: prevApplet/nextApplet when idle, navUp/Down when a system applet has focus (e.g. menu)
+// - Nav up/down: navUp/navDown always (menu scroll)
+// - Tap/long-press: direct touch point dispatch (with fallback to short/long button semantics)
+class TouchInkHUDBridge : public Observer
+{
+ int onNotify(const InputEvent *e) override
+ {
+ auto *inkhud = NicheGraphics::InkHUD::InkHUD::getInstance();
+
+ // Keep alignment in sync with the current rotation so that visual-frame gestures
+ // always pass through nav functions without remapping: (rotation + alignment) % 4 == 0.
+ inkhud->persistence->settings.joystick.alignment = (4 - inkhud->persistence->settings.rotation) % 4;
+
+ // Check whether a system applet (e.g. menu) is currently handling input
+ bool systemHandlingInput = false;
+ for (NicheGraphics::InkHUD::SystemApplet *sa : inkhud->systemApplets) {
+ if (sa->handleInput) {
+ systemHandlingInput = true;
+ break;
+ }
+ }
+
+ switch (e->inputEvent) {
+ case INPUT_BROKER_USER_PRESS:
+ inkhud->touchTap(e->touchX, e->touchY);
+ break;
+ case INPUT_BROKER_SELECT:
+ inkhud->touchLongPress(e->touchX, e->touchY);
+ break;
+ case INPUT_BROKER_LEFT:
+ if (systemHandlingInput)
+ inkhud->touchNavUp();
+ else
+ inkhud->prevApplet();
+ break;
+ case INPUT_BROKER_RIGHT:
+ if (systemHandlingInput)
+ inkhud->touchNavDown();
+ else
+ inkhud->nextApplet();
+ break;
+ case INPUT_BROKER_UP:
+ inkhud->touchNavUp();
+ break;
+ case INPUT_BROKER_DOWN:
+ inkhud->touchNavDown();
+ break;
+ default:
+ break;
+ }
+ return 0;
+ }
+};
+
+static TouchInkHUDBridge touchBridge;
+#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+TouchDrvGT911 touch;
+
+namespace
+{
+constexpr uint8_t BACKLIGHT_ON_LEVEL = HIGH;
+constexpr uint8_t BACKLIGHT_OFF_LEVEL = LOW;
+volatile bool backlightUserEnabled = true;
+volatile bool backlightForcedByTimeout = false;
+volatile bool backlightForcedBySleep = false;
+
+void applyBacklightState()
+{
+ const bool shouldOn = backlightUserEnabled && !backlightForcedByTimeout && !backlightForcedBySleep;
+ digitalWrite(BOARD_BL_EN, shouldOn ? BACKLIGHT_ON_LEVEL : BACKLIGHT_OFF_LEVEL);
+}
+
+volatile bool touchInputEnabled = true;
+volatile bool touchForcedByTimeout = false;
+volatile bool touchControllerReady = false;
+volatile bool touchLightSleepActive = false;
+volatile bool touchNeedsWake = false;
+volatile bool touchIndicatorRefreshPending = false;
+volatile uint32_t touchResumeBlockUntilMs = 0;
+volatile uint32_t touchStateEpoch = 1;
+volatile bool homeCapButtonEventsEnabled = false;
+#if HAS_SCREEN
+uint32_t lastTouchIndicatorMs = 0;
+#endif
+
+void showTouchIndicator(const char *text)
+{
+#if HAS_SCREEN
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ // InkHUD builds render a dedicated bottom-edge "TOUCH OFF" overlay instead of popup banners.
+ (void)text;
+ return;
+#else
+ // Keep repeated notifications low profile and non-spammy.
+ if ((millis() - lastTouchIndicatorMs) < 500) {
+ return;
+ }
+ lastTouchIndicatorMs = millis();
+ if (screen) {
+ screen->showSimpleBanner(text, 1400);
+ }
+#endif
+#else
+ (void)text;
+#endif
+}
+
+#if defined(BOARD_PCA9535_ADDR) && defined(BOARD_PCA9535_BUTTON_MASK)
+bool readPca9535Port1(uint8_t *value)
+{
+ if (!value) {
+ return false;
+ }
+
+ Wire.beginTransmission(BOARD_PCA9535_ADDR);
+ Wire.write((uint8_t)0x01); // input port 1
+ if (Wire.endTransmission(false) != 0) {
+ return false;
+ }
+ if (Wire.requestFrom((uint8_t)BOARD_PCA9535_ADDR, (uint8_t)1) != 1) {
+ return false;
+ }
+
+ *value = Wire.read();
+ return true;
+}
+
+bool isPca9535SideKeyPressed()
+{
+ uint8_t port1 = 0xFF;
+ if (!readPca9535Port1(&port1)) {
+ return false;
+ }
+
+ return (port1 & BOARD_PCA9535_BUTTON_MASK) == 0;
+}
+
+class SideKeyInterruptThread : public concurrency::OSThread
+{
+ public:
+ SideKeyInterruptThread() : concurrency::OSThread("t5s3SideKeyInt", SAMPLE_MS)
+ {
+ // Do not run unless an edge arrives.
+ OSThread::disable();
+ instance = this;
+#ifdef ARCH_ESP32
+ lsObserver.observe(¬ifyLightSleep);
+ lsEndObserver.observe(¬ifyLightSleepEnd);
+#endif
+ }
+
+ void begin()
+ {
+ pinMode(BOARD_PCA9535_INT, INPUT_PULLUP);
+ attachInterrupt(BOARD_PCA9535_INT, SideKeyInterruptThread::isr, FALLING);
+ }
+
+ protected:
+ int32_t runOnce() override
+ {
+ const uint32_t now = millis();
+
+ if (now < touchResumeBlockUntilMs) {
+ resetStateAndStop();
+ return OSThread::disable();
+ }
+
+ if (touchLightSleepActive) {
+ resetStateAndStop();
+ return OSThread::disable();
+ }
+
+ // Ignore side-key handling while BOOT/user button is held.
+ if (digitalRead(BUTTON_PIN) == LOW) {
+ resetStateAndStop();
+ return OSThread::disable();
+ }
+
+ switch (state) {
+ case State::IRQ_PENDING:
+ // Initial debounce after expander interrupt edge.
+ if ((uint32_t)(now - irqAtMs) < DEBOUNCE_MS) {
+ return SAMPLE_MS;
+ }
+
+ if (isPca9535SideKeyPressed()) {
+ state = State::PRESSED;
+ pressStartMs = now;
+ return SAMPLE_MS;
+ }
+
+ // Spurious/cleared edge.
+ resetStateAndStop();
+ return OSThread::disable();
+
+ case State::PRESSED: {
+ if (isPca9535SideKeyPressed()) {
+ // Fire long-press action as soon as threshold is reached, without waiting for release.
+ if (!longPressFired && (uint32_t)(now - pressStartMs) >= LONG_PRESS_MIN_MS &&
+ (uint32_t)(now - lastActionMs) >= ACTION_COOLDOWN_MS) {
+ t5BacklightToggleUser();
+ longPressFired = true;
+ lastActionMs = now;
+ }
+ return SAMPLE_MS;
+ }
+
+ // Released: if long-press already fired, do nothing. Otherwise classify short press.
+ const uint32_t heldMs = now - pressStartMs;
+ if (!longPressFired && heldMs >= SHORT_PRESS_MIN_MS && (uint32_t)(now - lastActionMs) >= ACTION_COOLDOWN_MS) {
+ // If timeout forced touch/backlight off, short-press acts as a wake action first.
+ if (t5TouchIsForcedByTimeout()) {
+ t5TouchHandleUserInput();
+ t5BacklightHandleUserInput();
+ } else {
+ toggleTouchInputEnabled();
+ }
+ lastActionMs = now;
+ }
+
+ resetStateAndStop();
+ return OSThread::disable();
+ }
+
+ case State::REST:
+ default:
+ return OSThread::disable();
+ }
+ }
+
+ private:
+ enum class State : uint8_t {
+ REST,
+ IRQ_PENDING,
+ PRESSED,
+ };
+
+ static constexpr uint32_t SAMPLE_MS = 15;
+ static constexpr uint32_t DEBOUNCE_MS = 25;
+ static constexpr uint32_t SHORT_PRESS_MIN_MS = 30;
+ static constexpr uint32_t LONG_PRESS_MIN_MS = 450;
+ static constexpr uint32_t ACTION_COOLDOWN_MS = 180;
+
+ static SideKeyInterruptThread *instance;
+
+ static void isr()
+ {
+ if (instance) {
+ instance->onInterruptEdge();
+ }
+ }
+
+ void onInterruptEdge()
+ {
+ if (touchLightSleepActive) {
+ return;
+ }
+ const uint32_t now = millis();
+ if (now < touchResumeBlockUntilMs) {
+ return;
+ }
+ if (state != State::REST) {
+ return;
+ }
+
+ state = State::IRQ_PENDING;
+ irqAtMs = millis();
+ startThread();
+ }
+
+ void startThread()
+ {
+ if (!OSThread::enabled) {
+ OSThread::setIntervalFromNow(0);
+ OSThread::enabled = true;
+ runASAP = true;
+ }
+ }
+
+ void resetStateAndStop()
+ {
+ state = State::REST;
+ longPressFired = false;
+ if (OSThread::enabled) {
+ OSThread::disable();
+ }
+ }
+
+#ifdef ARCH_ESP32
+ int onLightSleep(void *)
+ {
+ detachInterrupt(BOARD_PCA9535_INT);
+ // Clear any latched PCA9535 interrupt before enabling GPIO wake.
+ // If INT is left asserted low, light sleep exits immediately.
+ uint8_t ignored = 0xFF;
+ (void)readPca9535Port1(&ignored);
+ resetStateAndStop();
+ return 0;
+ }
+
+ int onLightSleepEnd(esp_sleep_wakeup_cause_t cause)
+ {
+ (void)cause;
+ // Consume any pending interrupt source before reattaching ISR.
+ uint8_t ignored = 0xFF;
+ (void)readPca9535Port1(&ignored);
+ pinMode(BOARD_PCA9535_INT, INPUT_PULLUP);
+ attachInterrupt(BOARD_PCA9535_INT, SideKeyInterruptThread::isr, FALLING);
+
+ return 0;
+ }
+
+ CallbackObserver lsObserver{this, &SideKeyInterruptThread::onLightSleep};
+ CallbackObserver lsEndObserver{this,
+ &SideKeyInterruptThread::onLightSleepEnd};
+#endif
+
+ volatile State state = State::REST;
+ volatile uint32_t irqAtMs = 0;
+ uint32_t pressStartMs = 0;
+ bool longPressFired = false;
+ uint32_t lastActionMs = 0;
+};
+
+SideKeyInterruptThread *SideKeyInterruptThread::instance = nullptr;
+SideKeyInterruptThread *sideKeyThread = nullptr;
+#endif
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+void refreshTouchIndicatorInInkHUD(bool async = true)
+{
+ auto *inkhud = NicheGraphics::InkHUD::InkHUD::getInstance();
+ NicheGraphics::InkHUD::SystemApplet *touchStatus = nullptr;
+ for (auto *sa : inkhud->systemApplets) {
+ if (sa && sa->name && strcmp(sa->name, "TouchStatus") == 0) {
+ touchStatus = sa;
+ break;
+ }
+ }
+
+ if (touchStatus) {
+ if (inkhud->isTouchEnabled())
+ touchStatus->sendToBackground();
+ else
+ touchStatus->bringToForeground();
+ }
+
+ // Re-render all applets so touch-status visibility changes are immediately reflected.
+ inkhud->forceUpdate(NicheGraphics::Drivers::EInk::UpdateTypes::FAST, true, async);
+}
+#endif
+
+} // namespace
+
+void t5BacklightSetUserEnabled(bool enabled)
+{
+ backlightUserEnabled = enabled;
+ if (enabled) {
+ // Manual ON should release auto-off gates.
+ backlightForcedByTimeout = false;
+ backlightForcedBySleep = false;
+ }
+ applyBacklightState();
+}
+
+bool t5BacklightIsUserEnabled()
+{
+ return backlightUserEnabled;
+}
+
+void t5BacklightToggleUser()
+{
+ t5BacklightSetUserEnabled(!backlightUserEnabled);
+}
+
+void t5BacklightSetForcedByTimeout(bool forced)
+{
+ backlightForcedByTimeout = forced;
+ applyBacklightState();
+}
+
+void t5BacklightSetForcedBySleep(bool forced)
+{
+ backlightForcedBySleep = forced;
+ applyBacklightState();
+}
+
+void t5BacklightHandleUserInput()
+{
+ // Screen-timeout should be lifted by direct user interaction.
+ backlightForcedByTimeout = false;
+ applyBacklightState();
+}
+
+void t5TouchSetForcedByTimeout(bool forced)
+{
+ touchForcedByTimeout = forced;
+ touchStateEpoch++;
+ touchIndicatorRefreshPending = true;
+
+ if (forced) {
+ // While timeout-forced, keep controller asleep to avoid stale IRQ chatter.
+ touchNeedsWake = false;
+ if (touchControllerReady && !touchLightSleepActive) {
+ touch.sleep();
+ }
+ } else if (touchInputEnabled && touchControllerReady && !touchLightSleepActive) {
+ // Defer wake until readTouch() so I2C settles post-state transition.
+ touchNeedsWake = true;
+ }
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ if (!touchLightSleepActive) {
+ refreshTouchIndicatorInInkHUD();
+ touchIndicatorRefreshPending = false;
+ }
+#endif
+}
+
+bool t5TouchIsForcedByTimeout()
+{
+ return touchForcedByTimeout;
+}
+
+void t5TouchHandleUserInput()
+{
+ t5TouchSetForcedByTimeout(false);
+}
+
+void t5SetHomeCapButtonEventsEnabled(bool enabled)
+{
+ homeCapButtonEventsEnabled = enabled;
+}
+
+bool isTouchInputEnabled()
+{
+ return touchInputEnabled && !touchForcedByTimeout && !touchLightSleepActive;
+}
+
+void setTouchInputEnabled(bool enabled, bool showIndicator)
+{
+ if (touchInputEnabled == enabled) {
+ LOG_DEBUG("touchscreen1: setTouchInputEnabled no-op en=%d", enabled);
+ return;
+ }
+
+ LOG_DEBUG("touchscreen1: setTouchInputEnabled %d -> %d (showIndicator=%d)", touchInputEnabled, enabled, showIndicator);
+ touchInputEnabled = enabled;
+ touchStateEpoch++;
+
+ if (enabled) {
+ touchNeedsWake = touchControllerReady;
+ if (touchControllerReady && !touchLightSleepActive) {
+ LOG_DEBUG("touchscreen1: wakeup() on enable");
+ touch.wakeup();
+ touchNeedsWake = false;
+ }
+ } else {
+ touchNeedsWake = false;
+ if (touchControllerReady && !touchLightSleepActive) {
+ LOG_DEBUG("touchscreen1: sleep() on disable");
+ touch.sleep();
+ }
+ if (showIndicator) {
+ showTouchIndicator("Touch OFF");
+ touchIndicatorRefreshPending = true;
+ }
+ }
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ if (showIndicator && !touchLightSleepActive) {
+ refreshTouchIndicatorInInkHUD();
+ touchIndicatorRefreshPending = false;
+ }
+#endif
+}
+
+void toggleTouchInputEnabled()
+{
+ setTouchInputEnabled(!touchInputEnabled, true);
+}
+
+// Commands the GT911 into standby before the Wire bus is torn down.
+// notifyDeepSleep fires before Wire.end() in doDeepSleep(), so I2C is still available here.
+struct TouchDeepSleepObserver {
+ int onDeepSleep(void *)
+ {
+ touch.sleep();
+ return 0;
+ }
+ CallbackObserver observer{this, &TouchDeepSleepObserver::onDeepSleep};
+} static touchDeepSleepObserver;
+
+#ifdef ARCH_ESP32
+struct TouchLightSleepObserver {
+ int onLightSleep(void *)
+ {
+ touchLightSleepActive = true;
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ // Render touch-off overlay before sleeping so user sees touch is unavailable.
+ touchIndicatorRefreshPending = true;
+ refreshTouchIndicatorInInkHUD(false);
+ touchIndicatorRefreshPending = false;
+#endif
+ return 0;
+ }
+
+ CallbackObserver observer{this, &TouchLightSleepObserver::onLightSleep};
+} static touchLightSleepObserver;
+
+struct TouchLightSleepEndObserver {
+ int onLightSleepEnd(esp_sleep_wakeup_cause_t cause)
+ {
+ (void)cause;
+ touchLightSleepActive = false;
+
+ if (!touchControllerReady) {
+ return 0;
+ }
+
+ if (touchInputEnabled && !touchForcedByTimeout) {
+ touchNeedsWake = true;
+ } else {
+ touchNeedsWake = false;
+ }
+
+ touchStateEpoch++;
+ touchResumeBlockUntilMs = millis() + 150;
+ touchIndicatorRefreshPending = !isTouchInputEnabled();
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ // Clear sleep-time touch overlay after wake.
+ touchIndicatorRefreshPending = true;
+ refreshTouchIndicatorInInkHUD();
+ touchIndicatorRefreshPending = false;
+#endif
+ return 0;
+ }
+
+ CallbackObserver observer{this,
+ &TouchLightSleepEndObserver::onLightSleepEnd};
+} static touchLightSleepEndObserver;
+#endif
+
+bool readTouch(int16_t *x, int16_t *y)
+{
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ static uint32_t suppressUntilMs = 0;
+ static uint32_t seenTouchStateEpoch = 0;
+
+ // Reset transient gesture helpers whenever touch mode changes.
+ if (seenTouchStateEpoch != touchStateEpoch) {
+ seenTouchStateEpoch = touchStateEpoch;
+ suppressUntilMs = 0;
+ }
+
+ // Let buses and peripherals settle briefly after light-sleep wake.
+ if (millis() < touchResumeBlockUntilMs) {
+ return false;
+ }
+
+ if (touchIndicatorRefreshPending) {
+ refreshTouchIndicatorInInkHUD();
+ touchIndicatorRefreshPending = false;
+ }
+
+ if (!isTouchInputEnabled()) {
+ return false;
+ }
+
+ if (touchNeedsWake && touchControllerReady) {
+ LOG_DEBUG("touchscreen1: wakeup() on deferred resume");
+ touch.wakeup();
+ touchNeedsWake = false;
+ suppressUntilMs = millis() + 60;
+ return false;
+ }
+
+ // After a recovery pulse, emit a brief "released" window so gesture state can reset.
+ if (suppressUntilMs != 0 && millis() < suppressUntilMs) {
+ return false;
+ }
+#endif
+
+ if (!digitalRead(GT911_PIN_INT)) {
+ int16_t raw_x;
+ int16_t raw_y;
+ if (touch.getPoint(&raw_x, &raw_y)) {
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ // Transform raw GT911 axes to visual-frame coordinates for the current display rotation.
+ // rotation=3 is the physical identity (device's default orientation).
+ switch (NicheGraphics::InkHUD::InkHUD::getInstance()->persistence->settings.rotation) {
+ default:
+ case 3:
+ *x = raw_x;
+ *y = raw_y;
+ break; // identity
+ case 2:
+ *x = (EPD_WIDTH - 1) - raw_y;
+ *y = raw_x;
+ break; // 90° CW tilt
+ case 1:
+ *x = (EPD_HEIGHT - 1) - raw_x;
+ *y = (EPD_WIDTH - 1) - raw_y;
+ break; // 180° flip
+ case 0:
+ *x = raw_y;
+ *y = (EPD_HEIGHT - 1) - raw_x;
+ break; // 90° CCW tilt
+ }
+#else
+ *x = raw_x;
+ *y = raw_y;
+#endif
+ LOG_DEBUG("touched(%d/%d)", *x, *y);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void variant_shutdown()
+{
+ // Ensure backlight is off during deep sleep.
+ t5BacklightSetForcedBySleep(true);
+}
+
+void lateInitVariant()
+{
+ touch.setPins(GT911_PIN_RST, GT911_PIN_INT);
+ if (touch.begin(Wire, GT911_SLAVE_ADDRESS_H, GT911_PIN_SDA, GT911_PIN_SCL)) {
+ // Match LilyGO sample behavior: GT911 center/home capacitive key callback.
+ touch.setHomeButtonCallback(
+ [](void *user_data) {
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ if (!homeCapButtonEventsEnabled) {
+ return;
+ }
+
+ static uint32_t lastHomeMs = 0;
+ const uint32_t now = millis();
+ if ((uint32_t)(now - lastHomeMs) < 220) {
+ return; // debounce repeated key reports while still touched
+ }
+ lastHomeMs = now;
+
+ auto *inkhud = NicheGraphics::InkHUD::InkHUD::getInstance();
+ if (inkhud) {
+ // Route through InkHUD EXIT/HOME path (menu close, etc).
+ inkhud->exitShort();
+ }
+#else
+ (void)user_data;
+#endif
+ },
+ nullptr);
+ touchControllerReady = true;
+ touchInputEnabled = true;
+ touchForcedByTimeout = false;
+ touchLightSleepActive = false;
+ touchStateEpoch++;
+ touchDeepSleepObserver.observer.observe(¬ifyDeepSleep);
+#ifdef ARCH_ESP32
+ touchLightSleepObserver.observer.observe(¬ifyLightSleep);
+ touchLightSleepEndObserver.observer.observe(¬ifyLightSleepEnd);
+#endif
+ touchScreenImpl1 = new TouchScreenImpl1(EPD_WIDTH, EPD_HEIGHT, readTouch);
+ touchScreenImpl1->init();
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+ touchBridge.observe(touchScreenImpl1);
+#endif
+ } else {
+ touchControllerReady = false;
+ LOG_ERROR("Failed to find touch controller!");
+ }
+
+#if defined(BOARD_PCA9535_ADDR) && defined(BOARD_PCA9535_BUTTON_MASK)
+ // Start side-key interrupt handling after touch init is complete.
+ if (!sideKeyThread) {
+ sideKeyThread = new SideKeyInterruptThread();
+ sideKeyThread->begin();
+ }
+#endif
+}
+#endif
diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp
index 307e35b0c..52e45cccc 100644
--- a/src/platform/nrf52/NRF52Bluetooth.cpp
+++ b/src/platform/nrf52/NRF52Bluetooth.cpp
@@ -1,6 +1,7 @@
#include "NRF52Bluetooth.h"
#include "BLEDfuSecure.h"
#include "BluetoothCommon.h"
+#include "HardwareRNG.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "main.h"
@@ -272,9 +273,13 @@ void NRF52Bluetooth::setup()
Bluefruit.setTxPower(NRF52_BLE_TX_POWER);
#endif
if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
- configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN
- ? config.bluetooth.fixed_pin
- : random(100000, 999999);
+ if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN) {
+ configuredPasskey = config.bluetooth.fixed_pin;
+ } else {
+ uint32_t hwrand = 0;
+ HardwareRNG::fill(reinterpret_cast(&hwrand), sizeof(hwrand));
+ configuredPasskey = hwrand % 900000u + 100000u;
+ }
auto pinString = std::to_string(configuredPasskey);
LOG_INFO("Bluetooth pin set to '%i'", configuredPasskey);
Bluefruit.Security.setPIN(pinString.c_str());
diff --git a/src/power.h b/src/power.h
index d46eaadd2..4b5ef609d 100644
--- a/src/power.h
+++ b/src/power.h
@@ -86,7 +86,7 @@ extern RAK9154Sensor rak9154Sensor;
extern XPowersLibInterface *PMU;
#endif
-class Power : private concurrency::OSThread
+class Power : public concurrency::OSThread
{
public:
@@ -100,6 +100,15 @@ class Power : private concurrency::OSThread
virtual int32_t runOnce() override;
void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; }
const uint16_t OCV[11] = {OCV_ARRAY};
+ bool isLowBattery() { return low_voltage_counter >= 10; };
+
+#ifdef ARCH_ESP32
+ int beforeLightSleep(void *unused);
+ int afterLightSleep(esp_sleep_wakeup_cause_t cause);
+#endif
+
+ void attachPowerInterrupts();
+ void detachPowerInterrupts();
protected:
meshtastic::PowerStatus *statusHandler;
@@ -125,6 +134,14 @@ class Power : private concurrency::OSThread
// open circuit voltage lookup table
uint8_t low_voltage_counter;
uint32_t lastLogTime = 0;
+
+#ifdef ARCH_ESP32
+ // Get notified when lightsleep begins and ends
+ CallbackObserver lsObserver = CallbackObserver(this, &Power::beforeLightSleep);
+ CallbackObserver lsEndObserver =
+ CallbackObserver(this, &Power::afterLightSleep);
+#endif
+
#ifdef DEBUG_HEAP
uint32_t lastheap;
#endif
diff --git a/src/sleep.cpp b/src/sleep.cpp
index 2af6222f4..00533f5fa 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -79,7 +79,7 @@ RTC_DATA_ATTR int bootCount = 0;
*/
void setCPUFast(bool on)
{
-#if defined(ARCH_ESP32) && HAS_WIFI && !HAS_TFT
+#if defined(ARCH_ESP32) && HAS_WIFI && !HAS_TFT && !defined(T_LORA_PAGER) && !defined(T_DECK)
if (isWifiAvailable()) {
/*
@@ -316,9 +316,14 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
#ifdef HAS_PPM
if (PPM) {
- LOG_INFO("PMM shutdown");
- console->flush();
- PPM->shutdown();
+ // BQ25896 PMIC shutdown is a hard power-off state.
+ // Only use it for "sleep forever" / explicit shutdown, because timed deep sleep
+ // must remain wakeable by RTC timer.
+ if (msecToWake == portMAX_DELAY) {
+ LOG_INFO("PPM shutdown");
+ console->flush();
+ PPM->shutdown();
+ }
}
#endif
@@ -425,6 +430,10 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
#ifdef KB_INT
gpio_wakeup_enable((gpio_num_t)KB_INT, GPIO_INTR_LOW_LEVEL);
#endif
+#ifdef BOARD_PCA9535_INT
+ // Side-key interrupt line from PCA9535 expander (active low).
+ gpio_wakeup_enable((gpio_num_t)BOARD_PCA9535_INT, GPIO_INTR_LOW_LEVEL);
+#endif
#ifdef BUTTON_PIN
gpio_num_t pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL);
@@ -472,6 +481,9 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
#ifdef KB_INT
gpio_wakeup_disable((gpio_num_t)KB_INT);
#endif
+#ifdef BOARD_PCA9535_INT
+ gpio_wakeup_disable((gpio_num_t)BOARD_PCA9535_INT);
+#endif
#ifdef BUTTON_PIN
// Disable wake-on-button interrupt. Re-attach normal button-interrupts
gpio_wakeup_disable(pin);
@@ -497,13 +509,12 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here
-#ifdef BUTTON_PIN
if (cause == ESP_SLEEP_WAKEUP_GPIO) {
- LOG_INFO("Exit light sleep gpio: btn=%d",
- !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN));
- } else
-#endif
- {
+ LOG_INFO("Exit light sleep gpio");
+ // If we woke because of a GPIO, it's possible power needs to run to handle.
+ power->setIntervalFromNow(0);
+ runASAP = true;
+ } else {
LOG_INFO("Exit light sleep cause: %d", cause);
}
diff --git a/test/test_packet_history/test_main.cpp b/test/test_packet_history/test_main.cpp
new file mode 100644
index 000000000..2453956c5
--- /dev/null
+++ b/test/test_packet_history/test_main.cpp
@@ -0,0 +1,834 @@
+/*
+ * Unit tests for PacketHistory — the packet deduplication engine
+ * used by the mesh routing stack.
+ *
+ * PacketHistory maintains a fixed-size array of PacketRecords with an
+ * optional hash table for O(1) lookup. It tracks which nodes relayed
+ * each packet, supports LRU-style eviction, and detects fallback-to-
+ * flooding and hop-limit upgrades.
+ */
+
+#include "PacketHistory.h"
+
+#include "TestUtil.h"
+#include
+
+// ---------------------------------------------------------------------------
+// Constants
+// ---------------------------------------------------------------------------
+static constexpr uint32_t OUR_NODE_NUM = 0xDEAD1234;
+static constexpr uint8_t OUR_RELAY_ID = 0x34; // getLastByteOfNodeNum(OUR_NODE_NUM)
+static constexpr uint32_t SMALL_CAPACITY = 8;
+
+// ---------------------------------------------------------------------------
+// Per-test state
+// ---------------------------------------------------------------------------
+static PacketHistory *ph = nullptr;
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+static meshtastic_MeshPacket makePacket(uint32_t from, uint32_t id, uint8_t hop_limit = 3,
+ uint8_t next_hop = NO_NEXT_HOP_PREFERENCE, uint8_t relay_node = 0)
+{
+ meshtastic_MeshPacket p = meshtastic_MeshPacket_init_zero;
+ p.from = from;
+ p.id = id;
+ p.hop_limit = hop_limit;
+ p.next_hop = next_hop;
+ p.relay_node = relay_node;
+ return p;
+}
+
+// ---------------------------------------------------------------------------
+// setUp / tearDown — called before and after every test
+// ---------------------------------------------------------------------------
+void setUp(void)
+{
+ myNodeInfo.my_node_num = OUR_NODE_NUM;
+ ph = new PacketHistory(SMALL_CAPACITY);
+}
+
+void tearDown(void)
+{
+ delete ph;
+ ph = nullptr;
+}
+
+// ===========================================================================
+// Group 1 — Initialization
+// ===========================================================================
+
+void test_init_valid_size(void)
+{
+ PacketHistory h(8);
+ TEST_ASSERT_TRUE(h.initOk());
+}
+
+void test_init_minimum_size(void)
+{
+ PacketHistory h(4);
+ TEST_ASSERT_TRUE(h.initOk());
+}
+
+void test_init_too_small_falls_back(void)
+{
+ // Sizes < 4 or > PACKETHISTORY_MAX are clamped to PACKETHISTORY_MAX inside the constructor
+ PacketHistory h(2);
+ TEST_ASSERT_TRUE(h.initOk());
+}
+
+// ===========================================================================
+// Group 2 — Basic Deduplication
+// ===========================================================================
+
+void test_first_packet_not_seen(void)
+{
+ auto p = makePacket(0x1111, 100);
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p));
+}
+
+void test_same_packet_seen_twice(void)
+{
+ auto p = makePacket(0x1111, 100);
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p)); // first time
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p)); // duplicate
+}
+
+void test_different_id_not_confused(void)
+{
+ auto p1 = makePacket(0x1111, 100);
+ auto p2 = makePacket(0x1111, 200);
+ ph->wasSeenRecently(&p1);
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p2));
+}
+
+void test_different_sender_not_confused(void)
+{
+ auto p1 = makePacket(0x1111, 100);
+ auto p2 = makePacket(0x2222, 100);
+ ph->wasSeenRecently(&p1);
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p2));
+}
+
+void test_withUpdate_false_no_insert(void)
+{
+ auto p = makePacket(0x1111, 100);
+ // First call with withUpdate=false: should not store
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p, /*withUpdate=*/false));
+ // Second call with withUpdate=true: still not found because first didn't store
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p, /*withUpdate=*/true));
+}
+
+void test_withUpdate_true_inserts(void)
+{
+ auto p = makePacket(0x1111, 100);
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p, /*withUpdate=*/true));
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, /*withUpdate=*/false)); // found without inserting again
+}
+
+// ===========================================================================
+// Group 3 — LRU Eviction
+// ===========================================================================
+
+void test_fill_capacity_all_found(void)
+{
+ for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
+ auto p = makePacket(0xAAAA, i);
+ ph->wasSeenRecently(&p);
+ }
+ // All 8 should be found
+ for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
+ auto p = makePacket(0xAAAA, i);
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, false));
+ }
+}
+
+void test_eviction_oldest_replaced(void)
+{
+ // Fill all 8 slots
+ for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
+ auto p = makePacket(0xAAAA, i);
+ ph->wasSeenRecently(&p);
+ }
+
+ // Advance time so the eviction logic can distinguish "oldest" from "newest".
+ // insert() uses (now_millis - rxTimeMsec) > OldtrxTimeMsec with strict >, so
+ // entries with identical timestamps all have age 0 and none gets selected.
+ delay(1);
+
+ // Insert a 9th packet — should evict the oldest
+ auto p9 = makePacket(0xAAAA, 9);
+ ph->wasSeenRecently(&p9);
+
+ // The 9th should be found
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p9, false));
+
+ // At least one of the originals should have been evicted
+ int evicted = 0;
+ for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
+ auto p = makePacket(0xAAAA, i);
+ if (!ph->wasSeenRecently(&p, false))
+ evicted++;
+ }
+ TEST_ASSERT_TRUE(evicted > 0);
+}
+
+void test_matching_slot_reused(void)
+{
+ // Insert packet, then re-insert same (sender, id) — should reuse slot, not evict others
+ auto p1 = makePacket(0xAAAA, 1);
+ auto p2 = makePacket(0xBBBB, 2);
+ ph->wasSeenRecently(&p1);
+ ph->wasSeenRecently(&p2);
+
+ // Re-observe p1 (triggers merge path)
+ ph->wasSeenRecently(&p1);
+
+ // Both should still be present
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p1, false));
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p2, false));
+}
+
+void test_free_slot_preferred(void)
+{
+ // Insert 4 packets into capacity-8 history — next insert should use a free slot, not evict
+ for (uint32_t i = 1; i <= 4; i++) {
+ auto p = makePacket(0xAAAA, i);
+ ph->wasSeenRecently(&p);
+ }
+ auto p5 = makePacket(0xAAAA, 5);
+ ph->wasSeenRecently(&p5);
+
+ // All 5 should be present (no eviction needed)
+ for (uint32_t i = 1; i <= 5; i++) {
+ auto p = makePacket(0xAAAA, i);
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, false));
+ }
+}
+
+void test_evict_all_old_packets(void)
+{
+ // Fill with packets 1..8
+ for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
+ auto p = makePacket(0xAAAA, i);
+ ph->wasSeenRecently(&p);
+ }
+
+ // Advance time so the replacement batch can evict the originals
+ delay(1);
+
+ // Replace all with packets 101..108
+ for (uint32_t i = 101; i <= 100 + SMALL_CAPACITY; i++) {
+ auto p = makePacket(0xBBBB, i);
+ ph->wasSeenRecently(&p);
+ }
+ // None of the originals should be found
+ for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
+ auto p = makePacket(0xAAAA, i);
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p, false));
+ }
+ // All new ones should be found
+ for (uint32_t i = 101; i <= 100 + SMALL_CAPACITY; i++) {
+ auto p = makePacket(0xBBBB, i);
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, false));
+ }
+}
+
+// ===========================================================================
+// Group 4 — Relayer Tracking
+// ===========================================================================
+
+void test_wasRelayer_true(void)
+{
+ // Non-us relay_nodes only enter relayed_by[] through the "heard-back" merge path:
+ // we must have relayed first, then observe the packet return at hop_limit-1.
+ auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+
+ // Heard-back from 0xCC at hop_limit=2 (ourTxHopLimit-1) triggers the merge
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xCC);
+ ph->wasSeenRecently(&p2);
+
+ TEST_ASSERT_TRUE(ph->wasRelayer(0xCC, 100, 0x1111));
+}
+
+void test_wasRelayer_false(void)
+{
+ auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, 0xAA);
+ ph->wasSeenRecently(&p);
+ // 0xCC was never a relayer
+ TEST_ASSERT_FALSE(ph->wasRelayer(0xCC, 100, 0x1111));
+}
+
+void test_wasRelayer_zero_returns_false(void)
+{
+ auto p = makePacket(0x1111, 100);
+ ph->wasSeenRecently(&p);
+ TEST_ASSERT_FALSE(ph->wasRelayer(0, 100, 0x1111));
+}
+
+void test_wasRelayer_not_found(void)
+{
+ // Packet not in history at all
+ TEST_ASSERT_FALSE(ph->wasRelayer(0xAA, 999, 0x9999));
+}
+
+void test_wasRelayer_wasSole_true(void)
+{
+ // relay_node = ourRelayID → relayed_by[0] = ourRelayID
+ auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p);
+
+ bool wasSole = false;
+ bool result = ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111, &wasSole);
+ TEST_ASSERT_TRUE(result);
+ TEST_ASSERT_TRUE(wasSole);
+}
+
+void test_wasRelayer_wasSole_false(void)
+{
+ // First observation: we relay
+ auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+
+ // Second observation: different relayer adds to record
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
+ ph->wasSeenRecently(&p2);
+
+ bool wasSole = true;
+ bool result = ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111, &wasSole);
+ TEST_ASSERT_TRUE(result);
+ TEST_ASSERT_FALSE(wasSole);
+}
+
+void test_wasRelayer_all_six_slots(void)
+{
+ // First observation: we relay with hop_limit=3 (fills slot 0, ourTxHopLimit=3)
+ auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p);
+
+ // Each heard-back must satisfy: hop_limit == ourTxHopLimit OR ourTxHopLimit-1.
+ // Using hop_limit=2 (ourTxHopLimit-1) for all, which triggers the heard-back
+ // merge path each time. Each new relay_node pushes to slot 0 and shifts existing
+ // relayers right, eventually filling all NUM_RELAYERS(6) slots.
+ uint8_t relayers[] = {0x11, 0x22, 0x33, 0x44, 0x55};
+ for (int i = 0; i < 5; i++) {
+ auto pn = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, relayers[i]);
+ ph->wasSeenRecently(&pn);
+ }
+
+ // All 6 should be detected
+ TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
+ for (int i = 0; i < 5; i++) {
+ TEST_ASSERT_TRUE(ph->wasRelayer(relayers[i], 100, 0x1111));
+ }
+}
+
+// ===========================================================================
+// Group 5 — removeRelayer
+// ===========================================================================
+
+void test_removeRelayer_removes(void)
+{
+ auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+ TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
+
+ ph->removeRelayer(OUR_RELAY_ID, 100, 0x1111);
+ TEST_ASSERT_FALSE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
+}
+
+void test_removeRelayer_compacts(void)
+{
+ // We relay first
+ auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+ // Second relayer
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
+ ph->wasSeenRecently(&p2);
+
+ // Remove us, 0xBB should still be found
+ ph->removeRelayer(OUR_RELAY_ID, 100, 0x1111);
+ TEST_ASSERT_FALSE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
+ TEST_ASSERT_TRUE(ph->wasRelayer(0xBB, 100, 0x1111));
+}
+
+void test_removeRelayer_nonexistent_safe(void)
+{
+ auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p);
+ // Removing a relayer that doesn't exist should not crash
+ ph->removeRelayer(0xFF, 100, 0x1111);
+ // Original should still be there
+ TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
+}
+
+void test_removeRelayer_packet_not_found_safe(void)
+{
+ // Packet not in history — should not crash
+ ph->removeRelayer(0xAA, 999, 0x9999);
+}
+
+// ===========================================================================
+// Group 6 — checkRelayers
+// ===========================================================================
+
+void test_checkRelayers_both_found(void)
+{
+ // We relay first
+ auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+ // Second relayer
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
+ ph->wasSeenRecently(&p2);
+
+ bool r1 = false, r2 = false;
+ ph->checkRelayers(OUR_RELAY_ID, 0xBB, 100, 0x1111, &r1, &r2);
+ TEST_ASSERT_TRUE(r1);
+ TEST_ASSERT_TRUE(r2);
+}
+
+void test_checkRelayers_one_found(void)
+{
+ auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p);
+
+ bool r1 = false, r2 = false;
+ ph->checkRelayers(OUR_RELAY_ID, 0xCC, 100, 0x1111, &r1, &r2);
+ TEST_ASSERT_TRUE(r1);
+ TEST_ASSERT_FALSE(r2);
+}
+
+void test_checkRelayers_r2WasSole(void)
+{
+ auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p);
+
+ bool r1 = false, r2 = false, r2Sole = false;
+ // relayer1=0xCC (not found), relayer2=OUR_RELAY_ID (sole relayer)
+ ph->checkRelayers(0xCC, OUR_RELAY_ID, 100, 0x1111, &r1, &r2, &r2Sole);
+ TEST_ASSERT_FALSE(r1);
+ TEST_ASSERT_TRUE(r2);
+ TEST_ASSERT_TRUE(r2Sole);
+}
+
+// ===========================================================================
+// Group 7 — wasSeenRecently Merge Logic
+// ===========================================================================
+
+void test_merge_preserves_original_next_hop(void)
+{
+ // First observation with next_hop=0x55
+ auto p1 = makePacket(0x1111, 100, 3, 0x55, 0xAA);
+ ph->wasSeenRecently(&p1);
+
+ // Re-observation with different next_hop
+ auto p2 = makePacket(0x1111, 100, 2, 0x77, 0xBB);
+ ph->wasSeenRecently(&p2);
+
+ // The stored next_hop should still be 0x55 (the original)
+ // We verify via weWereNextHop: if we set original next_hop to ourRelayID, it should detect it
+ auto p3 = makePacket(0x1111, 200, 3, OUR_RELAY_ID, 0xAA);
+ ph->wasSeenRecently(&p3);
+ auto p4 = makePacket(0x1111, 200, 2, 0x99, 0xBB);
+ bool weWereNextHop = false;
+ ph->wasSeenRecently(&p4, true, nullptr, &weWereNextHop);
+ TEST_ASSERT_TRUE(weWereNextHop);
+}
+
+void test_merge_preserves_highest_hop_limit(void)
+{
+ // First observation with hop_limit=5
+ auto p1 = makePacket(0x1111, 100, 5);
+ ph->wasSeenRecently(&p1);
+
+ // Re-observation with hop_limit=2 (lower)
+ auto p2 = makePacket(0x1111, 100, 2);
+ ph->wasSeenRecently(&p2);
+
+ // Third observation with hop_limit=3 should not trigger upgrade (highest was 5)
+ bool wasUpgraded = true;
+ auto p3 = makePacket(0x1111, 100, 3);
+ ph->wasSeenRecently(&p3, true, nullptr, nullptr, &wasUpgraded);
+ TEST_ASSERT_FALSE(wasUpgraded);
+}
+
+void test_merge_no_duplicate_relayers(void)
+{
+ // Observe with relayer 0xAA (stored via relay_node, but only slot 0 for ourRelayID)
+ // We need to use ourRelayID for the first observation to get it into slot 0
+ auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+
+ // Re-observe with same relay_node=ourRelayID — should not create duplicates
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p2);
+
+ // ourRelayID should appear exactly once — wasSole should still be true
+ bool wasSole = false;
+ TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111, &wasSole));
+ TEST_ASSERT_TRUE(wasSole);
+}
+
+void test_merge_adds_new_relayer(void)
+{
+ auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
+ ph->wasSeenRecently(&p2);
+
+ TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
+ TEST_ASSERT_TRUE(ph->wasRelayer(0xBB, 100, 0x1111));
+}
+
+void test_merge_we_relay_sets_slot_zero(void)
+{
+ // When relay_node == ourRelayID, relayed_by[0] should be set to ourRelayID
+ auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p);
+
+ TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
+}
+
+void test_merge_heard_back_stores_relay_node(void)
+{
+ // First: we relay (hop_limit=3)
+ auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+
+ // Second: we hear the packet back with hop_limit=2 (one less), from relay_node=0xCC
+ // This triggers the "heard back" logic: weWereRelayer && hop_limit == ourTxHopLimit-1
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xCC);
+ ph->wasSeenRecently(&p2);
+
+ TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
+ TEST_ASSERT_TRUE(ph->wasRelayer(0xCC, 100, 0x1111));
+}
+
+// ===========================================================================
+// Group 8 — Fallback-to-Flooding Detection
+// ===========================================================================
+
+void test_fallback_detected(void)
+{
+ // The fallback condition requires wasRelayer(relay_node) && !wasRelayer(ourRelayID).
+ // Non-us relayers only enter relayed_by[] via the heard-back merge path, which
+ // also stores ourRelayID. So we must removeRelayer(ourRelayID) to satisfy both.
+ //
+ // Scenario: we relay a directed packet, hear it back from 0xAA, then the router
+ // removes us from the relayer list. Later the sender falls back to flooding.
+
+ // Step 1: We relay (directed to next_hop=0x55)
+ auto p1 = makePacket(0x1111, 100, 3, 0x55, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+
+ // Step 2: Heard-back from 0xAA at hop_limit-1 → stores 0xAA in relayed_by
+ auto p2 = makePacket(0x1111, 100, 2, 0x55, 0xAA);
+ ph->wasSeenRecently(&p2);
+
+ // Step 3: Router removes us from the relayer list
+ ph->removeRelayer(OUR_RELAY_ID, 100, 0x1111);
+
+ // Step 4: Sender falls back to flooding — same packet, NO_NEXT_HOP_PREFERENCE, from 0xAA
+ auto p3 = makePacket(0x1111, 100, 1, NO_NEXT_HOP_PREFERENCE, 0xAA);
+ bool wasFallback = false;
+ ph->wasSeenRecently(&p3, true, &wasFallback);
+ TEST_ASSERT_TRUE(wasFallback);
+}
+
+void test_fallback_not_when_we_relayed(void)
+{
+ // First observation: directed, we relayed it
+ auto p1 = makePacket(0x1111, 100, 3, 0x55, OUR_RELAY_ID);
+ ph->wasSeenRecently(&p1);
+
+ // Second observation: fallback to flooding from same relayer (us)
+ // But since we already relayed, wasFallback should be false
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
+ bool wasFallback = false;
+ ph->wasSeenRecently(&p2, true, &wasFallback);
+ TEST_ASSERT_FALSE(wasFallback);
+}
+
+void test_fallback_not_on_first_observation(void)
+{
+ // First time seen — can't be a fallback
+ auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, 0xAA);
+ bool wasFallback = false;
+ ph->wasSeenRecently(&p, true, &wasFallback);
+ TEST_ASSERT_FALSE(wasFallback);
+}
+
+// ===========================================================================
+// Group 9 — Next-Hop and Upgrade Detection
+// ===========================================================================
+
+void test_weWereNextHop_true(void)
+{
+ // Packet directed to us (next_hop = ourRelayID)
+ auto p1 = makePacket(0x1111, 100, 3, OUR_RELAY_ID, 0xAA);
+ ph->wasSeenRecently(&p1);
+
+ // Re-observe: check if we were the original next_hop
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
+ bool weWereNextHop = false;
+ ph->wasSeenRecently(&p2, true, nullptr, &weWereNextHop);
+ TEST_ASSERT_TRUE(weWereNextHop);
+}
+
+void test_weWereNextHop_false(void)
+{
+ // Packet directed to someone else
+ auto p1 = makePacket(0x1111, 100, 3, 0x99, 0xAA);
+ ph->wasSeenRecently(&p1);
+
+ auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
+ bool weWereNextHop = false;
+ ph->wasSeenRecently(&p2, true, nullptr, &weWereNextHop);
+ TEST_ASSERT_FALSE(weWereNextHop);
+}
+
+void test_wasUpgraded_true(void)
+{
+ // First observation with hop_limit=3 → stored as highestHopLimit bits 0-2 = 3
+ auto p1 = makePacket(0x1111, 100, 3);
+ ph->wasSeenRecently(&p1);
+
+ // Re-observation with hop_limit=5
+ // The upgrade check on line 122 compares the raw packed byte found->hop_limit against p->hop_limit.
+ // found->hop_limit has highestHopLimit=3 in bits 0-2 (and possibly ourTxHopLimit in bits 3-5).
+ // So the packed byte value is 3 (or more if ourTxHopLimit was set), and p->hop_limit is 5.
+ // Since 3 < 5 (with no ourTxHopLimit set), this should detect an upgrade.
+ auto p2 = makePacket(0x1111, 100, 5);
+ bool wasUpgraded = false;
+ ph->wasSeenRecently(&p2, true, nullptr, nullptr, &wasUpgraded);
+ TEST_ASSERT_TRUE(wasUpgraded);
+}
+
+void test_wasUpgraded_false(void)
+{
+ auto p1 = makePacket(0x1111, 100, 5);
+ ph->wasSeenRecently(&p1);
+
+ // Same or lower hop_limit
+ auto p2 = makePacket(0x1111, 100, 3);
+ bool wasUpgraded = false;
+ ph->wasSeenRecently(&p2, true, nullptr, nullptr, &wasUpgraded);
+ TEST_ASSERT_FALSE(wasUpgraded);
+}
+
+// ===========================================================================
+// Group 10 — Edge Cases
+// ===========================================================================
+
+void test_packet_id_zero_not_stored(void)
+{
+ auto p = makePacket(0x1111, 0);
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p));
+ TEST_ASSERT_FALSE(ph->wasSeenRecently(&p)); // still not found
+}
+
+void test_sender_zero_substituted(void)
+{
+ // from=0 means "from us" — getFrom() substitutes nodeDB->getNodeNum()
+ auto p = makePacket(0, 100);
+ ph->wasSeenRecently(&p);
+
+ // Should be stored under our node num, not 0
+ auto p2 = makePacket(OUR_NODE_NUM, 100);
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p2, false));
+}
+
+void test_uninitialized_wasSeenRecently(void)
+{
+ // Simulate uninitialized state — create a PacketHistory that looks uninitialized
+ // We can't easily make allocation fail, but we can test the initOk guard with a destructed one
+ PacketHistory h(4);
+ TEST_ASSERT_TRUE(h.initOk()); // sanity check
+ h.~PacketHistory();
+
+ auto p = makePacket(0x1111, 100);
+ TEST_ASSERT_FALSE(h.wasSeenRecently(&p));
+
+ // Reconstruct in place to allow proper destruction
+ new (&h) PacketHistory(4);
+}
+
+void test_uninitialized_wasRelayer(void)
+{
+ PacketHistory h(4);
+ h.~PacketHistory();
+
+ TEST_ASSERT_FALSE(h.wasRelayer(0xAA, 100, 0x1111));
+
+ new (&h) PacketHistory(4);
+}
+
+void test_multiple_instances_independent(void)
+{
+ PacketHistory h2(SMALL_CAPACITY);
+
+ auto p = makePacket(0x1111, 100);
+ ph->wasSeenRecently(&p);
+
+ // h2 should NOT find it
+ TEST_ASSERT_FALSE(h2.wasSeenRecently(&p, false));
+
+ // ph should still find it
+ TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, false));
+}
+
+// ===========================================================================
+// Group 11 — Hash Table Stress
+// ===========================================================================
+
+void test_many_packets_no_false_negatives(void)
+{
+ PacketHistory big(64);
+ for (uint32_t i = 1; i <= 64; i++) {
+ auto p = makePacket(0xAAAA, i);
+ big.wasSeenRecently(&p);
+ }
+ for (uint32_t i = 1; i <= 64; i++) {
+ auto p = makePacket(0xAAAA, i);
+ TEST_ASSERT_TRUE_MESSAGE(big.wasSeenRecently(&p, false), "False negative in hash table");
+ }
+}
+
+void test_many_packets_no_false_positives(void)
+{
+ PacketHistory big(64);
+ for (uint32_t i = 1; i <= 64; i++) {
+ auto p = makePacket(0xAAAA, i);
+ big.wasSeenRecently(&p);
+ }
+ // IDs 65..128 were never inserted
+ for (uint32_t i = 65; i <= 128; i++) {
+ auto p = makePacket(0xAAAA, i);
+ TEST_ASSERT_FALSE_MESSAGE(big.wasSeenRecently(&p, false), "False positive in hash table");
+ }
+}
+
+void test_churn_correctness(void)
+{
+ // Insert 3x capacity to force heavy eviction.
+ // Advance time between each generation so eviction can distinguish old from new.
+ PacketHistory big(32);
+ uint32_t capacity = 32;
+ uint32_t generations = 3;
+
+ for (uint32_t gen = 0; gen < generations; gen++) {
+ if (gen > 0)
+ delay(1); // Ensure new generation has a newer timestamp than the old
+ for (uint32_t i = 1; i <= capacity; i++) {
+ auto p = makePacket(0xAAAA, gen * capacity + i);
+ big.wasSeenRecently(&p);
+ }
+ }
+
+ uint32_t total = capacity * generations;
+
+ // Only the most recent 32 should be present (due to LRU eviction)
+ for (uint32_t i = total - 31; i <= total; i++) {
+ auto p = makePacket(0xAAAA, i);
+ TEST_ASSERT_TRUE_MESSAGE(big.wasSeenRecently(&p, false), "Recent packet lost after churn");
+ }
+ // Older packets should be gone
+ int found = 0;
+ for (uint32_t i = 1; i <= total - capacity; i++) {
+ auto p = makePacket(0xAAAA, i);
+ if (big.wasSeenRecently(&p, false))
+ found++;
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, found, "Evicted packets should not be found");
+}
+
+// ===========================================================================
+// Test runner
+// ===========================================================================
+
+void setup()
+{
+ delay(10);
+ delay(2000);
+
+ initializeTestEnvironment();
+ UNITY_BEGIN();
+
+ // Group 1 — Initialization
+ RUN_TEST(test_init_valid_size);
+ RUN_TEST(test_init_minimum_size);
+ RUN_TEST(test_init_too_small_falls_back);
+
+ // Group 2 — Basic Deduplication
+ RUN_TEST(test_first_packet_not_seen);
+ RUN_TEST(test_same_packet_seen_twice);
+ RUN_TEST(test_different_id_not_confused);
+ RUN_TEST(test_different_sender_not_confused);
+ RUN_TEST(test_withUpdate_false_no_insert);
+ RUN_TEST(test_withUpdate_true_inserts);
+
+ // Group 3 — LRU Eviction
+ RUN_TEST(test_fill_capacity_all_found);
+ RUN_TEST(test_eviction_oldest_replaced);
+ RUN_TEST(test_matching_slot_reused);
+ RUN_TEST(test_free_slot_preferred);
+ RUN_TEST(test_evict_all_old_packets);
+
+ // Group 4 — Relayer Tracking
+ RUN_TEST(test_wasRelayer_true);
+ RUN_TEST(test_wasRelayer_false);
+ RUN_TEST(test_wasRelayer_zero_returns_false);
+ RUN_TEST(test_wasRelayer_not_found);
+ RUN_TEST(test_wasRelayer_wasSole_true);
+ RUN_TEST(test_wasRelayer_wasSole_false);
+ RUN_TEST(test_wasRelayer_all_six_slots);
+
+ // Group 5 — removeRelayer
+ RUN_TEST(test_removeRelayer_removes);
+ RUN_TEST(test_removeRelayer_compacts);
+ RUN_TEST(test_removeRelayer_nonexistent_safe);
+ RUN_TEST(test_removeRelayer_packet_not_found_safe);
+
+ // Group 6 — checkRelayers
+ RUN_TEST(test_checkRelayers_both_found);
+ RUN_TEST(test_checkRelayers_one_found);
+ RUN_TEST(test_checkRelayers_r2WasSole);
+
+ // Group 7 — Merge Logic
+ RUN_TEST(test_merge_preserves_original_next_hop);
+ RUN_TEST(test_merge_preserves_highest_hop_limit);
+ RUN_TEST(test_merge_no_duplicate_relayers);
+ RUN_TEST(test_merge_adds_new_relayer);
+ RUN_TEST(test_merge_we_relay_sets_slot_zero);
+ RUN_TEST(test_merge_heard_back_stores_relay_node);
+
+ // Group 8 — Fallback-to-Flooding Detection
+ RUN_TEST(test_fallback_detected);
+ RUN_TEST(test_fallback_not_when_we_relayed);
+ RUN_TEST(test_fallback_not_on_first_observation);
+
+ // Group 9 — Next-Hop and Upgrade Detection
+ RUN_TEST(test_weWereNextHop_true);
+ RUN_TEST(test_weWereNextHop_false);
+ RUN_TEST(test_wasUpgraded_true);
+ RUN_TEST(test_wasUpgraded_false);
+
+ // Group 10 — Edge Cases
+ RUN_TEST(test_packet_id_zero_not_stored);
+ RUN_TEST(test_sender_zero_substituted);
+ RUN_TEST(test_uninitialized_wasSeenRecently);
+ RUN_TEST(test_uninitialized_wasRelayer);
+ RUN_TEST(test_multiple_instances_independent);
+
+ // Group 11 — Hash Table Stress
+ RUN_TEST(test_many_packets_no_false_negatives);
+ RUN_TEST(test_many_packets_no_false_positives);
+ RUN_TEST(test_churn_correctness);
+
+ exit(UNITY_END());
+}
+
+void loop() {}
diff --git a/test/test_transmit_history/test_main.cpp b/test/test_transmit_history/test_main.cpp
index 3bd84b55c..c242aa646 100644
--- a/test/test_transmit_history/test_main.cpp
+++ b/test/test_transmit_history/test_main.cpp
@@ -303,6 +303,10 @@ void setup()
{
initializeTestEnvironment();
+ // Wait for portduino's millis() clock to start ticking before tests run
+ testDelay(10);
+ testDelay(2000);
+
UNITY_BEGIN();
RUN_TEST(test_setLastSentToMesh_stores_millis);
diff --git a/test/test_utf8/test_main.cpp b/test/test_utf8/test_main.cpp
new file mode 100644
index 000000000..5a074e96e
--- /dev/null
+++ b/test/test_utf8/test_main.cpp
@@ -0,0 +1,197 @@
+#include "meshUtils.h"
+#include
+#include
+
+void setUp(void) {}
+void tearDown(void) {}
+
+// --- Valid UTF-8 should pass through unchanged ---
+
+void test_ascii_unchanged()
+{
+ char buf[32] = "Hello World";
+ TEST_ASSERT_FALSE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("Hello World", buf);
+}
+
+void test_valid_2byte_unchanged()
+{
+ // "café" — é is C3 A9
+ char buf[16] = "caf\xC3\xA9";
+ TEST_ASSERT_FALSE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("caf\xC3\xA9", buf);
+}
+
+void test_valid_3byte_unchanged()
+{
+ // "€" is E2 82 AC
+ char buf[16] = "\xE2\x82\xAC";
+ TEST_ASSERT_FALSE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("\xE2\x82\xAC", buf);
+}
+
+void test_valid_4byte_emoji_unchanged()
+{
+ // 🌙 is F0 9F 8C 99
+ char buf[16] = "\xF0\x9F\x8C\x99";
+ TEST_ASSERT_FALSE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("\xF0\x9F\x8C\x99", buf);
+}
+
+void test_valid_mixed_unchanged()
+{
+ // "Hi 🌙!" — mix of ASCII and 4-byte
+ char buf[16] = "Hi \xF0\x9F\x8C\x99!";
+ TEST_ASSERT_FALSE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("Hi \xF0\x9F\x8C\x99!", buf);
+}
+
+void test_empty_string()
+{
+ char buf[8] = "";
+ TEST_ASSERT_FALSE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("", buf);
+}
+
+// --- Invalid sequences observed in the wild ---
+
+void test_truncated_4byte_at_end()
+{
+ // Name with valid emoji 🌙 followed by a truncated 4-byte sequence + ASCII
+ char buf[32] = "Lunar Tower \xF0\x9F\x8C\x99\xF0\x9F\x97"
+ "4";
+ TEST_ASSERT_TRUE(sanitizeUtf8(buf, sizeof(buf)));
+ // The 🌙 should be preserved; F0 9F 97 is an incomplete 4-byte sequence,
+ // '4' (0x34) is not a valid continuation byte
+ TEST_ASSERT_EQUAL_STRING("Lunar Tower \xF0\x9F\x8C\x99???4", buf);
+}
+
+void test_lone_lead_bytes_without_continuations()
+{
+ // Mixed ASCII with stray multibyte lead bytes (E1, F3) lacking proper continuations
+ char buf[32] = "Mesht\xE1\xF3tic 37e2";
+ TEST_ASSERT_TRUE(sanitizeUtf8(buf, sizeof(buf)));
+ // E1 expects 2 continuation bytes, but F3 is not a continuation → E1 replaced
+ // F3 expects 3 continuation bytes, 't','i','c' are not continuations → F3 replaced
+ TEST_ASSERT_EQUAL_STRING("Mesht??tic 37e2", buf);
+}
+
+// --- Edge cases ---
+
+void test_bare_continuation_byte()
+{
+ // 0x80 alone is invalid (continuation byte with no lead)
+ char buf[8] = "\x80";
+ TEST_ASSERT_TRUE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("?", buf);
+}
+
+void test_overlong_2byte()
+{
+ // C0 AF is an overlong encoding of U+002F '/'
+ char buf[8] = "\xC0\xAF";
+ TEST_ASSERT_TRUE(sanitizeUtf8(buf, sizeof(buf)));
+ // C0 is a 2-byte lead, AF is valid continuation, but codepoint 0x2F < 0x80 → overlong
+ // C0 replaced, AF (now bare continuation) also replaced
+ TEST_ASSERT_EQUAL_STRING("??", buf);
+}
+
+void test_surrogate_half()
+{
+ // ED A0 80 encodes U+D800 (surrogate half — invalid in UTF-8)
+ char buf[8] = "\xED\xA0\x80";
+ TEST_ASSERT_TRUE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("???", buf);
+}
+
+void test_5byte_sequence_rejected()
+{
+ // F8 80 80 80 80 — 5-byte sequence, not valid UTF-8
+ char buf[8] = "\xF8\x80\x80\x80\x80";
+ TEST_ASSERT_TRUE(sanitizeUtf8(buf, sizeof(buf)));
+ // F8 is invalid lead (>= 0xF8), each 0x80 is bare continuation
+ TEST_ASSERT_EQUAL_STRING("?????", buf);
+}
+
+void test_truncated_3byte_at_buffer_end()
+{
+ // Buffer is exactly 4 bytes: E2 82 then forced null at [3]
+ char buf[4];
+ buf[0] = '\xE2';
+ buf[1] = '\x82';
+ buf[2] = '\0'; // String ends before the 3-byte sequence completes
+ buf[3] = '\0';
+ TEST_ASSERT_TRUE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("??", buf);
+}
+
+void test_null_termination_enforced()
+{
+ // Fill buffer completely with no null terminator
+ char buf[5];
+ memset(buf, 'A', sizeof(buf));
+ TEST_ASSERT_TRUE(sanitizeUtf8(buf, sizeof(buf)));
+ // Should be null-terminated and content preserved (all ASCII)
+ TEST_ASSERT_EQUAL_STRING("AAAA", buf);
+}
+
+void test_null_buffer()
+{
+ TEST_ASSERT_FALSE(sanitizeUtf8(nullptr, 10));
+}
+
+void test_zero_size()
+{
+ char buf[4] = "Hi";
+ TEST_ASSERT_FALSE(sanitizeUtf8(buf, 0));
+ // Buffer should be untouched
+ TEST_ASSERT_EQUAL_STRING("Hi", buf);
+}
+
+void test_valid_max_codepoint()
+{
+ // U+10FFFF = F4 8F BF BF (maximum valid Unicode codepoint)
+ char buf[8] = "\xF4\x8F\xBF\xBF";
+ TEST_ASSERT_FALSE(sanitizeUtf8(buf, sizeof(buf)));
+ TEST_ASSERT_EQUAL_STRING("\xF4\x8F\xBF\xBF", buf);
+}
+
+void test_above_max_codepoint()
+{
+ // U+110000 = F4 90 80 80 (just above maximum valid Unicode)
+ char buf[8] = "\xF4\x90\x80\x80";
+ TEST_ASSERT_TRUE(sanitizeUtf8(buf, sizeof(buf)));
+}
+
+void setup()
+{
+ UNITY_BEGIN();
+
+ // Valid UTF-8 passthrough
+ RUN_TEST(test_ascii_unchanged);
+ RUN_TEST(test_valid_2byte_unchanged);
+ RUN_TEST(test_valid_3byte_unchanged);
+ RUN_TEST(test_valid_4byte_emoji_unchanged);
+ RUN_TEST(test_valid_mixed_unchanged);
+ RUN_TEST(test_empty_string);
+
+ // Invalid sequences observed in the wild
+ RUN_TEST(test_truncated_4byte_at_end);
+ RUN_TEST(test_lone_lead_bytes_without_continuations);
+
+ // Edge cases
+ RUN_TEST(test_bare_continuation_byte);
+ RUN_TEST(test_overlong_2byte);
+ RUN_TEST(test_surrogate_half);
+ RUN_TEST(test_5byte_sequence_rejected);
+ RUN_TEST(test_truncated_3byte_at_buffer_end);
+ RUN_TEST(test_null_termination_enforced);
+ RUN_TEST(test_null_buffer);
+ RUN_TEST(test_zero_size);
+ RUN_TEST(test_valid_max_codepoint);
+ RUN_TEST(test_above_max_codepoint);
+
+ exit(UNITY_END());
+}
+
+void loop() {}
diff --git a/variants/esp32/m5stack_coreink/platformio.ini b/variants/esp32/m5stack_coreink/platformio.ini
index e107bd893..70ada7bf3 100644
--- a/variants/esp32/m5stack_coreink/platformio.ini
+++ b/variants/esp32/m5stack_coreink/platformio.ini
@@ -19,7 +19,7 @@ build_flags =
lib_deps =
${esp32_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.4
lib_ignore =
diff --git a/variants/esp32/tbeam/variant.h b/variants/esp32/tbeam/variant.h
index cca52cb9a..e51855b1a 100644
--- a/variants/esp32/tbeam/variant.h
+++ b/variants/esp32/tbeam/variant.h
@@ -35,9 +35,10 @@
// code)
#endif
-// Leave undefined to disable our PMU IRQ handler. DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts
-// and waking from light sleep
-// #define PMU_IRQ 35
+// Voiding more warranties.
+#define PMU_IRQ 35
+#define PMU_POWER_BUTTON_IS_CANCEL // maps a short click of the power button to a cancel action (turning off the screen)
+
#define HAS_AXP192
#define GPS_UBLOX
#define GPS_RX_PIN 34
diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini
index 90e4910f4..71116279c 100644
--- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini
+++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini
@@ -11,7 +11,7 @@ upload_speed = 921600
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
build_unflags =
${esp32s3_base.build_unflags}
-DARDUINO_USB_MODE=1
diff --git a/variants/esp32s3/esp32-s3-pico/platformio.ini b/variants/esp32s3/esp32-s3-pico/platformio.ini
index 64f50f80e..b5ff66b85 100644
--- a/variants/esp32s3/esp32-s3-pico/platformio.ini
+++ b/variants/esp32s3/esp32-s3-pico/platformio.ini
@@ -23,4 +23,4 @@ build_flags = ${esp32s3_base.build_flags}
lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h
index 3ee5545a8..f689b20a8 100644
--- a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h
+++ b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h
@@ -1,6 +1,7 @@
#define LED_POWER 33
#define LED_POWER2 34
#define EXT_PWR_DETECT 35
+#define EXT_PWR_DETECT_MODE INPUT_PULLUP
#define BUTTON_PIN 18
#define BUTTON_ACTIVE_LOW false
diff --git a/variants/esp32s3/heltec_sensor_hub/variant.h b/variants/esp32s3/heltec_sensor_hub/variant.h
index 8c5d31c9a..64255c038 100644
--- a/variants/esp32s3/heltec_sensor_hub/variant.h
+++ b/variants/esp32s3/heltec_sensor_hub/variant.h
@@ -1,4 +1,5 @@
#define EXT_PWR_DETECT 20
+#define EXT_PWR_DETECT_MODE INPUT_PULLUP
#define BUTTON_PIN 17
#define BUTTON_ACTIVE_LOW false
diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini
index 5a5004a45..d6634aa74 100644
--- a/variants/esp32s3/heltec_v4/platformio.ini
+++ b/variants/esp32s3/heltec_v4/platformio.ini
@@ -120,7 +120,7 @@ build_flags =
-D TFT_OFFSET_Y=0
-D TFT_OFFSET_ROTATION=0
-D SCREEN_ROTATE
- -D SCREEN_TRANSITION_FRAMERATE=5
+ -D SCREEN_TRANSITION_FRAMERATE=30
-D BRIGHTNESS_DEFAULT=130 ; Medium Low Brightness
-D HAS_TOUCHSCREEN=1
-D TOUCH_I2C_PORT=0
@@ -133,4 +133,4 @@ lib_deps = ${heltec_v4_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.19
# renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master
- https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
\ No newline at end of file
+ https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
diff --git a/variants/esp32s3/heltec_vision_master_t190/platformio.ini b/variants/esp32s3/heltec_vision_master_t190/platformio.ini
index 3dab9f93c..c0c3b2f0e 100644
--- a/variants/esp32s3/heltec_vision_master_t190/platformio.ini
+++ b/variants/esp32s3/heltec_vision_master_t190/platformio.ini
@@ -20,5 +20,5 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
- https://github.com/meshtastic/st7789/archive/92bae2e4a307afb430c3b0bc3d661c55ee1565f0.zip
+ https://github.com/meshtastic/st7789/archive/5180423ae2dbf5885168a8bfb308c7fb7eff6930.zip
upload_speed = 921600
diff --git a/variants/esp32s3/m5stack_cardputer_adv/platformio.ini b/variants/esp32s3/m5stack_cardputer_adv/platformio.ini
index 3b378ed94..1144994a0 100644
--- a/variants/esp32s3/m5stack_cardputer_adv/platformio.ini
+++ b/variants/esp32s3/m5stack_cardputer_adv/platformio.ini
@@ -10,13 +10,10 @@ build_flags =
-D M5STACK_CARDPUTER_ADV
-D ARDUINO_USB_CDC_ON_BOOT=1
-I variants/esp32s3/m5stack_cardputer_adv
-build_src_filter =
- ${esp32s3_base.build_src_filter}
- +<../variants/esp32s3/m5stack_cardputer_adv>
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
- https://github.com/meshtastic/st7789/archive/92bae2e4a307afb430c3b0bc3d661c55ee1565f0.zip
+ https://github.com/meshtastic/st7789/archive/5180423ae2dbf5885168a8bfb308c7fb7eff6930.zip
# renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver
https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.1.zip
# renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix
diff --git a/variants/esp32s3/picomputer-s3/variant.h b/variants/esp32s3/picomputer-s3/variant.h
index 7b6218f87..60afac002 100644
--- a/variants/esp32s3/picomputer-s3/variant.h
+++ b/variants/esp32s3/picomputer-s3/variant.h
@@ -47,10 +47,9 @@
#define TFT_OFFSET_Y 0
#define TFT_OFFSET_ROTATION 0
#define SCREEN_ROTATE
-#define SCREEN_TRANSITION_FRAMERATE 5
+#define SCREEN_TRANSITION_FRAMERATE 30
// Picomputer gets a white on black display
-#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255)
#define INPUTBROKER_MATRIX_TYPE 1
diff --git a/variants/esp32s3/seeed-sensecap-indicator/variant.h b/variants/esp32s3/seeed-sensecap-indicator/variant.h
index f946528ae..8fa9e2393 100644
--- a/variants/esp32s3/seeed-sensecap-indicator/variant.h
+++ b/variants/esp32s3/seeed-sensecap-indicator/variant.h
@@ -36,7 +36,7 @@
#define TFT_OFFSET_ROTATION 0
#define TFT_BL 45
#define SCREEN_ROTATE
-#define SCREEN_TRANSITION_FRAMERATE 5 // fps
+#define SCREEN_TRANSITION_FRAMERATE 30 // fps
#define USE_TFTDISPLAY 1
#define HAS_TOUCHSCREEN 1
diff --git a/variants/esp32s3/station-g2/pins_arduino.h b/variants/esp32s3/station-g2/pins_arduino.h
old mode 100644
new mode 100755
diff --git a/variants/esp32s3/station-g2/platformio.ini b/variants/esp32s3/station-g2/platformio.ini
old mode 100644
new mode 100755
diff --git a/variants/esp32s3/station-g2/variant.h b/variants/esp32s3/station-g2/variant.h
old mode 100644
new mode 100755
diff --git a/variants/esp32s3/t-deck-pro-v1_1/platformio.ini b/variants/esp32s3/t-deck-pro-v1_1/platformio.ini
index 1a9b20f76..22432d769 100644
--- a/variants/esp32s3/t-deck-pro-v1_1/platformio.ini
+++ b/variants/esp32s3/t-deck-pro-v1_1/platformio.ini
@@ -30,7 +30,7 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
# renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main
https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip
# renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328
diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini
index 93ef8babf..d1a2398a4 100644
--- a/variants/esp32s3/t-deck-pro/platformio.ini
+++ b/variants/esp32s3/t-deck-pro/platformio.ini
@@ -34,7 +34,7 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
# renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main
https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip
# renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328
diff --git a/variants/esp32s3/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h
index 5d885579a..eb1bbdfef 100644
--- a/variants/esp32s3/t-deck/variant.h
+++ b/variants/esp32s3/t-deck/variant.h
@@ -20,7 +20,7 @@
#define TFT_OFFSET_Y 0
#define TFT_OFFSET_ROTATION 0
#define SCREEN_ROTATE
-#define SCREEN_TRANSITION_FRAMERATE 5
+#define SCREEN_TRANSITION_FRAMERATE 30
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
#define USE_TFTDISPLAY 1
#define HAS_PHYSICAL_KEYBOARD 1
diff --git a/variants/esp32s3/t-watch-s3/variant.h b/variants/esp32s3/t-watch-s3/variant.h
index df275c31d..fddd98304 100644
--- a/variants/esp32s3/t-watch-s3/variant.h
+++ b/variants/esp32s3/t-watch-s3/variant.h
@@ -39,9 +39,10 @@
#define DAC_I2S_BCK 48
#define DAC_I2S_WS 15
#define DAC_I2S_DOUT 46
-#define DAC_I2S_MCLK 0
+#define DAC_I2S_MCLK -1
#define HAS_AXP2101
+#define PMU_POWER_BUTTON_IS_CANCEL // maps a short click of the power button to a cancel action (turning off the screen)
// PCF8563 RTC Module
#define PCF8563_RTC 0x51
@@ -60,6 +61,8 @@
#define BUTTON_PIN 0 // only for Plus version
+#define PMU_IRQ 21 // Interrupt pin for the PMU
+
#define USE_SX1262
#define USE_SX1268
diff --git a/variants/esp32s3/t5s3_epaper/nicheGraphics.h b/variants/esp32s3/t5s3_epaper/nicheGraphics.h
index 699a82de0..6b5f434ba 100644
--- a/variants/esp32s3/t5s3_epaper/nicheGraphics.h
+++ b/variants/esp32s3/t5s3_epaper/nicheGraphics.h
@@ -6,26 +6,27 @@ NicheGraphics attempts a different approach:
Per-device config takes place in this setupNicheGraphics() method
(And a small amount in platformio.ini)
-This file sets up InkHUD for Heltec VM-E290.
-Different NicheGraphics UIs and different hardware variants will each have their own setup procedure.
+This file sets up InkHUD for the LilyGo T5-E-Paper-S3-Pro.
+
+The board uses a 4.7" ED047TC1 parallel e-paper display (960×540, 8-bit parallel interface).
+This is driven via the FastEPD library through the NicheGraphics ED047TC1 driver adapter.
*/
#pragma once
#include "configuration.h"
-#include "mesh/MeshModule.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
// InkHUD-specific components
// ---------------------------
-// #include "graphics/niche/InkHUD/InkHUD.h"
-#include "graphics/niche/InkHUD/WindowManager.h"
+#include "graphics/niche/InkHUD/InkHUD.h"
// Applets
#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
@@ -33,27 +34,20 @@ Different NicheGraphics UIs and different hardware variants will each have their
// Shared NicheGraphics components
// --------------------------------
-#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
-#include "graphics/niche/Drivers/EInk/DEPG0290BNS800.h"
+#include "graphics/niche/Drivers/EInk/ED047TC1.h"
#include "graphics/niche/Inputs/TwoButton.h"
void setupNicheGraphics()
{
using namespace NicheGraphics;
- // SPI
- // -----------------------------
-
- // Display is connected to HSPI
- SPIClass *hspi = new SPIClass(HSPI);
- hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
-
// E-Ink Driver
// -----------------------------
+ // The ED047TC1 is a parallel display — no SPI bus setup needed.
+ // begin() args are part of the EInk interface but are ignored for parallel displays.
- // Use E-Ink driver
- Drivers::EInk *driver = new Drivers::DEPG0290BNS800;
- driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY);
+ Drivers::EInk *driver = new Drivers::ED047TC1;
+ driver->begin(nullptr, 0, 0, 0);
// InkHUD
// ----------------------------
@@ -67,57 +61,59 @@ void setupNicheGraphics()
// Set how unhealthy additional FAST updates beyond this number are
inkhud->setDisplayResilience(7, 1.5);
- // Prepare fonts
- InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252;
- InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
+ // Prepare fonts — use larger sizes to suit the 4.7" screen at ~234 DPI
+ InkHUD::Applet::fontLarge = FREESANS_24PT_WIN1253;
+ InkHUD::Applet::fontMedium = FREESANS_18PT_WIN1253;
+ InkHUD::Applet::fontSmall = FREESANS_12PT_WIN1253;
- // Init settings, and customize defaults
+ // Customize default settings
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
- inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise
+ inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
- inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
- inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
+ inkhud->persistence->settings.optionalFeatures.batteryIcon = true;
+ inkhud->persistence->settings.optionalMenuItems.backlight = false;
- // Setup backlight
- // Note: AUX button behavior configured further down
- Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance();
- backlight->setPin(PIN_EINK_EN);
+ // Alignment must cancel rotation for visual-frame touch input: (rotation + alignment) % 4 == 0.
+ inkhud->persistence->settings.joystick.alignment = (4 - inkhud->persistence->settings.rotation) % 4;
// Pick applets
// Note: order of applets determines priority of "auto-show" feature
- // Optional arguments for defaults:
- // - is activated?
- // - is autoshown?
- // - is foreground on a specific tile (index)?
- inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
- inkhud->addApplet("DMs", new InkHUD::DMApplet);
- inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
- inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
- inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
- inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
+ inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, false, false); // Not Active, not autoshown
+ inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Activated, Autoshown
+ inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, true); // Activated, Autoshown
+ inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), false, false); // Not Active, not autoshown
+ inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false); // Activated, not autoshown
+ inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false); // Activated, not autoshown
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
- // inkhud->addApplet("Basic", new InkHUD::BasicExampleApplet);
- // inkhud->addApplet("NewMsg", new InkHUD::NewMsgExampleApplet);
+ inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // Not Active, not autoshown
+
+ // Enable reusable InkHUD touch status indicator for this touch-capable board.
+ inkhud->setTouchEnabledProvider(isTouchInputEnabled);
// Start running InkHUD
inkhud->begin();
+ // Arm GT911 capacitive-home callback only after InkHUD startup is complete.
+ t5SetHomeCapButtonEventsEnabled(true);
+
+ // Keep Wireless Paper single-button semantics regardless of persisted settings:
+ // short press advances, long press opens menu/selects.
+ inkhud->persistence->settings.joystick.enabled = false;
// Buttons
// --------------------------
Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // A shared NicheGraphics component
- // Setup the main user button (0)
+ // #0: BOOT button (primary user input for InkHUD navigation on T5-S3)
+#if defined(T5_S3_EPAPER_PRO_V1)
+ buttons->setWiring(0, PIN_BUTTON2);
+#else
buttons->setWiring(0, BUTTON_PIN);
- buttons->setHandlerShortPress(0, []() { InkHUD::InkHUD::getInstance()->shortpress(); });
- buttons->setHandlerLongPress(0, []() { InkHUD::InkHUD::getInstance()->longpress(); });
-
- // Setup the aux button (1)
- // Bonus feature of VME290
- buttons->setWiring(1, BUTTON_PIN_SECONDARY);
- buttons->setHandlerShortPress(1, []() { InkHUD::InkHUD::getInstance()->nextTile(); });
+#endif
+ buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
+ buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
buttons->start();
}
-#endif
\ No newline at end of file
+#endif
diff --git a/variants/esp32s3/t5s3_epaper/platformio.ini b/variants/esp32s3/t5s3_epaper/platformio.ini
index bad36706c..2001133c7 100644
--- a/variants/esp32s3/t5s3_epaper/platformio.ini
+++ b/variants/esp32s3/t5s3_epaper/platformio.ini
@@ -5,7 +5,7 @@ board_build.partition = default_16MB.csv
board_check = true
upload_protocol = esptool
build_flags = -fno-strict-aliasing
- ${esp32_base.build_flags}
+ ${esp32s3_base.build_flags}
-I variants/esp32s3/t5s3_epaper
-D T5_S3_EPAPER_PRO
-D USE_EINK
diff --git a/variants/esp32s3/t5s3_epaper/variant.cpp b/variants/esp32s3/t5s3_epaper/variant.cpp
index e10d7c347..6599ced23 100644
--- a/variants/esp32s3/t5s3_epaper/variant.cpp
+++ b/variants/esp32s3/t5s3_epaper/variant.cpp
@@ -1,28 +1,9 @@
-#include "configuration.h"
-
-#ifdef T5_S3_EPAPER_PRO
-
-#include "TouchDrvGT911.hpp"
-#include "Wire.h"
-#include "input/TouchScreenImpl1.h"
-
-TouchDrvGT911 touch;
-
-bool readTouch(int16_t *x, int16_t *y)
-{
- if (!digitalRead(GT911_PIN_INT)) {
- int16_t raw_x;
- int16_t raw_y;
- if (touch.getPoint(&raw_x, &raw_y)) {
- // rotate 90° for landscape
- *x = raw_y;
- *y = EPD_WIDTH - 1 - raw_x;
- LOG_DEBUG("touched(%d/%d)", *x, *y);
- return true;
- }
- }
- return false;
-}
+// Pin-level early init only. All touch, backlight, and InkHUD code lives in
+// src/platform/extra_variants/t5s3_epaper/variant.cpp where PlatformIO's
+// library dependency finder can resolve headers like TouchDrvGT911.hpp.
+#include "variant.h"
+#include "Arduino.h"
+#include "pins_arduino.h"
void earlyInitVariant()
{
@@ -31,17 +12,28 @@ void earlyInitVariant()
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(BOARD_BL_EN, OUTPUT);
-}
+ // Backlight ON at boot (active-HIGH). Full backlight state management
+ // lives in src/platform/extra_variants/t5s3_epaper/variant.cpp.
+ digitalWrite(BOARD_BL_EN, HIGH);
-// T5-S3-ePaper Pro specific (late-) init
-void lateInitVariant(void)
-{
- touch.setPins(GT911_PIN_RST, GT911_PIN_INT);
- if (touch.begin(Wire, GT911_SLAVE_ADDRESS_L, GT911_PIN_SDA, GT911_PIN_SCL)) {
- touchScreenImpl1 = new TouchScreenImpl1(EPD_WIDTH, EPD_HEIGHT, readTouch);
- touchScreenImpl1->init();
- } else {
- LOG_ERROR("Failed to find touch controller!");
- }
+ // Program GT911 touch controller to I2C address 0x14 (GT911_SLAVE_ADDRESS_H) before
+ // the I2C bus scan runs. GPIO3 (INT) defaults LOW on ESP32-S3 cold boot, which would
+ // leave the GT911 at 0x5D (GT911_SLAVE_ADDRESS_L) — the same address as the SFA30
+ // air quality sensor — causing a false-positive SFA30 detection during the I2C scan.
+ //
+ // GT911 datasheet §4.3 "Address Selection":
+ // Pull INT HIGH before releasing RST → device latches address 0x14 (SLAVE_ADDRESS_H)
+ // Pull INT LOW before releasing RST → device latches address 0x5D (SLAVE_ADDRESS_L)
+ // Minimum RST assert time: 100 µs; minimum startup time after RST deassert: 5 ms.
+ //
+ // lateInitVariant() calls touch.begin() which repeats this sequence internally while
+ // also performing full I2C initialisation; the double-reset is harmless.
+ pinMode(GT911_PIN_RST, OUTPUT);
+ digitalWrite(GT911_PIN_RST, LOW);
+ pinMode(GT911_PIN_INT, OUTPUT);
+ digitalWrite(GT911_PIN_INT, HIGH); // HIGH → latch address 0x14
+ delay(1); // > 100 µs
+ digitalWrite(GT911_PIN_RST, HIGH);
+ delay(10); // > 5 ms startup
+ pinMode(GT911_PIN_INT, INPUT); // release INT for interrupt use
}
-#endif
\ No newline at end of file
diff --git a/variants/esp32s3/t5s3_epaper/variant.h b/variants/esp32s3/t5s3_epaper/variant.h
index c2c001373..49579a39c 100644
--- a/variants/esp32s3/t5s3_epaper/variant.h
+++ b/variants/esp32s3/t5s3_epaper/variant.h
@@ -16,6 +16,11 @@
#define I2C_SCL SCL
#define HAS_TOUCHSCREEN 1
+#define TOUCH_POLL_INTERVAL_IDLE 25
+#define TOUCH_POLL_INTERVAL_ACTIVE 15
+#define TOUCH_POLL_INTERVAL_RELEASE 20
+#define TOUCH_POLL_INTERVAL_ACTIVE_FAST 8
+#define TOUCH_POLL_INTERVAL_RELEASE_FAST 8
#define GT911_PIN_SDA SDA
#define GT911_PIN_SCL SCL
#if defined(T5_S3_EPAPER_PRO_V1)
@@ -25,10 +30,34 @@
#define GT911_PIN_INT 3
#define GT911_PIN_RST 9
#endif
+// Do not use touch as a light-sleep wake source on T5-S3.
+// Wake should come from physical buttons/radio/timer only.
+#define SCREEN_TOUCH_INT GT911_PIN_INT
-#define PCF85063_RTC 0x51
+// Touch control helpers for this variant
+bool isTouchInputEnabled();
+void setTouchInputEnabled(bool enabled, bool showIndicator);
+void toggleTouchInputEnabled();
+
+// Backlight control helpers for this variant (non-latching behavior)
+void t5BacklightSetUserEnabled(bool enabled);
+bool t5BacklightIsUserEnabled();
+void t5BacklightToggleUser();
+void t5BacklightSetForcedByTimeout(bool forced);
+void t5BacklightSetForcedBySleep(bool forced);
+void t5BacklightHandleUserInput();
+
+// Touch timeout/wake helpers for this variant
+void t5TouchSetForcedByTimeout(bool forced);
+bool t5TouchIsForcedByTimeout();
+void t5TouchHandleUserInput();
+
+// Gate GT911 capacitive-home callback delivery until InkHUD startup is complete.
+void t5SetHomeCapButtonEventsEnabled(bool enabled);
+
+#define PCF8563_RTC 0x51
#define HAS_RTC 1
-#define PCF85063_INT 2
+#define PCF8563_INT 2
#define USE_POWERSAVE
#define SLEEP_TIME 120
@@ -45,8 +74,10 @@
#define ALT_BUTTON_PIN PIN_BUTTON2
#else
#define BUTTON_PIN 0
+#define BOARD_PCA9535_ADDR 0x20
+#define BOARD_PCA9535_INT 38
+#define BOARD_PCA9535_BUTTON_MASK 0x04
#endif
-
// SD card
#define HAS_SDCARD
#define SDCARD_CS SPI_CS
diff --git a/variants/esp32s3/tbeam-s3-core/variant.h b/variants/esp32s3/tbeam-s3-core/variant.h
index 9ce4aade9..11e463364 100644
--- a/variants/esp32s3/tbeam-s3-core/variant.h
+++ b/variants/esp32s3/tbeam-s3-core/variant.h
@@ -9,8 +9,6 @@
#define BUTTON_PIN 0 // The middle button GPIO on the T-Beam S3
// #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module.
-#define LED_STATE_ON 0 // State when LED is lit
-
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
// not found then probe for SX1262
#define USE_SX1262
@@ -48,9 +46,9 @@
#define LR11X0_DIO_AS_RF_SWITCH
#endif
-// Leave undefined to disable our PMU IRQ handler. DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts
-// and waking from light sleep
-// #define PMU_IRQ 40
+// Voiding warrenties, we're gonna try the IRQ
+#define PMU_IRQ 40
+#define PMU_POWER_BUTTON_IS_CANCEL // maps a short click of the power button to a cancel action (turning off the screen)
#define HAS_AXP2101
// PCF8563 RTC Module
@@ -76,4 +74,4 @@
// has 32768 Hz crystal
#define HAS_32768HZ 1
-#define USE_SH1106
\ No newline at end of file
+#define USE_SH1106
diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h
index 28359c786..f2512fb7c 100644
--- a/variants/esp32s3/tlora-pager/variant.h
+++ b/variants/esp32s3/tlora-pager/variant.h
@@ -18,7 +18,7 @@
#define TFT_OFFSET_Y 0
#define TFT_OFFSET_ROTATION 3
#define SCREEN_ROTATE
-#define SCREEN_TRANSITION_FRAMERATE 5
+#define SCREEN_TRANSITION_FRAMERATE 30
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
#define USE_TFTDISPLAY 1
#define HAS_PHYSICAL_KEYBOARD 1
diff --git a/variants/esp32s3/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h
index f9a20c901..b2822c24b 100644
--- a/variants/esp32s3/tracksenger/internal/variant.h
+++ b/variants/esp32s3/tracksenger/internal/variant.h
@@ -73,7 +73,6 @@
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// Picomputer gets a white on black display
-#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255)
// keyboard changes
@@ -89,4 +88,4 @@
{ \
26, 37, 17, 16, 15, 7 \
}
-// #end keyboard
\ No newline at end of file
+// #end keyboard
diff --git a/variants/esp32s3/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h
index 029f7753b..6c32ff279 100644
--- a/variants/esp32s3/tracksenger/lcd/variant.h
+++ b/variants/esp32s3/tracksenger/lcd/variant.h
@@ -97,7 +97,6 @@
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// Picomputer gets a white on black display
-#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255)
// keyboard changes
@@ -113,4 +112,4 @@
{ \
26, 37, 17, 16, 15, 7 \
}
-// #end keyboard
\ No newline at end of file
+// #end keyboard
diff --git a/variants/esp32s3/tracksenger/oled/variant.h b/variants/esp32s3/tracksenger/oled/variant.h
index 1f1fbbaa1..72762c7af 100644
--- a/variants/esp32s3/tracksenger/oled/variant.h
+++ b/variants/esp32s3/tracksenger/oled/variant.h
@@ -74,7 +74,6 @@
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// Picomputer gets a white on black display
-#define TFT_MESH_OVERRIDE COLOR565(255, 255, 255)
// keyboard changes
@@ -90,4 +89,4 @@
{ \
26, 37, 17, 16, 15, 7 \
}
-// #end keyboard
\ No newline at end of file
+// #end keyboard
diff --git a/variants/esp32s3/unphone/variant.h b/variants/esp32s3/unphone/variant.h
index 268eedea5..76c66ca64 100644
--- a/variants/esp32s3/unphone/variant.h
+++ b/variants/esp32s3/unphone/variant.h
@@ -35,7 +35,7 @@
#define TFT_OFFSET_ROTATION 6 // unPhone's screen wired unusually, 0 typical
#define TFT_INVERT false
#define SCREEN_ROTATE true
-#define SCREEN_TRANSITION_FRAMERATE 5
+#define SCREEN_TRANSITION_FRAMERATE 30
#define USE_TFTDISPLAY 1
#define HAS_TOUCHSCREEN 1
@@ -74,4 +74,4 @@
// #define BATTERY_PIN 13 // battery V measurement pin; vbat divider is here
// #define ADC_CHANNEL ADC2_GPIO13_CHANNEL
-// #define BAT_MEASURE_ADC_UNIT 2
\ No newline at end of file
+// #define BAT_MEASURE_ADC_UNIT 2
diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini
index 87d8431a3..b276d2779 100644
--- a/variants/native/portduino.ini
+++ b/variants/native/portduino.ini
@@ -2,7 +2,7 @@
[portduino_base]
platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
- https://github.com/meshtastic/platform-native/archive/71ed55bb95feb3c43ebde1ec1e2e17643a424c04.zip
+ https://github.com/meshtastic/platform-native/archive/135b91e953db0b5f44d278f8ebd5b8d985fc03d8.zip
framework = arduino
build_src_filter =
diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini
index fd159a6d2..e0cdd73c5 100644
--- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini
+++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini
@@ -12,5 +12,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/Dongle_
lib_deps =
${nrf52840_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
debug_tool = jlink
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h
index 2cfe948e3..2164bcedc 100644
--- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h
+++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h
@@ -85,7 +85,6 @@ static const uint8_t A0 = PIN_A0;
// charger status
#define EXT_CHRG_DETECT (32 + 6)
-#define EXT_CHRG_DETECT_VALUE HIGH
// SPI
#define SPI_INTERFACES_COUNT 1
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp
index a43755c06..f15a03f4d 100644
--- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp
+++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp
@@ -20,6 +20,7 @@
#include "variant.h"
#include "nrf.h"
+#include "power.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
@@ -65,7 +66,11 @@ void variant_shutdown()
nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW;
nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1);
- nrf_gpio_cfg_input(EXT_CHRG_DETECT, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
- nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_LOW;
- nrf_gpio_cfg_sense_set(EXT_CHRG_DETECT, sense2);
+ // If we are sleeping because of low battery, wake up when the solar charger detects power.
+ // But if the user intentionally put us to sleep with the button, don't wake up just because the lights are on
+ if (power->isLowBattery()) {
+ nrf_gpio_cfg_input(EXT_CHRG_DETECT, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
+ nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_LOW;
+ nrf_gpio_cfg_sense_set(EXT_CHRG_DETECT, sense2);
+ }
}
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h
index 2ebb79031..48b27c669 100644
--- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h
+++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h
@@ -138,7 +138,7 @@ static const uint8_t A0 = PIN_A0;
#define HAS_SOLAR
-#define OCV_ARRAY 4080, 3990, 3935, 3880, 3825, 3770, 3715, 3660, 3605, 3550, 3450
+#define OCV_ARRAY 4080, 3990, 3935, 3880, 3825, 3770, 3715, 3660, 3605, 3550, 3490
#ifdef __cplusplus
}
diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
index 39b5dfbd4..217e0dd3a 100644
--- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
+++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
@@ -16,7 +16,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS0
lib_deps =
${nrf52840_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
upload_protocol = nrfutil
;upload_port = /dev/ttyACM1
diff --git a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini
index ebea1ce97..d89ef348d 100644
--- a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini
+++ b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini
@@ -15,6 +15,6 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
debug_tool = jlink
;upload_port = /dev/ttyACM4
\ No newline at end of file
diff --git a/variants/nrf52840/TWC_mesh_v4/platformio.ini b/variants/nrf52840/TWC_mesh_v4/platformio.ini
index c529caa0b..8f7479f74 100644
--- a/variants/nrf52840/TWC_mesh_v4/platformio.ini
+++ b/variants/nrf52840/TWC_mesh_v4/platformio.ini
@@ -9,5 +9,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/TWC_mes
lib_deps =
${nrf52840_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
debug_tool = jlink
diff --git a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini
index c9f998240..77beb4d33 100644
--- a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini
+++ b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini
@@ -23,4 +23,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_
lib_deps =
${nrf52840_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
- https://github.com/meshtastic/st7789/archive/92bae2e4a307afb430c3b0bc3d661c55ee1565f0.zip
+ https://github.com/meshtastic/st7789/archive/5180423ae2dbf5885168a8bfb308c7fb7eff6930.zip
diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h
index e7385c4bb..509f749a8 100644
--- a/variants/nrf52840/heltec_mesh_node_t114/variant.h
+++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h
@@ -57,9 +57,6 @@ extern "C" {
#define TFT_OFFSET_X 0
#define TFT_OFFSET_Y 0
-// T114 gets a muted yellow on black display
-#define TFT_MESH_OVERRIDE COLOR565(255, 255, 128)
-
// #define TFT_OFFSET_ROTATION 0
// #define SCREEN_ROTATE
// #define SCREEN_TRANSITION_FRAMERATE 5
diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini
index 1b6f59a68..ae68455dc 100644
--- a/variants/nrf52840/heltec_mesh_solar/platformio.ini
+++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini
@@ -132,4 +132,4 @@ build_flags = ${heltec_mesh_solar_base.build_flags}
lib_deps =
${heltec_mesh_solar_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
- https://github.com/meshtastic/st7789/archive/92bae2e4a307afb430c3b0bc3d661c55ee1565f0.zip
+ https://github.com/meshtastic/st7789/archive/5180423ae2dbf5885168a8bfb308c7fb7eff6930.zip
diff --git a/variants/nrf52840/nrf52.ini b/variants/nrf52840/nrf52.ini
index f42c29308..d11f4fc56 100644
--- a/variants/nrf52840/nrf52.ini
+++ b/variants/nrf52840/nrf52.ini
@@ -7,7 +7,7 @@ extends = arduino_base
platform_packages =
; our custom Git version with C++17 support in platform.txt
# TODO renovate
- platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#cpp17-platform
+ platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#master
; Don't renovate toolchain-gccarmnoneeabi
platformio/toolchain-gccarmnoneeabi@~1.90301.0
diff --git a/variants/nrf52840/rak4631_epaper/platformio.ini b/variants/nrf52840/rak4631_epaper/platformio.ini
index f71fb6301..c970baddd 100644
--- a/variants/nrf52840/rak4631_epaper/platformio.ini
+++ b/variants/nrf52840/rak4631_epaper/platformio.ini
@@ -16,7 +16,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631
lib_deps =
${nrf52840_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
melopero/Melopero RV3028@1.2.0
# renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library
diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini
index 670b2c415..af57ea3cd 100644
--- a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini
+++ b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini
@@ -18,7 +18,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631
lib_deps =
${nrf52840_base.lib_deps}
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
- zinggjm/GxEPD2@1.6.8
+ zinggjm/GxEPD2@1.6.9
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
melopero/Melopero RV3028@1.2.0
# renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library