Compare commits

...

199 Commits

Author SHA1 Message Date
Ben Meadors
44733609c9 Merge branch 'develop' into fix-radio-init 2026-01-27 09:44:09 -06:00
HarukiToreda
d54ae5dad8 InkHUD Menu improvements (#8975)
* InkHUD: Region Picker on initial setup

* Added Node Config menu with Lora Region Picker

* Role picker

* Preset Picker

* Timezone picker added

* Power save mode and bluetooth configs

* Config section Headers

* Channel Config

* Cleaning some behavior

* Add back to all Options

* Display config added

* Position Toggle added

* Network Config for ESP32

* Wifi details

* Reduce line spacing to fit more content

* Recent list with checkboxes

* Timezone labels easier to understand

* Trunk fix

* Added "Saving Changes" screen when reboot is needed

* Trunk fix

* Make Tips show after first boot if the region is Unset

* Added ResetDB and keep only favorite commands

* quick fix to joystick

* Trunk Fix

* Fix to tips to work with new joystick input

* Added ADC multiplier value display on power config

* added ADC calibration feature

* Fixed missing stray endiff

* GPS toggle now is aware if gps is present.
2026-01-27 09:11:11 -06:00
Ben Meadors
f0cdb777b2 Merge branch 'develop' into fix-radio-init 2026-01-27 06:11:40 -06:00
Ben Meadors
ef8f90f8b1 Add error handling for SPI command failures in LR11x0, RF95, and SX128x interfaces 2026-01-27 06:04:30 -06:00
Andrew Yong
90778a4e78 feat(GPS): Support Softsleep with WAKE-UP pin on PA1010D (#9078)
Support softsleep by defining PIN_GPS_STANDBY on CDTop CD-PA1010D.

This differs from existing MTK GPS e.g. L76K, where pulling PIN_GPS_STANDBY (WAKE-UP pin) low is not sufficient to put the GPS module in standby.

An additional `$PMTK225,4*2F` must be sent to enter "Backup Mode", which is exited by bringing PIN_GPS_STANDBY (WAKE-UP pin) high.

Refer to datasheet[0] §1.9.3 "Backup Mode".

0: https://cdn-learn.adafruit.com/assets/assets/000/084/295/original/CD_PA1010D_Datasheet_v.03.pdf

Signed-off-by: Andrew Yong <me@ndoo.sg>
2026-01-27 06:03:31 -06:00
Colby Dillion
63a97a54e1 Fix retry_delay calculation for error responses (#9443)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-26 18:45:24 -06:00
Jonathan Bennett
a2e8e232f1 Remove the unused OCV_ARRAYs and move one to a variant.h (#9442) 2026-01-26 16:58:16 -06:00
Keane
7efc3e3770 Replace strcpy with strncpy and null termination (#9436)
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-01-26 14:32:03 -06:00
Jonathan Bennett
3d58c6e916 Trackball revamp (#9440)
* Trackball revamp

* Use Throttle

* Volatile!
2026-01-26 14:28:05 -06:00
Jonathan Bennett
c038cfe69a Move device code from main.cpp to earlyInitVariant (#9438) 2026-01-26 11:54:14 -06:00
Ben Meadors
0770f25e79 Merge remote-tracking branch 'origin/master' into develop 2026-01-26 08:56:38 -06:00
Jonathan Bennett
8a9830282a Move Lora Init code into LoraInit.cpp/h (#9435)
* Move Lora Init code into LoraInit.cpp/h

* Add missed extern definition

* More carefully check for nullptr in LoraInit.cpp

* STM32 fixes

* Add extern SPI1 for HW_SPI1_DEVICE

* Move initLora to RadioInterface.h
2026-01-25 20:54:17 -06:00
Ben Meadors
8894a0b711 Consolidate LoRa params / preset logic and fix display of preset values (#9413)
* Consolidate LoRa params / preset logic and fix display of preset values

* Move preset coercion logic to RadioInterface

* Fix some warnings

* Fix warnings

* STM32 fix

* Add unit tests

* Update src/mesh/RadioInterface.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-25 18:02:26 -06:00
phaseloop
57a3ff8dfc NRF52 - power management improvements (#9211)
* minor NRF52 test cleanup

* detect USB power input on ProMicro boards

* prevent booting on power failure detection

* introduce PowerHAL layer

* powerHAL basic implementation for NRF52

* prevent data saves on low power

* remove comment

* Update src/platform/nrf52/main-nrf52.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/power/PowerHAL.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/main.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Merge missing voltage threshold comparison

* add missing variable

* add missing function declaration

* remove debug strings

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-24 08:39:03 -06:00
Uğur ALTINSOY
6cff13623f Added Minimesh variant (#9289)
* Minimesh Lite Added

* Add Minimesh Lite NRF

* Added board_level = extra

* Fix formatting and optimize image for Minimesh Lite

* Change image

* The image has been deleted.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-24 08:38:07 -06:00
phaseloop
b312f226b4 Cut NRF52 bluetooth power usage (#8992)
* Improve NRF52 bluetooth power efficiency

* test T114 bad LFXO

* T1000 test

* force BLE param negotiation

* stash

* NRF52 bluetooth small cleanup

* fix potential connectivity issues

* lower BLE min interval to make iOS happy

* remove slave latency negotation

* add BLE issue comment

* code format

* Revert "code format"

This reverts commit 1f92b09d08.

* remove LFCLK debug info

* Fix

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-24 06:22:01 -06:00
renovate[bot]
b627fa720b Update SensorLib to v0.3.4 (#9396)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-24 05:46:46 -06:00
renovate[bot]
9faf178bdc Update XPowersLib to v0.3.3 (#9354)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-24 05:45:26 -06:00
renovate[bot]
c98f134b40 Update meshtastic-esp32_https_server digest to b0f3960 (#9393)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-24 05:39:22 -06:00
renovate[bot]
5838b26d90 Update lewisxhe-SensorLib to v0.3.4 (#9395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-24 05:39:05 -06:00
Eric Sesterhenn
7221fc4d4b Delete unused code (#9350)
* Delete unused code

CryptoEngine::clearKeys() is not used in the code base, therefore this
cleanup removes the code. It might give casual reviewers the impression,
that keys are wiped.

Since the code uses memset() which might be optimized away by the
compiler, using the code might not even cause the memory
to be wiped.

* Update CryptoEngine.cpp

Fix stray newline, this is the only thing that I can come up with that might confuse the linter.

---------

Co-authored-by: Jason P <applewiz@mac.com>
2026-01-24 05:19:19 -06:00
renovate[bot]
a417760887 Update meshtastic/device-ui digest to 37ad715 (#9403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-24 05:18:48 -06:00
renovate[bot]
04d2dd3b1c Update GxEPD2 to v1.6.6 (#9412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-24 05:16:47 -06:00
Justin E. Mann
6b88d37b73 To fix the gps power rail issue on RAK 19007 when RAK12023+RAK12035 is installed (#9409)
* asked claude to fix the gps power rail issue when the io slot is in use.. this fixes the gps when both the RAK12500 GPS module and the RAK12035 soil sensor modules are being used.

* remove do { ... } while(0) from RESTORE_3V3_POWER() Macro

* remove some comments

* cleaner macro

* removed more excessive comments

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-24 05:16:36 -06:00
Ben Meadors
d407ec1975 Merge remote-tracking branch 'origin/master' into develop 2026-01-24 04:47:24 -06:00
Jonathan Bennett
6d6a0734b0 Add pin sense to wake M6 on Solar Charge (#9416) 2026-01-23 15:37:16 -06:00
Mattijs
0157a769c3 Make BLE TX power configurable for nRF52 variants (#9232)
* Make BLE TX power configurable for nRF52 variants

* Include BLE TX power setting in T114 variant.h as tested

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-23 11:26:01 -06:00
Till Maas
73932dd1c3 device-install: Consistently use write-flash (#8868)
* flash scripts: Unify indentation

* flash scripts: Support esptool v4 and v5

esptool v5 supports commands with dashes and deprecates commands with
underscores. Prior versions only support commands with underscores.
2026-01-23 06:05:29 -06:00
HarukiToreda
bc2abf3db4 BaseUI: Bubbles for messages (#9365)
* Message Bubbles

* Angled edges

* Proper indent for messages inside the bubble

* Fix message header line width

* Correctly calculate text width for the header and shrink Channel Name is on OLED

---------

Co-authored-by: Jason P <applewiz@mac.com>
2026-01-22 17:00:21 -06:00
github-actions[bot]
073eb2c672 Automated version bumps (#9402)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-22 16:19:35 -06:00
Jorropo
4744010295 run trunk fmt -a (#9400)
* run trunk fmt -a

* fix bracket bug

This was introduced by @tedwardd and @thebentern in 021106dfe5.

See this diff:
         else
+            checkConfigPort = false;
             printf("Using config file %d\n", TCPPort);
2026-01-22 15:46:37 -06:00
Chloe Bethel
d8d02cd6ec Implement setting TX_GAIN_LORA for portduino (#8501)
* Implement setting TX_GAIN_LORA for portduino

* use std::size instead of sizeof

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-22 07:08:43 -06:00
renovate[bot]
3e3299f549 Update meshtastic/device-ui digest to 613c095 (#9383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 09:07:23 +11:00
github-actions[bot]
eefc08087d Update protobufs (#9371)
Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com>
2026-01-20 10:29:11 -06:00
Andrew Yong
fb6d199d36 feat: Add Russell, a board designed to go Up! on a balloon (#9079)
Hardware repository: https://github.com/Meshtastic-Malaysia/russell
- Designed to mount on an ER34615/IFR32700 cell
- RAK3172 STM32WLE5CCU6 MCU + integrated SX1262 LoRa
- CDtop CD-PA1010D GPS
- Bosch Sensortec BME280 sensor
- Consonance CN3158 LiFePO4 solar charger

Signed-off-by: Andrew Yong <me@ndoo.sg>
2026-01-20 06:38:04 -06:00
Ben Meadors
fb3bf783dd Implement graduated scaling for NodeInfo send timeout based on active mesh size (#9364)
* Implement graduated scaling for NodeInfo send timeout based on active mesh size

* Shorter timeout still needed for pubkey unkown and ad-hoc send

* Update src/modules/NodeInfoModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-19 20:00:41 -06:00
Ben Meadors
fc268d43d0 Add Meshtastic exclusion flags for webserver and paxcounter in platformio.ini 2026-01-19 16:57:21 -06:00
Jonathan Bennett
c38aff7e52 Add interrupt for external charge detection (#9332)
Tested on Thinknode m4, m6, and T1000-e
2026-01-19 15:39:24 -06:00
Ben Meadors
7d4600f8c2 Merge branch 'master' into develop 2026-01-19 12:13:52 -06:00
Ben Meadors
ff50ba4002 Remove bsec from OG ESP32 to fix DRAM overflow 2026-01-19 12:12:14 -06:00
github-actions[bot]
5c401b8e34 Update protobufs (#9362)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-19 12:11:13 -06:00
Ben Meadors
c96ebf15fd Merge remote-tracking branch 'origin/master' into develop 2026-01-19 07:57:36 -06:00
github-actions[bot]
3e4239daf8 Upgrade trunk (#9330)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-19 07:22:27 -06:00
Ben Meadors
ab97c0126f Merge pull request #9355 from meshtastic/fix-bme
Untangle some BME680 ifdef spaghetti
2026-01-19 07:21:37 -06:00
github-actions[bot]
d34d694731 Update protobufs (#9360)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-19 06:21:01 -06:00
Jonathan Bennett
e545897d4e Untangle some BME680 ifdef spaghetti 2026-01-18 21:28:20 -06:00
renovate[bot]
caa6ec0e8a Update meshtastic/device-ui digest to 3480b73 (#9353)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 14:28:05 +11:00
Jason P
49accefd8b Don't Mute DMs just because we mute a channel (#9348)
* Don't Mute DMs just because we mute a channel

* Updated code to consolidate muting
2026-01-18 16:39:23 -05:00
Jason P
02f24b9015 Improve BaseUI Preset Change Flow (#9343)
* Reset Channel Number to 0 on Preset Change

* Add Channel Picker to LoRa Options

* Change Channel to Frequency Slot

* Catch comparison issue

* Reset override_frequency to ensure we correctly move to new Radio Preset

* CoPilot Suggestions
2026-01-18 16:38:46 -05:00
Catalin Patulea
33ae3777a3 toradio, fromradio OPTIONS handler: fix sending proper HTTP response. (#9322)
Before this (missing response):
$ curl -v -X OPTIONS http://meshtastic.local/api/v1/fromradio
* Host meshtastic.local:80 was resolved.
* IPv6: (none)
* IPv4: 192.168.0.19
*   Trying 192.168.0.19:80...
* Connected to meshtastic.local (192.168.0.19) port 80
* using HTTP/1.x
> OPTIONS /api/v1/fromradio HTTP/1.1
> Host: meshtastic.local
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
* Empty reply from server
* shutting down connection #0
curl: (52) Empty reply from server

After this (proper HTTP 204 response):
$ curl -v -X OPTIONS http://meshtastic.local/api/v1/fromradio
* Host meshtastic.local:80 was resolved.
* IPv6: (none)
* IPv4: 192.168.0.19
*   Trying 192.168.0.19:80...
* Connected to meshtastic.local (192.168.0.19) port 80
* using HTTP/1.x
> OPTIONS /api/v1/fromradio HTTP/1.1
> Host: meshtastic.local
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 204 OK
< Content-Type: application/x-protobuf
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET
< X-Protobuf-Schema: https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto
<
* Connection #0 to host meshtastic.local left intact

This is related to https://github.com/meshtastic/firmware/issues/5385.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-18 07:41:24 -06:00
Ted W.
021106dfe5 Add support for setting API port from the config file (#8435)
* Add support for setting API port from the config file

* Update PortduinoGlue.cpp

Fix typo in var identifier

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-17 15:23:16 -06:00
Jonathan Bennett
afbd9e2180 Filter BLE updates that don't change pairing status (#9333) 2026-01-16 13:52:04 -06:00
Tom Fifield
91dd39a651 Add sqlite depdendency (Cherry-picks from sfpp) (#9328)
* Add sqlite to build requires

* Add missed comma

* Add sqlite dev to more dockerfiles

* Alpine docker fix

* Add sqlite to build requires

* Add sqlite depdendency (Cherry-picks from sfpp)

Store and Forward Plus Plus requires sqlite to work.

This PR cherry picks the commits that added the dependency so that
this can be added, and reduce the amount of effort to review sfpp.

Authored-By: @jp-bennett

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-01-15 17:18:02 -06:00
Ben Meadors
64116cd0d3 Meshtastic OTA (moar) (#9327)
* Initial commit of combined BLE and WiFi OTA

* Incorporate ota_hash in AdminMessage protobuf

* OTA protobuf changes

* Trunk fmt

* Partition header check for OTA type

* Guards

* Guards

* Derp

* Missed one

---------

Co-authored-by: Jake-B <jake-b@users.noreply.github.com>
2026-01-15 14:36:36 -06:00
Ben Meadors
d493f5f171 Merge branch 'master' into develop 2026-01-15 08:26:09 -06:00
Ben Meadors
c8f0295a9c Cleanup 2026-01-15 08:25:38 -06:00
Ben Meadors
3911d5fe15 Fix build with high / low i2c address for OLED 2026-01-15 07:54:33 -06:00
Ben Meadors
59bdb9b097 Merge remote-tracking branch 'origin/develop' 2026-01-15 06:49:05 -06:00
Ben Meadors
b4157bd9bb Heltec V4 TFT metadata (#9325)
* Upgrade trunk (#9323)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>

* ICM20948 IMU sleep (#9324)

* Add v4-tft metadata

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
2026-01-15 06:48:41 -06:00
Austin
7e4e772113 Add EByte EoRa-Hub (#9169) 2026-01-15 06:24:10 -06:00
HarukiToreda
82735ca04e ICM20948 IMU sleep (#9324) 2026-01-15 06:23:40 -06:00
github-actions[bot]
e8fbdb4d84 Upgrade trunk (#9323)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-15 06:21:03 -06:00
Ben Meadors
a69e439dc1 Merge branch 'develop' 2026-01-15 06:19:49 -06:00
Ben Meadors
360579926c Trunk fmt 2026-01-15 06:19:18 -06:00
Ben Meadors
ff8316f895 Merge branch 'master' into develop 2026-01-15 06:18:43 -06:00
Jason P
6ee52ca7fa Node Actions Menu Overhaul (#9287)
* Start overhaul and clean up of the Node Actions menu

* Wired up commands - still a lot of work and testing

* Remove old favorites menu

* Remove addFavoritesMenu

* CoPilot to the rescue, wired up some function in both directions

* Clean up CoPilot actions

* Cross out Mute or Ignored in lists, add Save to NodeDB on changes

* Improve strikethrough for columns

* Correct menu wording and adjust vertical divider on Node List

* Code cleanup

* Testing unveiled some issues - fixed with these changes
2026-01-15 16:22:55 +11:00
Thomas Göttgens
233e6acc85 Preliminary Thinknode M4 Support (#8754)
* Preliminary Thinknode M4 Support

* oops

* Fix RF switch TX configuration

* trunk'd

* GPS fix for M4

* Battery handling and LED for M4

* Trunk

* Drop debug warnings

* Make Red LED notification

* Merge cleanup

* Make white LEDs flash during charge

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-01-14 21:36:53 -06:00
Lewis He
5f63f91cbc Added I2C scanner a check for the QMC6310N. (#9305)
* Added support for the new SSD1306 control panel.

* Added QMC6310N inspection to I2C scanner

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-14 20:54:57 -06:00
Ben Meadors
c0afe92a7f Meshtastic unified OTA (#9231)
* Initial commit of combined BLE and WiFi OTA

* Incorporate ota_hash in AdminMessage protobuf

* OTA protobuf changes

* Trunk fmt

---------

Co-authored-by: Jake-B <jake-b@users.noreply.github.com>
2026-01-14 20:54:31 -06:00
Mike Robbins
a6a80b067f Recover long_name, short_name from our own NodeDB entry if device.proto is unreadable (#9248)
* Recover long_name, short_name from our own NodeDB entry if device.proto is unreadable

* NodeDB::loadFromDisk: restore long/short name with memcpy and explicit null termination

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-15 11:02:09 +11:00
oscgonfer
64e95554bb Small fix in register size for SHT4X (#9309) 2026-01-15 11:00:42 +11:00
renovate[bot]
6537eeab03 Update pschatzmann_arduino-audio-driver to v0.2.0 (#9272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 11:00:24 +11:00
brad112358
fad315e99d Fix rotary encoder long press (#9039) 2026-01-15 10:59:24 +11:00
renovate[bot]
2d4f1b6bfe Update Adafruit BMP280 to v3 (#9307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 10:47:54 +11:00
oscgonfer
5a81403594 Move PMSA003I to separate class and update AQ telemetry (#7190) 2026-01-14 13:00:08 -06:00
Jonathan Bennett
5d7d1ae7a5 Adds Custom battery curve for thinknode m6 (#9313) 2026-01-14 11:40:35 -06:00
Manuel
940b3e236b fix GPS for T-Watch S3 plus (#9312)
* support T-Watch S3 Plus GPS

* HAS_GPS

* define BUTTON_PIN

* swap GPS pins, USB_MODE=1
2026-01-14 10:01:08 -06:00
vicliu
d1ae131502 T-Deck Pro: speed up eink force refresh (#9303) 2026-01-14 10:00:33 -06:00
Ben Meadors
552df4c88c Supress reboot banner in Reboot OTA 2026-01-14 07:06:40 -06:00
github-actions[bot]
cdbc8f48d4 Update protobufs (#9308)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-14 06:40:10 -06:00
Ben Meadors
919f214e8d Fix OTA partition name matching (#9302) 2026-01-14 06:33:01 -06:00
github-actions[bot]
89a83d00fa Upgrade trunk (#9306)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-14 06:26:31 -06:00
renovate[bot]
5610d4809c Update meshtastic/device-ui digest to 5a870c6 (#9301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-13 15:59:09 -06:00
github-actions[bot]
dae4061b06 Update protobufs (#9299)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-13 05:58:12 -06:00
Mike Robbins
e99853f660 SafeFile: use atomic rename-with-overwrite, rather than non-atomic delete-then-rename (#9296) 2026-01-13 05:57:04 -06:00
github-actions[bot]
3640e35a8b Upgrade trunk (#9297)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-13 05:50:40 -06:00
Ben Meadors
782ffdc5cd Merge branch 'develop' 2026-01-13 05:48:19 -06:00
Ben Meadors
6f36f39da9 Fix up T-Beam 1W HW_MODEL 2026-01-13 05:48:14 -06:00
Ben Meadors
ded4f57cb7 Partition name in manifest script (#9294)
* Fix up T-Beam 1W HW_MODEL

* Add part_name for bin files

* app0
2026-01-13 05:47:08 -06:00
HarukiToreda
3a0f3520d1 BaseUI: Autosave Messages (#9269)
* Autosave Messages

* fix

* Add logging, code cleanup, and add save on delete.

* We already save as part of delete messages, no need to do it again

* fix spelling errors

* Updating comment

---------

Co-authored-by: Jason P <applewiz@mac.com>
2026-01-12 19:40:44 -06:00
renovate[bot]
f73a944fcb Update ESP8266SAM to v1.1.0 (#9271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 19:39:58 -06:00
Ben Meadors
57a2000271 Merge remote-tracking branch 'origin/develop' 2026-01-12 18:54:41 -06:00
github-actions[bot]
daad424806 Update protobufs (#9291)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-12 18:45:46 -06:00
小林
30d6eb01e6 add support for uMesh Modules (#9259)
* add support for uMesh Modules

* Update lora-usb-umesh-1262.yaml

* Update lora-usb-umesh-1268.yaml
2026-01-12 16:13:09 -06:00
Ben Meadors
cbaa58894f Merge remote-tracking branch 'origin/master' into develop 2026-01-12 10:50:30 -06:00
Austin
1df26c2c5a Renovate: Ignore lovyangfx for elecrow-panel (#9279) 2026-01-12 10:47:35 -06:00
Ben Meadors
99d9191224 Merge remote-tracking branch 'origin/develop' 2026-01-12 10:47:08 -06:00
Ben Meadors
3b6ea95375 Enhance release notes generation by adding dependency update checks and improving new contributor detection 2026-01-12 10:22:22 -06:00
Austin
70fa657f36 Update RadioLib to v7.5.0 (#9281) 2026-01-12 09:53:31 -06:00
Martin Emrich
986d70db6a Pioarduino preparation (#9223)
* Resolve naming conflict of Syslog class with namespace

* do not include libpax headers if pax counter is excluded

* clean only top-level sdkconfigs, keep them in the variants directories

* Fix code formatting
2026-01-12 09:52:39 -06:00
github-actions[bot]
405c4f33af Upgrade trunk (#9270)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-12 08:43:27 -06:00
Austin
f4d7dab4ca EXCLUDE_AUDIO on (original) ESP32 (#9276)
iram is scarce, give it back!
2026-01-12 08:43:09 -06:00
Austin
723d8cac79 CI: tiny - include mt-ota in firmware zips (#9275) 2026-01-12 08:41:34 -06:00
Ford Jones
5ce821c775 Mute specific nodes (#9209)
* Regen protobufs

* Ensure mute state is set when node is ignored

* Added mechanism for toggling muted state

* Implement the ability to mute specific nodes

* Switch boolean value for bitmask

* Correctly toggle bitfield position 2 on-change to mute state

* Dont push submodule refs

* Log correct info

* Trunk fmt

* Update protobuf ref to master branch of base

* Update src/modules/ExternalNotificationModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Re-sync generated files

---------

Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-01-11 22:59:51 -06:00
Ben Meadors
d4045dff2c Remove INTERRUPT_ATTR from disableInterrupt methods on interfaces 2026-01-11 19:37:20 -06:00
Ben Meadors
e1605d126f Fix warning and exclude powermon by default 2026-01-11 19:35:41 -06:00
renovate[bot]
e9bdd2b031 Update ArduinoJson to v6.21.5 (#9265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-11 18:36:47 -06:00
renovate[bot]
f805aec867 Update GxEPD2 to v1.6.5 (#9266)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-11 18:36:37 -06:00
Jonathan Bennett
b6b129650a Extra pins (#9260)
* Maybe add working extra GPIO pins to portduino

* Fix typo and add config.yaml example for ExtraPins

* Write extra pins back out with -y flag
2026-01-11 18:27:06 -06:00
renovate[bot]
f38b4c1a98 chore(deps): update meshtastic-gxepd2 digest to a05c11c (#9264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-11 16:30:27 -06:00
renovate[bot]
c0f60ad664 chore(deps): update meshtastic/device-ui digest to 12f8cdd (#9263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-11 16:28:42 -06:00
Ben Meadors
3fabd57381 Merge pull request #9262 from meshtastic/develop
Develop to master
2026-01-11 16:27:58 -06:00
Ben Meadors
8cf8fbb8e0 Add unified OTA to manifest (#9261) 2026-01-11 16:20:47 -06:00
Austin
bafdeb4275 CI: Unified ESP32 OTA firmware + manifests (#9258) 2026-01-11 14:30:42 -06:00
Ben Meadors
8cb8540ef6 Add release notes generation and publishing workflow (#9255) 2026-01-11 12:08:39 -06:00
Ben Meadors
e8ddda6f0d Merge remote-tracking branch 'origin/master' into develop 2026-01-11 05:35:17 -06:00
github-actions[bot]
6f62748916 Update protobufs (#9254)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-11 05:34:56 -06:00
Ben Meadors
05bb43189e Merge remote-tracking branch 'origin/master' into develop 2026-01-10 19:42:02 -06:00
renovate[bot]
5dba5c82c0 Update INA226 to v0.6.6 (#9247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-10 19:39:45 -06:00
Ted W.
727b1b3e85 Add support for sevice start wrapper (#8676)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Austin <vidplace7@gmail.com>
2026-01-10 19:39:11 -06:00
Jonathan Bennett
c45c217743 Remove a strlcpy reference (#9249) 2026-01-10 19:36:40 -06:00
apo-mak
c520d3aae7 Gr language specific font (#8808)
* add Greek special font

* add Greek fonts with with proper Greek glyphs

* lint fix ( run trunk fmt)

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-10 12:43:17 -06:00
Max
acb6eb704b Update diy_promicro platformio.ini (#9245)
Thus PR updates extra_scripts definition.
Current env.extra_scripts don't triggers `extra_scripts/nrf52_extra.py`  and user have to convert hex to uf2 manually.
2026-01-10 07:43:42 -06:00
github-actions[bot]
04e755aa48 Update protobufs (#9242)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-10 06:15:55 -06:00
Ben Meadors
9e96b0ace8 Merge pull request #9236 from meshtastic/position-intervals-increase
Increase default position broadcast intervals and enforce minimums for the public default channels
2026-01-10 05:30:36 -06:00
Ben Meadors
be024d8d4e Add copilot-instructions.md for better contextual hints 2026-01-09 17:14:35 -06:00
Ben Meadors
b6512d3de1 Merge pull request #9237 from meshtastic/master
Master to dev merge
2026-01-09 14:56:54 -06:00
Manuel
214c76ce1b T-Watch S3 Plus GPS support (#9235)
* Upgrade trunk (#9229)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>

* support T-Watch S3 Plus GPS

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-09 11:48:27 -06:00
Jonathan Bennett
b002844aa0 Add Rak 6421 autoconf (#9010)
* Add Rak 6421 autoconf

* Minor memory safety hardening
2026-01-09 11:36:53 -06:00
Jason P
925381ef7b Fix TFT_MESH settings across setting and recalling (#9234)
* Fix TFT_MESH settings across setting and recalling

* Fix a word in documentation

* Update src/graphics/Screen.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/graphics/Screen.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-09 11:30:49 -05:00
Ben Meadors
ff8d6aa9c3 Increase default position broadcast intervals and enforce minimums for default channels 2026-01-09 08:35:37 -06:00
Jonathan Bennett
b12acba44f CH341 MAC address derivation from serial and product string (#9226)
Updated MAC address derivation logic to include product string in hashing.
2026-01-09 05:42:00 -06:00
github-actions[bot]
6b8e5e9d7b Upgrade trunk (#9229)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-09 05:41:19 -06:00
Jonathan Bennett
489de09375 Use correct name for ALT_BUTTON_PIN (#9225) 2026-01-08 19:19:17 -06:00
Jason P
390f0c8248 Screenless Devices want to mute too! (#9210)
* Screenless Devices want to mute too!
* Add logging for actions
* Gate to screenless devices only
* WisMesh Tag was missing HAS_SCREEN 0

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-08 16:44:05 -06:00
Jonathan Bennett
792e930e45 Trunk 2026-01-08 12:07:14 -06:00
Ben Meadors
5fab45c133 Merge branch 'master' into develop 2026-01-08 08:50:21 -06:00
Ben Meadors
c6e070461a Remove disabled gh action 2026-01-08 08:44:05 -06:00
Ben Meadors
f289b78061 Fix rotary regression and tighten up playBeep (#9221)
* Fix T-LoRA rotary regression and tighten up playBeep

* Derp

* Increase duration of chirp sound in playChirp
2026-01-08 08:17:45 -06:00
Heath Dutton🕴️
b4369b2730 Fix screen not sleeping due to power status updates (#9216)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-08 08:04:17 -06:00
github-actions[bot]
29d0d5e559 Upgrade trunk (#9219)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-08 05:42:13 -06:00
Wessel
6e110788fd 🔧 Fix LNA/PA power control for Heltec v4, wireless tracker v2 (#9029)
* Fix LNA/PA power control for Heltec v4, wireless tracker v2

* Stop using pin 46 as RF switch, just let DIO2 switch handle the RF path

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-08 05:42:01 -06:00
Heath Dutton🕴️
86326f294d Fix TSL2591 detection by adding command bit to register read (#9215) 2026-01-08 17:08:41 +11:00
Jonathan Bennett
fb7af18f4f Add needed support bits for the Meshstick (#9042)
* Add needed support bits for the Meshstick

* Portduino: Reduce allowed length by one byte to prevent possible overflow
2026-01-07 23:04:02 -06:00
Jonathan Bennett
4d303c95d1 Add list of text message packet IDs, and check for dupes (#9180)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-07 16:17:38 -06:00
Ben Meadors
1a6cbb5caa Migrate all of the Meshtastic API attributes into the ini as a source of truth (#9214)
* Migrate all of the Meshtastic API attributes into the ini as a source of truth

* Cleanup garbage coalescing

* Another spot

* We already account for inkhud and mui

* Consolidate

* Removed them

* Boogers

* Infer

* Copying manifest should always succeed

* Remove portduino guards

* Rename

* None
2026-01-07 15:25:38 -06:00
github-actions[bot]
a6c4683ddc Upgrade trunk (#9208)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-07 05:50:17 -06:00
Jason P
70f909d718 Fix Function + M in code (#9200) 2026-01-06 16:02:41 -06:00
Jason P
9c1d55c844 Add option to Mute/Unmute Channel to BaseUI (#9194) 2026-01-06 15:26:45 -06:00
Bob Reese
ba9d0e6fa3 RadioInterface::getRetransmissionMsec now handles encrypted packets correctly (#9184) 2026-01-06 12:34:48 -06:00
Lewis He
da11cc739d Added support for the new SSD1306 control panel. (#9192) 2026-01-06 07:25:31 -06:00
santosvivos
9f5170a0bc Add LilyGO T-Beam 1W support (#8967)
* Add LilyGO T-Beam 1W support
- Add board definition and variant files for ESP32-S3 based T-Beam 1W
- Add RF95_FAN_EN support to SX126xInterface for PA cooling fan
- Add SX126X_PA_RAMP_US for configurable PA ramp time (800us for 1W PA)
- Configure RF switch: DIO2 for PA, GPIO 21 for LNA control

* Set TX_GAIN_LORA to 10dB per PR feedback (offset for 1W PA)

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-06 06:23:28 -06:00
github-actions[bot]
594f27c3ff Upgrade trunk (#9183)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-06 05:27:14 -06:00
Ben Meadors
e648e26c17 Merge pull request #9191 from meshtastic/bme-native
BME680 on Native
2026-01-05 20:55:44 -06:00
Jonathan Bennett
1669a027e6 BME680 on Native
Co-authored-by: juanjin-dev <juanjin.dev@gmail.com>
2026-01-05 19:33:41 -06:00
Ben Meadors
105d657359 Merge pull request #9189 from vidplace7/actions-feature-branches 2026-01-05 16:52:57 -06:00
Austin Lane
37ab800500 Actions: CI for feature/ branches
...and pioarduino
2026-01-05 17:44:07 -05:00
Sergey Galkin
0c553c40d4 Fix zero in sp02 and Heart Rate on screen (#9174)
Fixed zero in sp02 and Heart Rate in HealthTelemetry screen
2026-01-05 07:57:49 +11:00
Iris
17b075a11c added tcxo definition to mesh-tab (#8604) 2026-01-04 05:57:50 -06:00
renovate[bot]
15f5b35859 chore(deps): update meshtastic/device-ui digest to 272defc (#9166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-04 05:22:50 -06:00
Valentyn Diduryk
25bdefecb2 Fixed shouldFilterReceived function to check prev relay accoding to the function definition (#9168) 2026-01-04 05:22:26 -06:00
Jorropo
beb268ff25 Revert "add a .clang-format file (#9154)" (#9172)
I thought git would be smart enough to understand all the whitespace changes but even with all the flags I know to make it ignore theses it still blows up if there are identical changes on both sides.

I have a solution but it require creating a new commit at the merge base for each conflicting PR and merging it into develop.

I don't think blowing up all PRs is worth for now, maybe if we can coordinate this for V3 let's say.

This reverts commit 0d11331d18.
2026-01-04 05:15:53 -06:00
Jorropo
0d11331d18 add a .clang-format file (#9154) 2026-01-03 14:19:24 -06:00
renovate[bot]
21ca25404a chore(deps): update dorny/test-reporter action to v2.5.0 (#9167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-03 12:00:38 -06:00
Tom Fifield
abab6ce815 Fix link formatting in welcome message (#9163) 2026-01-03 06:00:23 -06:00
brad112358
52907e4c44 Faster rotary encoder events (#9146)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-02 20:22:40 -06:00
Ben Meadors
3a90781e1b Add support for LilyGo T-Echo Plus (#9149)
* Add t-echo plus support

* T-Echo plus hw model

* Cruft

* Fix

* Added InkHUD style touch backlight control
2026-01-02 20:14:09 -06:00
Jonathan Bennett
f63dadd19e Add custom coding rate configuration for LoRa (#9155) 2026-01-02 16:23:01 -06:00
Ben Meadors
9313d465f6 I think this is supposed to be extra 2026-01-02 15:58:54 -06:00
Jason P
004746683e Refactored some of the system menus to the new DRY method (Redux) (#9152)
* Refactored some of the system menus to the new DRY method

* Fix menu name from Position to GPS
2026-01-02 15:34:25 -06:00
github-actions[bot]
1e914140ca Update protobufs (#9148)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-02 08:35:52 -06:00
github-actions[bot]
caceaf424a Automated version bumps (#9030)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-02 06:56:02 -06:00
Ben Meadors
75144d2028 Update security policy to reflect new stage 2026-01-02 06:42:28 -06:00
github-actions[bot]
b5e952b008 Upgrade trunk (#9128)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-01 19:17:55 -06:00
Ben Meadors
27b522b55a Merge branch 'master' into develop 2026-01-01 18:25:18 -06:00
renovate[bot]
11b5f1a4fe chore(deps): update dorny/test-reporter action to v2.4.0 (#9135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 18:04:13 -06:00
renovate[bot]
f9c9350f45 chore(deps): update meshtastic/device-ui digest to a8e2f94 (#9140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 18:03:27 -06:00
Jonathan Bennett
a5b2d4a9d5 Add null check for p_encrypted before MQTT publish (#9136)
* Add null check for p_encrypted before MQTT publish

A user on BayMesh observed a strange crash in MQTT::onSend that seemed to be a null pointer dereference of this value.

* Trunk
2026-01-01 13:53:36 -06:00
Ben Meadors
7fb95841e4 Apparently I marked board level extra on the wrong tbeam target 2026-01-01 08:25:33 -06:00
renovate[bot]
eaab8f04b5 chore(deps): update meshtastic/device-ui digest to 940ba85 (#9129)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 10:58:56 +11:00
Eric Severance
9058ccecf9 Calculate hops correctly even when hop_start==0 (#9120)
* Calculate hops correctly even when hop_start==0.

* Use the same type (int8_t) in the loop, avoiding signed/unsigned mismatches.

* Clarify defaultIfUnknown is returned for encrypted packets.
2025-12-30 19:03:51 -06:00
Ben Meadors
1b83501ee2 Revert "Upgrade all esp32 targets to NimBLE 2.X (#9003)" (#9125)
This reverts commit 40f1f91c0d.
2025-12-30 17:23:50 -06:00
github-actions[bot]
ac571d5dd2 Upgrade trunk (#9121)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-30 07:10:36 -06:00
renovate[bot]
ef30fd850d Update meshtastic/device-ui digest to 7656d49 (#9111)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 19:09:44 +01:00
renovate[bot]
b9a0015149 chore(deps): update meshtastic/device-ui digest to d234bd9 (#9108)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 06:50:12 -06:00
github-actions[bot]
9673cfb0b2 Upgrade trunk (#9106)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-29 06:03:03 -06:00
renovate[bot]
757f7b68d6 Update meshtastic/device-ui digest to caff403 (#9104)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 13:35:31 +11:00
Jason P
5510dae8d3 Implement HAS_PHYSICAL_KEYBOARD for devices with physical keyboards (#9071)
- Implement HAS_PHYSICAL_KEYBOARD for devices with physical keyboards
- Add HAS_PHYSICAL_KEYBOARD to variant.h for:
  - TDeck
  - TLora Pager
  - TDeck Pro
2025-12-27 06:53:55 -06:00
Tom
52fd362720 Fix gps pin defs for various NRF variants. (#9034)
* fix on nrf52_promicro

* try fix for GPS issue

* fix GPS pin assignment in variant.h

* cleared up some comments and confirmed pinouts from schematics

---------

Co-authored-by: macvenez <macvenez@gmail.com>
2025-12-27 06:50:07 -06:00
github-actions[bot]
33e1f58f6e Upgrade trunk (#9076)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-26 17:45:57 -06:00
Jonathan Bennett
9dc7ef612e In autoconf, don't probe Wire unless i2c device is set (#9081)
Found another bit of code that crashes my desktop, by probing the wrong i2c bus.
2025-12-26 14:33:17 -06:00
github-actions[bot]
b2c82bdc41 Upgrade trunk (#9072)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-25 06:34:38 -06:00
Jonathan Bennett
54a928f47f M6 shutdown and LEDs work (#9065)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-12-24 07:48:14 -06:00
github-actions[bot]
33f18659c8 Upgrade trunk (#9067)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-24 05:20:22 -06:00
github-actions[bot]
3a7093a973 Upgrade trunk (#9047)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-23 18:55:54 -06:00
renovate[bot]
a4f6f4515a Update meshtastic-esp8266-oled-ssd1306 digest to b34c681 (#9062)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-23 18:55:37 -06:00
Jonathan Bennett
d609d05698 In statusLEDModule, also detect isCharging (#9050) 2025-12-23 07:48:55 -06:00
Ben Meadors
83c6161ac6 Revert "Automated version bumps (#9025)"
This reverts commit 1021d967da.
2025-12-20 14:10:02 -06:00
Ben Meadors
d93d68d31e Fix -ota.zip in manifest and build output 2025-12-20 14:09:05 -06:00
329 changed files with 11105 additions and 2066 deletions

View File

@@ -20,7 +20,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
RUN apt-get update && apt-get install --no-install-recommends -y \
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
libusb-1.0-0-dev libssl-dev pkg-config && \
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
pip install --no-cache-dir -U \
platformio==6.1.16 \

314
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,314 @@
# Meshtastic Firmware - Copilot Instructions
This document provides context and guidelines for AI assistants working with the Meshtastic firmware codebase.
## Project Overview
Meshtastic is an open-source LoRa mesh networking project for long-range, low-power communication without relying on internet or cellular infrastructure. The firmware enables text messaging, location sharing, and telemetry over a decentralized mesh network.
### Supported Hardware Platforms
- **ESP32** (ESP32, ESP32-S3, ESP32-C3) - Most common platform
- **nRF52** (nRF52840, nRF52833) - Low power Nordic chips
- **RP2040/RP2350** - Raspberry Pi Pico variants
- **STM32WL** - STM32 with integrated LoRa
- **Linux/Portduino** - Native Linux builds (Raspberry Pi, etc.)
### Supported Radio Chips
- **SX1262/SX1268** - Sub-GHz LoRa (868/915 MHz regions)
- **SX1280** - 2.4 GHz LoRa
- **LR1110/LR1120/LR1121** - Wideband radios (sub-GHz and 2.4 GHz capable, but not simultaneously)
- **RF95** - Legacy RFM95 modules
- **LLCC68** - Low-cost LoRa
### MQTT Integration
MQTT provides a bridge between Meshtastic mesh networks and the internet, enabling nodes with network connectivity to share messages with remote meshes or external services.
#### Key Components
- **`src/mqtt/MQTT.cpp`** - Main MQTT client singleton, handles connection and message routing
- **`src/mqtt/ServiceEnvelope.cpp`** - Protobuf wrapper for mesh packets sent over MQTT
- **`moduleConfig.mqtt`** - MQTT module configuration
#### MQTT Topic Structure
Messages are published/subscribed using a hierarchical topic format:
```
{root}/{channel_id}/{gateway_id}
```
- `root` - Configurable prefix (default: `msh`)
- `channel_id` - Channel name/identifier
- `gateway_id` - Node ID of the publishing gateway
#### Configuration Defaults (from `Default.h`)
```cpp
#define default_mqtt_address "mqtt.meshtastic.org"
#define default_mqtt_username "meshdev"
#define default_mqtt_password "large4cats"
#define default_mqtt_root "msh"
#define default_mqtt_encryption_enabled true
#define default_mqtt_tls_enabled false
```
#### Key Concepts
- **Uplink** - Mesh packets sent TO the MQTT broker (controlled by `uplink_enabled` per channel)
- **Downlink** - MQTT messages received and injected INTO the mesh (controlled by `downlink_enabled` per channel)
- **Encryption** - When `encryption_enabled` is true, only encrypted packets are sent; plaintext JSON is disabled
- **ServiceEnvelope** - Protobuf wrapper containing packet + channel_id + gateway_id for routing
- **JSON Support** - Optional JSON encoding for integration with external systems (disabled on nRF52 by default)
#### PKI Messages
PKI (Public Key Infrastructure) messages have special handling:
- Accepted on a special "PKI" channel
- Allow encrypted DMs between nodes that discovered each other on downlink-enabled channels
## Project Structure
```
firmware/
├── src/ # Main source code
│ ├── main.cpp # Application entry point
│ ├── mesh/ # Core mesh networking
│ │ ├── NodeDB.* # Node database management
│ │ ├── Router.* # Packet routing
│ │ ├── Channels.* # Channel management
│ │ ├── *Interface.* # Radio interface implementations
│ │ └── generated/ # Protobuf generated code
│ ├── modules/ # Feature modules (Position, Telemetry, etc.)
│ ├── gps/ # GPS handling
│ ├── graphics/ # Display drivers and UI
│ ├── platform/ # Platform-specific code
│ ├── input/ # Input device handling
│ └── concurrency/ # Threading utilities
├── variants/ # Hardware variant definitions
│ ├── esp32/ # ESP32 variants
│ ├── esp32s3/ # ESP32-S3 variants
│ ├── nrf52/ # nRF52 variants
│ └── rp2xxx/ # RP2040/RP2350 variants
├── protobufs/ # Protocol buffer definitions
├── boards/ # Custom PlatformIO board definitions
└── bin/ # Build and utility scripts
```
## Coding Conventions
### General Style
- Follow existing code style - run `trunk fmt` before commits
- Prefer `LOG_DEBUG`, `LOG_INFO`, `LOG_WARN`, `LOG_ERROR` for logging
- Use `assert()` for invariants that should never fail
### Naming Conventions
- Classes: `PascalCase` (e.g., `PositionModule`, `NodeDB`)
- Functions/Methods: `camelCase` (e.g., `sendOurPosition`, `getNodeNum`)
- Constants/Defines: `UPPER_SNAKE_CASE` (e.g., `MAX_INTERVAL`, `ONE_DAY`)
- Member variables: `camelCase` (e.g., `lastGpsSend`, `nodeDB`)
- Config defines: `USERPREFS_*` for user-configurable options
### Key Patterns
#### Module System
Modules inherit from `MeshModule` or `ProtobufModule<T>` and implement:
- `handleReceivedProtobuf()` - Process incoming packets
- `allocReply()` - Generate response packets
- `runOnce()` - Periodic task execution (returns next run interval in ms)
```cpp
class MyModule : public ProtobufModule<meshtastic_MyMessage>
{
protected:
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_MyMessage *msg) override;
virtual int32_t runOnce() override;
};
```
#### Configuration Access
- `config.*` - Device configuration (LoRa, position, power, etc.)
- `moduleConfig.*` - Module-specific configuration
- `channels.*` - Channel configuration and management
#### Default Values
Use the `Default` class helpers in `src/mesh/Default.h`:
- `Default::getConfiguredOrDefaultMs(configured, default)` - Returns ms, using default if configured is 0
- `Default::getConfiguredOrMinimumValue(configured, min)` - Enforces minimum values
- `Default::getConfiguredOrDefaultMsScaled(configured, default, numNodes)` - Scales based on network size
#### Thread Safety
- Use `concurrency::Lock` for mutex protection
- Radio SPI access uses `SPILock`
- Prefer `OSThread` for background tasks
### Hardware Variants
Each hardware variant has:
- `variant.h` - Pin definitions and hardware capabilities
- `platformio.ini` - Build configuration
- Optional: `pins_arduino.h`, `rfswitch.h`
Key defines in variant.h:
```cpp
#define USE_SX1262 // Radio chip selection
#define HAS_GPS 1 // Hardware capabilities
#define LORA_CS 36 // Pin assignments
#define SX126X_DIO1 14 // Radio-specific pins
```
### Protobuf Messages
- Defined in `protobufs/meshtastic/*.proto`
- Generated code in `src/mesh/generated/`
- Regenerate with `bin/regen-protos.sh`
- Message types prefixed with `meshtastic_`
### Conditional Compilation
```cpp
#if !MESHTASTIC_EXCLUDE_GPS // Feature exclusion
#ifdef ARCH_ESP32 // Architecture-specific
#if defined(USE_SX1262) // Radio-specific
#ifdef HAS_SCREEN // Hardware capability
#if USERPREFS_EVENT_MODE // User preferences
```
## Build System
Uses **PlatformIO** with custom scripts:
- `bin/platformio-pre.py` - Pre-build script
- `bin/platformio-custom.py` - Custom build logic
Build commands:
```bash
pio run -e tbeam # Build specific target
pio run -e tbeam -t upload # Build and upload
pio run -e native # Build native/Linux version
```
## Common Tasks
### Adding a New Module
1. Create `src/modules/MyModule.cpp` and `.h`
2. Inherit from appropriate base class
3. Register in `src/modules/Modules.cpp`
4. Add protobuf messages if needed in `protobufs/`
### Adding a New Hardware Variant
1. Create directory under `variants/<arch>/<name>/`
2. Add `variant.h` with pin definitions
3. Add `platformio.ini` with build config
4. Reference common configs with `extends`
### Modifying Configuration Defaults
- Check `src/mesh/Default.h` for default value defines
- Check `src/mesh/NodeDB.cpp` for initialization logic
- Consider `isDefaultChannel()` checks for public channel restrictions
## Important Considerations
### Traffic Management
The mesh network has limited bandwidth. When modifying broadcast intervals:
- Respect minimum intervals on default/public channels
- Use `Default::getConfiguredOrMinimumValue()` to enforce minimums
- Consider `numOnlineNodes` scaling for congestion control
### Power Management
Many devices are battery-powered:
- Use `IF_ROUTER(routerVal, normalVal)` for role-based defaults
- Check `config.power.is_power_saving` for power-saving modes
- Implement proper `sleep()` methods in radio interfaces
### Channel Security
- `channels.isDefaultChannel(index)` - Check if using default/public settings
- Default channels get stricter rate limits to prevent abuse
- Private channels may have relaxed limits
## GitHub Actions CI/CD
The project uses GitHub Actions extensively for CI/CD. Key workflows are in `.github/workflows/`:
### Core CI Workflows
- **`main_matrix.yml`** - Main CI pipeline, runs on push to `master`/`develop` and PRs
- Uses `bin/generate_ci_matrix.py` to dynamically generate build targets
- Builds all supported hardware variants
- PRs build a subset (`--level pr`) for faster feedback
- **`trunk_check.yml`** - Code quality checks on PRs
- Runs Trunk.io for linting and formatting
- Must pass before merge
- **`tests.yml`** - End-to-end and hardware tests
- Runs daily on schedule
- Includes native tests and hardware-in-the-loop testing
- **`test_native.yml`** - Native platform unit tests
- Runs `pio test -e native`
### Release Workflows
- **`release_channels.yml`** - Triggered on GitHub release publish
- Builds Docker images
- Packages for PPA (Ubuntu), OBS (openSUSE), and COPR (Fedora)
- Handles Alpha/Beta/Stable release channels
- **`nightly.yml`** - Nightly builds from develop branch
- **`docker_build.yml`** / **`docker_manifest.yml`** - Docker image builds
### Build Matrix Generation
The CI uses `bin/generate_ci_matrix.py` to dynamically select which targets to build:
```bash
# Generate full build matrix
./bin/generate_ci_matrix.py all
# Generate PR-level matrix (subset for faster builds)
./bin/generate_ci_matrix.py all --level pr
```
Variants can specify their support level in `platformio.ini`:
- `custom_meshtastic_support_level = 1` - Actively supported, built on every PR
- `custom_meshtastic_support_level = 2` - Supported, built on merge to main branches
- `board_level = extra` - Extra builds, only on full releases
### Running Workflows Locally
Most workflows can be triggered manually via `workflow_dispatch` for testing.
## Testing
- Unit tests in `test/` directory
- Run with `pio test -e native`
- Use `bin/test-simulator.sh` for simulation testing
## Resources
- [Documentation](https://meshtastic.org/docs/)

View File

@@ -29,23 +29,6 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Set OTA firmware source and target
if: startsWith(inputs.platform, 'esp32')
id: ota_dir
env:
PIO_PLATFORM: ${{ inputs.platform }}
run: |
if [ "$PIO_PLATFORM" = "esp32s3" ]; then
echo "src=firmware-s3.bin" >> $GITHUB_OUTPUT
echo "tgt=release/bleota-s3.bin" >> $GITHUB_OUTPUT
elif [ "$PIO_PLATFORM" = "esp32c3" ] || [ "$PIO_PLATFORM" = "esp32c6" ]; then
echo "src=firmware-c3.bin" >> $GITHUB_OUTPUT
echo "tgt=release/bleota-c3.bin" >> $GITHUB_OUTPUT
elif [ "$PIO_PLATFORM" = "esp32" ]; then
echo "src=firmware.bin" >> $GITHUB_OUTPUT
echo "tgt=release/bleota.bin" >> $GITHUB_OUTPUT
fi
- name: Build ${{ inputs.platform }}
id: build
uses: meshtastic/gh-action-firmware@main
@@ -53,8 +36,66 @@ jobs:
pio_platform: ${{ inputs.platform }}
pio_env: ${{ inputs.pio_env }}
pio_target: build
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
- name: ESP32 - Download Unified OTA firmware
# Currently only esp32 and esp32s3 use the unified ota
if: inputs.platform == 'esp32' || inputs.platform == 'esp32s3'
id: dl-ota-unified
env:
PIO_PLATFORM: ${{ inputs.platform }}
PIO_ENV: ${{ inputs.pio_env }}
OTA_URL: https://github.com/meshtastic/esp32-unified-ota/releases/latest/download/mt-${{ inputs.platform }}-ota.bin
working-directory: release
run: |
curl -L -o "mt-$PIO_PLATFORM-ota.bin" $OTA_URL
- name: ESP32-C* - Download BLE-Only OTA firmware
if: inputs.platform == 'esp32c3' || inputs.platform == 'esp32c6'
id: dl-ota-ble
env:
PIO_ENV: ${{ inputs.pio_env }}
OTA_URL: https://github.com/meshtastic/firmware-ota/releases/latest/download/firmware-c3.bin
working-directory: release
run: |
curl -L -o bleota-c3.bin $OTA_URL
- name: Update manifest with OTA file
if: inputs.platform == 'esp32' || inputs.platform == 'esp32s3' || inputs.platform == 'esp32c3' || inputs.platform == 'esp32c6'
working-directory: release
env:
PIO_PLATFORM: ${{ inputs.platform }}
run: |
# Determine OTA filename based on platform
if [[ "$PIO_PLATFORM" == "esp32" || "$PIO_PLATFORM" == "esp32s3" ]]; then
OTA_FILE="mt-${PIO_PLATFORM}-ota.bin"
else
OTA_FILE="bleota-c3.bin"
fi
# Check if OTA file exists
if [[ ! -f "$OTA_FILE" ]]; then
echo "OTA file $OTA_FILE not found, skipping manifest update"
exit 0
fi
# Calculate MD5 and size
if command -v md5sum &> /dev/null; then
OTA_MD5=$(md5sum "$OTA_FILE" | cut -d' ' -f1)
else
OTA_MD5=$(md5 -q "$OTA_FILE")
fi
OTA_SIZE=$(stat -f%z "$OTA_FILE" 2>/dev/null || stat -c%s "$OTA_FILE")
# Find and update manifest file
for manifest in firmware-*.mt.json; do
if [[ -f "$manifest" ]]; then
echo "Updating $manifest with $OTA_FILE (md5: $OTA_MD5, size: $OTA_SIZE)"
# Add OTA entry to files array if not already present
jq --arg name "$OTA_FILE" --arg md5 "$OTA_MD5" --argjson bytes "$OTA_SIZE" --arg part "app1" \
'if .files | map(select(.name == $name)) | length == 0 then .files += [{"name": $name, "md5": $md5, "bytes": $bytes, "part_name": $part}] else . end' \
"$manifest" > "${manifest}.tmp" && mv "${manifest}.tmp" "$manifest"
fi
done
- name: Job summary
env:

View File

@@ -22,7 +22,7 @@ jobs:
### @{fc-author}, Welcome to Meshtastic! :wave:
Thanks for opening your first issue. If it's helpful, an easy way
to get logs is the "Open Serial Monitor" button on the (Web Flasher](https://flasher.meshtastic.org).
to get logs is the "Open Serial Monitor" button on the [Web Flasher](https://flasher.meshtastic.org).
If you have ideas for features, note that we often debate big ideas
in the [discussions tab](https://github.com/meshtastic/firmware/discussions/categories/ideas)

View File

@@ -8,7 +8,9 @@ on:
branches:
- master
- develop
- pioarduino # Remove when merged // use `feature/` in the future.
- event/*
- feature/*
paths-ignore:
- "**.md"
- version.properties
@@ -18,7 +20,9 @@ on:
branches:
- master
- develop
- pioarduino # Remove when merged // use `feature/` in the future.
- event/*
- feature/*
paths-ignore:
- "**.md"
#- "**.yml"
@@ -197,6 +201,7 @@ jobs:
./device-*.bat
./littlefs-*.bin
./bleota*bin
./mt-*-ota.bin
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
@@ -289,6 +294,24 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Generate release notes
id: release_notes
run: |
chmod +x ./bin/generate_release_notes.py
NOTES=$(./bin/generate_release_notes.py ${{ needs.version.outputs.long }})
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create release
uses: softprops/action-gh-release@v2
@@ -298,8 +321,7 @@ jobs:
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
body: ${{ steps.release_notes.outputs.notes }}
- name: Download source deb
uses: actions/download-artifact@v7
@@ -423,6 +445,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v6
@@ -442,6 +466,13 @@ jobs:
pattern: manifest-${{ needs.version.outputs.long }}
path: ./publish
- name: Generate release notes
run: |
chmod +x ./bin/generate_release_notes.py
./bin/generate_release_notes.py ${{ needs.version.outputs.long }} > ./publish/release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish firmware to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
env:

View File

@@ -48,6 +48,37 @@ jobs:
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
secrets: inherit
publish-release-notes:
if: github.event.action == 'published'
runs-on: ubuntu-latest
steps:
- name: Get release version
id: version
run: |
# Extract version from tag (e.g., v2.7.15.567b8ea -> 2.7.15.567b8ea)
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Get release notes
run: |
mkdir -p ./publish
gh release view ${{ github.event.release.tag_name }} --json body --jq '.body' > ./publish/release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish release notes to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
destination_dir: firmware-${{ steps.version.outputs.version }}
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com
commit_message: Release notes for ${{ steps.version.outputs.version }}
enable_jekyll: true
# Create a PR to bump version when a release is Published
bump-version:
if: github.event.action == 'published'

View File

@@ -143,7 +143,7 @@ jobs:
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.3.0
uses: dorny/test-reporter@v2.5.0
with:
name: PlatformIO Tests
path: testreport.xml

View File

@@ -1,51 +0,0 @@
name: Run Trunk Fmt on PR Comment
on:
issue_comment:
types: [created]
permissions: read-all
jobs:
trunk-fmt:
if: github.event.issue.pull_request != null && contains(github.event.comment.body, 'trunk fmt')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Install trunk
run: curl https://get.trunk.io -fsSL | bash
- name: Run Trunk Fmt
run: trunk fmt
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Commit and push changes
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "Add firmware version ${{ steps.version.outputs.long }}"
git push
- name: Comment on PR
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '`trunk fmt` has been run on this PR.'
})

2
.gitignore vendored
View File

@@ -48,5 +48,5 @@ arduino-lib-builder*
dependencies.lock
idf_component.yml
CMakeLists.txt
sdkconfig.*
/sdkconfig.*
.dummy/*

View File

@@ -8,25 +8,25 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.495
- renovate@42.66.8
- prettier@3.7.4
- trufflehog@3.92.4
- yamllint@1.37.1
- bandit@1.9.2
- checkov@3.2.497
- renovate@42.84.2
- prettier@3.8.0
- trufflehog@3.92.5
- yamllint@1.38.0
- bandit@1.9.3
- trivy@0.68.2
- taplo@0.10.0
- ruff@0.14.10
- ruff@0.14.13
- isort@7.0.0
- markdownlint@0.47.0
- oxipng@10.0.0
- svgo@4.0.0
- actionlint@1.7.9
- actionlint@1.7.10
- flake8@7.3.0
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- black@25.12.0
- black@26.1.0
- git-diff-check
- gitleaks@8.30.0
- clang-format@16.0.3

View File

@@ -14,7 +14,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
curl wget g++ zip git ca-certificates pkg-config \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware

View File

@@ -4,8 +4,8 @@
| Firmware Version | Supported |
| ---------------- | ------------------ |
| 2.6.x | :white_check_mark: |
| <= 2.5.x | :x: |
| 2.7.x | :white_check_mark: |
| <= 2.6.x | :x: |
## Reporting a Vulnerability

View File

@@ -11,7 +11,7 @@ RUN apk --no-cache add \
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
libgpiod-dev yaml-cpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
libx11-dev libinput-dev libxkbcommon-dev \
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev \
&& rm -rf /var/cache/apk/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware

View File

@@ -38,4 +38,4 @@ cp bin/device-install.* $OUTDIR/
cp bin/device-update.* $OUTDIR/
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true

View File

@@ -49,4 +49,4 @@ if (echo $1 | grep -q "rak4631"); then
fi
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true

View File

@@ -30,4 +30,4 @@ echo "Copying uf2 file"
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true

View File

@@ -30,4 +30,4 @@ echo "Copying STM32 bin file"
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true

View File

@@ -105,6 +105,8 @@ Lora:
GPS:
# SerialPath: /dev/ttyS0
# ExtraPins:
# - 22
### Specify I2C device, or leave blank for none

View File

@@ -0,0 +1,11 @@
Lora:
### RAK13300in Slot 1
Module: sx1262
IRQ: 22 #IO6
Reset: 16 # IO4
Busy: 24 # IO5
# Ant_sw: 13 # IO3
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0

View File

@@ -0,0 +1,14 @@
Lora:
Module: sx1262
CS: 0
IRQ: 6
Reset: 2
Busy: 4
RXen: 1
DIO2_AS_RF_SWITCH: true
spidev: ch341
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
USB_PID: 0x5512
USB_VID: 0x1A86
SX126X_MAX_POWER: 22

View File

@@ -0,0 +1,15 @@
Lora:
Module: sx1262
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 30
# Reduce output power to improve EMI

View File

@@ -0,0 +1,15 @@
Lora:
Module: sx1268
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 30
# Reduce output power to improve EMI

View File

@@ -32,6 +32,19 @@ if ! command -v jq >/dev/null 2>&1; then
exit 1
fi
# esptool v5 supports commands with dashes and deprecates commands with
# underscores. Prior versions only support commands with underscores
if ${ESPTOOL_CMD} | grep --quiet write-flash
then
ESPTOOL_WRITE_FLASH=write-flash
ESPTOOL_ERASE_FLASH=erase-flash
ESPTOOL_READ_FLASH_STATUS=read-flash-status
else
ESPTOOL_WRITE_FLASH=write_flash
ESPTOOL_ERASE_FLASH=erase_flash
ESPTOOL_READ_FLASH_STATUS=read_flash_status
fi
set -e
# Usage info
@@ -83,8 +96,8 @@ while [ $# -gt 0 ]; do
done
if [[ $BPS_RESET == true ]]; then
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
exit 0
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS}
exit 0
fi
[ -z "$FILENAME" ] && [ -n "$1" ] && {
@@ -144,12 +157,12 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then
fi
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
$ESPTOOL_CMD erase-flash
$ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
$ESPTOOL_CMD ${ESPTOOL_ERASE_FLASH}
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $FIRMWARE_OFFSET "${FILENAME}"
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OTA_OFFSET "${OTAFILE}"
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OFFSET "${SPIFFSFILE}"
else
show_help

View File

@@ -20,6 +20,17 @@ else
exit 1
fi
# esptool v5 supports commands with dashes and deprecates commands with
# underscores. Prior versions only support commands with underscores
if ${ESPTOOL_CMD} | grep --quiet write-flash
then
ESPTOOL_WRITE_FLASH=write-flash
ESPTOOL_READ_FLASH_STATUS=read-flash-status
else
ESPTOOL_WRITE_FLASH=write_flash
ESPTOOL_READ_FLASH_STATUS=read_flash_status
fi
# Usage info
show_help() {
cat << EOF
@@ -69,7 +80,7 @@ done
shift "$((OPTIND-1))"
if [ "$CHANGE_MODE" = true ]; then
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS}
exit 0
fi
@@ -80,7 +91,7 @@ fi
if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then
echo "Trying to flash update ${FILENAME}"
$ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
$ESPTOOL_CMD --baud $FLASH_BAUD ${ESPTOOL_WRITE_FLASH} $UPDATE_OFFSET "${FILENAME}"
else
show_help
echo "Invalid file: ${FILENAME}"

355
bin/generate_release_notes.py Executable file
View File

@@ -0,0 +1,355 @@
#!/usr/bin/env python3
"""
Generate release notes from merged PRs on develop and master branches.
Categorizes PRs into Enhancements and Bug Fixes/Maintenance sections.
"""
import subprocess
import re
import json
import sys
from datetime import datetime
def get_last_release_tag():
"""Get the most recent release tag."""
result = subprocess.run(
["git", "describe", "--tags", "--abbrev=0"],
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
def get_tag_date(tag):
"""Get the commit date (ISO 8601) of the tag."""
result = subprocess.run(
["git", "show", "-s", "--format=%cI", tag],
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
def get_merged_prs_since_tag(tag, branch):
"""Get all merged PRs since the given tag on the specified branch."""
# Get commits since tag on the branch - look for PR numbers in parentheses
result = subprocess.run(
[
"git",
"log",
f"{tag}..origin/{branch}",
"--oneline",
],
capture_output=True,
text=True,
)
prs = []
seen_pr_numbers = set()
for line in result.stdout.strip().split("\n"):
if not line:
continue
# Extract PR number from commit message - format: "Title (#1234)"
pr_match = re.search(r"\(#(\d+)\)", line)
if pr_match:
pr_number = pr_match.group(1)
if pr_number not in seen_pr_numbers:
seen_pr_numbers.add(pr_number)
prs.append(pr_number)
return prs
def get_pr_details(pr_number):
"""Get PR details from GitHub API via gh CLI."""
try:
result = subprocess.run(
[
"gh",
"pr",
"view",
pr_number,
"--json",
"title,author,labels,url",
],
capture_output=True,
text=True,
check=True,
)
return json.loads(result.stdout)
except subprocess.CalledProcessError:
return None
def should_exclude_pr(pr_details):
"""Check if PR should be excluded from release notes."""
if not pr_details:
return True
title = pr_details.get("title", "").lower()
# Exclude trunk update PRs
if "upgrade trunk" in title or "update trunk" in title or "trunk update" in title:
return True
# Exclude protobuf update PRs
if "update protobufs" in title or "update protobuf" in title:
return True
# Exclude automated version bump PRs
if "bump release version" in title or "bump version" in title:
return True
return False
def is_dependency_update(pr_details):
"""Check if PR is a dependency/chore update."""
if not pr_details:
return False
title = pr_details.get("title", "").lower()
author = pr_details.get("author", {}).get("login", "").lower()
labels = [label.get("name", "").lower() for label in pr_details.get("labels", [])]
# Check for renovate or dependabot authors
if "renovate" in author or "dependabot" in author:
return True
# Check for chore(deps) pattern
if re.match(r"^chore\(deps\):", title):
return True
# Check for digest update patterns
if re.match(r".*digest to [a-f0-9]+", title, re.IGNORECASE):
return True
# Check for dependency-related labels
dependency_labels = ["dependencies", "deps", "renovate"]
if any(dep in label for label in labels for dep in dependency_labels):
return True
return False
def is_enhancement(pr_details):
"""Determine if PR is an enhancement based on labels and title."""
labels = [label.get("name", "").lower() for label in pr_details.get("labels", [])]
# Check labels first
enhancement_labels = ["enhancement", "feature", "feat", "new feature"]
for label in labels:
if any(enh in label for enh in enhancement_labels):
return True
# Check title prefixes
title = pr_details.get("title", "")
enhancement_prefixes = ["feat:", "feature:", "add:"]
title_lower = title.lower()
for prefix in enhancement_prefixes:
if title_lower.startswith(prefix) or f" {prefix}" in title_lower:
return True
return False
def clean_title(title):
"""Clean up PR title for release notes."""
# Remove common prefixes
prefixes_to_remove = [
r"^fix:\s*",
r"^feat:\s*",
r"^feature:\s*",
r"^bug:\s*",
r"^bugfix:\s*",
r"^chore:\s*",
r"^chore\([^)]+\):\s*",
r"^refactor:\s*",
r"^docs:\s*",
r"^ci:\s*",
r"^build:\s*",
r"^perf:\s*",
r"^style:\s*",
r"^test:\s*",
]
cleaned = title
for prefix in prefixes_to_remove:
cleaned = re.sub(prefix, "", cleaned, flags=re.IGNORECASE)
# Ensure first letter is capitalized
if cleaned:
cleaned = cleaned[0].upper() + cleaned[1:]
return cleaned.strip()
def format_pr_line(pr_details):
"""Format a PR as a markdown bullet point."""
title = clean_title(pr_details.get("title", "Unknown"))
author = pr_details.get("author", {}).get("login", "unknown")
url = pr_details.get("url", "")
return f"- {title} by @{author} in {url}"
def get_new_contributors(pr_details_list, tag, repo="meshtastic/firmware"):
"""Find contributors who made their first merged PR before this release.
GitHub usernames do not necessarily match git commit authors, so we use the
GitHub search API via `gh` to see if the user has any merged PRs before the
tag date. This mirrors how GitHub's "Generate release notes" feature works.
"""
bot_authors = {"github-actions", "renovate", "dependabot", "app/renovate", "app/github-actions", "app/dependabot"}
new_contributors = []
seen_authors = set()
try:
tag_date = get_tag_date(tag)
except subprocess.CalledProcessError:
print(f"Warning: Could not determine tag date for {tag}; skipping new contributor detection", file=sys.stderr)
return []
for pr in pr_details_list:
author = pr.get("author", {}).get("login", "")
if not author or author in seen_authors:
continue
# Skip bots
if author.lower() in bot_authors or author.startswith("app/"):
continue
seen_authors.add(author)
try:
# Search for merged PRs by this author created before the tag date
search_query = f"is:pr author:{author} repo:{repo} closed:<=\"{tag_date}\""
search = subprocess.run(
[
"gh",
"search",
"issues",
"--json",
"number,mergedAt,createdAt",
"--state",
"closed",
"--limit",
"200",
search_query,
],
capture_output=True,
text=True,
)
if search.returncode != 0:
# If gh fails, be conservative and skip adding to new contributors
print(f"Warning: gh search failed for author {author}: {search.stderr.strip()}", file=sys.stderr)
continue
results = json.loads(search.stdout or "[]")
# If any merged PR exists before or on tag date, not a new contributor
had_prior_pr = any(item.get("mergedAt") for item in results)
if not had_prior_pr:
new_contributors.append((author, pr.get("url", "")))
except Exception as e:
print(f"Warning: Could not check contributor history for {author}: {e}", file=sys.stderr)
continue
return new_contributors
def main():
if len(sys.argv) < 2:
print("Usage: generate_release_notes.py <new_version>", file=sys.stderr)
sys.exit(1)
new_version = sys.argv[1]
# Get last release tag
try:
last_tag = get_last_release_tag()
except subprocess.CalledProcessError:
print("Error: Could not find last release tag", file=sys.stderr)
sys.exit(1)
# Collect PRs from both branches
all_pr_numbers = set()
for branch in ["develop", "master"]:
try:
prs = get_merged_prs_since_tag(last_tag, branch)
all_pr_numbers.update(prs)
except Exception as e:
print(f"Warning: Could not get PRs from {branch}: {e}", file=sys.stderr)
# Get details for all PRs
enhancements = []
bug_fixes = []
dependencies = []
all_pr_details = []
for pr_number in sorted(all_pr_numbers, key=int):
details = get_pr_details(pr_number)
if details and not should_exclude_pr(details):
all_pr_details.append(details)
if is_dependency_update(details):
dependencies.append(details)
elif is_enhancement(details):
enhancements.append(details)
else:
bug_fixes.append(details)
# Generate release notes
output = []
if enhancements:
output.append("## 🚀 Enhancements\n")
for pr in enhancements:
output.append(format_pr_line(pr))
output.append("")
if bug_fixes:
output.append("## 🐛 Bug fixes and maintenance\n")
for pr in bug_fixes:
output.append(format_pr_line(pr))
output.append("")
if dependencies:
output.append("## ⚙️ Dependencies\n")
for pr in dependencies:
output.append(format_pr_line(pr))
output.append("")
# Find new contributors (GitHub-accurate check using merged PRs before tag date)
new_contributors = get_new_contributors(all_pr_details, last_tag)
if new_contributors:
output.append("## New Contributors\n")
for author, url in new_contributors:
# Find first PR URL for this contributor
first_pr_url = url
for pr in all_pr_details:
if pr.get("author", {}).get("login") == author:
first_pr_url = pr.get("url", url)
break
output.append(f"- @{author} made their first contribution in {first_pr_url}")
output.append("")
# Add full changelog link
output.append(
f"**Full Changelog**: https://github.com/meshtastic/firmware/compare/{last_tag}...v{new_version}"
)
print("\n".join(output))
if __name__ == "__main__":
main()

32
bin/meshtasticd-start.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env sh
INSTANCE=$1
CONF_DIR="/etc/meshtasticd/config.d"
VFS_DIR="/var/lib"
# If no instance ID provided, start bare daemon and exit
echo "no instance ID provided, starting bare meshtasticd service"
if [ -z "${INSTANCE}" ]; then
/usr/bin/meshtasticd
exit 0
fi
# Make VFS dir if it does not exist
if [ ! -d "${VFS_DIR}/meshtasticd-${INSTANCE}" ]; then
echo "vfs for ${INSTANCE} does not exist, creating it."
mkdir "${VFS_DIR}/meshtasticd-${INSTANCE}"
fi
# Abort if config for $INSTANCE does not exist
if [ ! -f "${CONF_DIR}/config-${INSTANCE}.yaml" ]; then
echo "no config for ${INSTANCE} found in ${CONF_DIR}. refusing to start" >&2
exit 1
fi
# Start meshtasticd with instance parameters
printf "starting meshtasticd-%s..., ${INSTANCE}"
if /usr/bin/meshtasticd --config="${CONF_DIR}/config-${INSTANCE}.yaml" --fsdir="${VFS_DIR}/meshtasticd-${INSTANCE}"; then
echo "ok"
else
echo "failed"
fi

View File

@@ -1,5 +1,5 @@
[Unit]
Description=Meshtastic Native Daemon
Description=Meshtastic %i Daemon
After=network-online.target
StartLimitInterval=200
StartLimitBurst=5
@@ -9,7 +9,7 @@ AmbientCapabilities=CAP_NET_BIND_SERVICE
User=meshtasticd
Group=meshtasticd
Type=simple
ExecStart=/usr/bin/meshtasticd
ExecStart=/usr/bin/meshtasticd-start.sh %i
Restart=always
RestartSec=3

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env bash
cp "release/meshtasticd_linux_$(uname -m)" /usr/bin/meshtasticd
cp "bin/meshtasticd-start.sh" /usr/bin/meshtasticd-start.sh
mkdir -p /etc/meshtasticd
if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml

View File

@@ -87,6 +87,12 @@
</screenshots>
<releases>
<release version="2.7.19" date="2026-01-22">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19</url>
</release>
<release version="2.7.18" date="2026-01-02">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
</release>
<release version="2.7.17" date="2025-11-28">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
</release>

View File

@@ -2,11 +2,12 @@
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
import sys
from os.path import join, basename, isfile
from os.path import join
import subprocess
import json
import re
from datetime import datetime
from typing import Dict
from readprops import readProps
@@ -14,11 +15,59 @@ Import("env")
platform = env.PioPlatform()
progname = env.get("PROGNAME")
lfsbin = f"{progname.replace('firmware-', 'littlefs-')}.bin"
manifest_ran = False
def infer_architecture(board_cfg):
try:
mcu = board_cfg.get("build.mcu") if board_cfg else None
except KeyError:
mcu = None
except Exception:
mcu = None
if not mcu:
return None
mcu_l = str(mcu).lower()
if "esp32s3" in mcu_l:
return "esp32-s3"
if "esp32c6" in mcu_l:
return "esp32-c6"
if "esp32c3" in mcu_l:
return "esp32-c3"
if "esp32" in mcu_l:
return "esp32"
if "rp2040" in mcu_l:
return "rp2040"
if "rp2350" in mcu_l:
return "rp2350"
if "nrf52" in mcu_l or "nrf52840" in mcu_l:
return "nrf52840"
if "stm32" in mcu_l:
return "stm32"
return None
def manifest_gather(source, target, env):
global manifest_ran
if manifest_ran:
return
# Skip manifest generation if we cannot determine architecture (host/native builds)
board_arch = infer_architecture(env.BoardConfig())
if not board_arch:
print(f"Skipping mtjson generation for unknown architecture (env={env.get('PIOENV')})")
manifest_ran = True
return
manifest_ran = True
out = []
board_platform = env.BoardConfig().get("platform")
board_mcu = env.BoardConfig().get("build.mcu").lower()
needs_ota_suffix = board_platform == "nordicnrf52"
# Mapping of bin files to their target partition names
# Maps the filename pattern to the partition name where it should be flashed
partition_map = {
f"{progname}.bin": "app0", # primary application slot (app0 / OTA_0)
lfsbin: "spiffs", # filesystem image flashed to spiffs
}
check_paths = [
progname,
f"{progname}.elf",
@@ -29,7 +78,9 @@ def manifest_gather(source, target, env):
f"{progname}.uf2",
f"{progname}.factory.uf2",
f"{progname}.zip",
lfsbin
lfsbin,
f"mt-{board_mcu}-ota.bin",
"bleota-c3.bin"
]
for p in check_paths:
f = env.File(env.subst(f"$BUILD_DIR/{p}"))
@@ -42,19 +93,47 @@ def manifest_gather(source, target, env):
"md5": f.get_content_hash(), # Returns MD5 hash
"bytes": f.get_size() # Returns file size in bytes
}
# Add part_name if this file represents a partition that should be flashed
if p in partition_map:
d["part_name"] = partition_map[p]
out.append(d)
print(d)
manifest_write(out, env)
def manifest_write(files, env):
# Defensive: also skip manifest writing if we cannot determine architecture
def get_project_option(name):
try:
return env.GetProjectOption(name)
except Exception:
return None
def get_project_option_any(names):
for name in names:
val = get_project_option(name)
if val is not None:
return val
return None
def as_bool(val):
return str(val).strip().lower() in ("1", "true", "yes", "on")
def as_int(val):
try:
return int(str(val), 10)
except (TypeError, ValueError):
return None
def as_list(val):
return [item.strip() for item in str(val).split(",") if item.strip()]
manifest = {
"version": verObj["long"],
"build_epoch": build_epoch,
"board": env.get("PIOENV"),
"platformioTarget": env.get("PIOENV"),
"mcu": env.get("BOARD_MCU"),
"repo": repo_owner,
"files": files,
"part": None,
"has_mui": False,
"has_inkhud": False,
}
@@ -69,6 +148,51 @@ def manifest_write(files, env):
if "MESHTASTIC_INCLUDE_INKHUD" in env.get("CPPDEFINES", []):
manifest["has_inkhud"] = True
pioenv = env.get("PIOENV")
device_meta = {}
device_meta_fields = [
("hwModel", ["custom_meshtastic_hw_model"], as_int),
("hwModelSlug", ["custom_meshtastic_hw_model_slug"], str),
("architecture", ["custom_meshtastic_architecture"], str),
("activelySupported", ["custom_meshtastic_actively_supported"], as_bool),
("displayName", ["custom_meshtastic_display_name"], str),
("supportLevel", ["custom_meshtastic_support_level"], as_int),
("images", ["custom_meshtastic_images"], as_list),
("tags", ["custom_meshtastic_tags"], as_list),
("requiresDfu", ["custom_meshtastic_requires_dfu"], as_bool),
("partitionScheme", ["custom_meshtastic_partition_scheme"], str),
("url", ["custom_meshtastic_url"], str),
("key", ["custom_meshtastic_key"], str),
("variant", ["custom_meshtastic_variant"], str),
]
for manifest_key, option_keys, caster in device_meta_fields:
raw_val = get_project_option_any(option_keys)
if raw_val is None:
continue
parsed = caster(raw_val) if callable(caster) else raw_val
if parsed is not None and parsed != "":
device_meta[manifest_key] = parsed
# Determine architecture once; if we can't infer it, skip manifest generation
board_arch = device_meta.get("architecture") or infer_architecture(env.BoardConfig())
if not board_arch:
print(f"Skipping mtjson write for unknown architecture (env={env.get('PIOENV')})")
return
device_meta["architecture"] = board_arch
# Always set requiresDfu: true for nrf52840 targets
if board_arch == "nrf52840":
device_meta["requiresDfu"] = True
device_meta.setdefault("displayName", pioenv)
device_meta.setdefault("activelySupported", False)
if device_meta:
manifest.update(device_meta)
# Write the manifest to the build directory
with open(env.subst("$BUILD_DIR/${PROGNAME}.mt.json"), "w") as f:
json.dump(manifest, f, indent=2)
@@ -166,8 +290,12 @@ def load_boot_logo(source, target, env):
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
env.AddPreAction(f"$BUILD_DIR/{lfsbin}", load_boot_logo)
mtjson_deps = ["buildprog"]
if platform.name == "espressif32":
board_arch = infer_architecture(env.BoardConfig())
should_skip_manifest = board_arch is None
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
mtjson_deps = [] if should_skip_manifest else ["buildprog"]
if not should_skip_manifest and platform.name == "espressif32":
# Build littlefs image as part of mtjson target
# Equivalent to `pio run -t buildfs`
target_lfs = env.DataToBin(
@@ -175,11 +303,27 @@ if platform.name == "espressif32":
)
mtjson_deps.append(target_lfs)
env.AddCustomTarget(
name="mtjson",
dependencies=mtjson_deps,
actions=[manifest_gather],
title="Meshtastic Manifest",
description="Generating Meshtastic manifest JSON + Checksums",
always_build=False,
)
if should_skip_manifest:
def skip_manifest(source, target, env):
print(f"mtjson: skipped for native environment: {env.get('PIOENV')}")
env.AddCustomTarget(
name="mtjson",
dependencies=mtjson_deps,
actions=[skip_manifest],
title="Meshtastic Manifest (skipped)",
description="mtjson generation is skipped for native environments",
always_build=True,
)
else:
env.AddCustomTarget(
name="mtjson",
dependencies=mtjson_deps,
actions=[manifest_gather],
title="Meshtastic Manifest",
description="Generating Meshtastic manifest JSON + Checksums",
always_build=True,
)
# Run manifest generation as part of the default build pipeline for non-native builds.
env.Default("mtjson")

View File

@@ -18,8 +18,9 @@ def readProps(prefsLoc):
# Try to find current build SHA if if the workspace is clean. This could fail if git is not installed
try:
# Pin abbreviation length to keep local builds and CI matching (avoid auto-shortening)
sha = (
subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
subprocess.check_output(["git", "rev-parse", "--short=7", "HEAD"])
.decode("utf-8")
.strip()
)

View File

@@ -0,0 +1,38 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default.csv",
"memory_type": "qio_qspi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "CDEBYTE_EoRa-Hub",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.cdebyte.com/products/EoRa-HUB-900TB",
"vendor": "CDEBYTE"
}

53
boards/ThinkNode-M4.json Normal file
View File

@@ -0,0 +1,53 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_ELECROW_M4 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"]
],
"usb_product": "elecrow_thinknode_m4",
"mcu": "nrf52840",
"variant": "ELECROW-ThinkNode-M4",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "ELECROW ThinkNode m4",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.elecrow.com/thinknode-m4-power-bank-lora-device-with-meshtastic-lora-tracker-function-powered-by-nrf52840.html",
"vendor": "ELECROW"
}

50
boards/minimesh_lite.json Normal file
View File

@@ -0,0 +1,50 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DMINIMESH_LITE -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"]
],
"usb_product": "Minimesh Lite",
"mcu": "nrf52840",
"variant": "dls_Minimesh_Lite",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Minimesh Lite",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://deeplabstudio.com",
"vendor": "Deeplab Studio"
}

39
boards/t-beam-1w.json Normal file
View File

@@ -0,0 +1,39 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_1W",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "t-beam-1w"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino"],
"name": "LilyGo TBeam-1W",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"url": "http://www.lilygo.cn/",
"vendor": "LilyGo"
}

View File

@@ -9,7 +9,7 @@
"-DBOARD_HAS_PSRAM",
"-DT_WATCH_S3",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],

12
debian/changelog vendored
View File

@@ -1,3 +1,15 @@
meshtasticd (2.7.19.0) unstable; urgency=medium
* Version 2.7.19
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Thu, 22 Jan 2026 22:17:40 +0000
meshtasticd (2.7.18.0) unstable; urgency=medium
* Version 2.7.18
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Fri, 02 Jan 2026 12:45:36 +0000
meshtasticd (2.7.17.0) unstable; urgency=medium
* Version 2.7.17

3
debian/control vendored
View File

@@ -25,7 +25,8 @@ Build-Depends: debhelper-compat (= 13),
liborcania-dev,
libx11-dev,
libinput-dev,
libxkbcommon-x11-dev
libxkbcommon-x11-dev,
libsqlite3-dev
Standards-Version: 4.6.2
Homepage: https://github.com/meshtastic/firmware
Rules-Requires-Root: no

View File

@@ -4,5 +4,6 @@ bin/config.yaml etc/meshtasticd
bin/config.d/* etc/meshtasticd/available.d
bin/meshtasticd.service lib/systemd/system
bin/meshtasticd-start.sh usr/bin
web/* usr/share/meshtasticd/web

View File

@@ -39,6 +39,7 @@ BuildRequires: pkgconfig(bluez)
BuildRequires: pkgconfig(libusb-1.0)
BuildRequires: libi2c-devel
BuildRequires: pkgconfig(libuv)
BuildRequires: pkgconfig(sqlite3)
# Web components:
BuildRequires: pkgconfig(openssl)
BuildRequires: pkgconfig(liborcania)
@@ -95,6 +96,9 @@ cp -r bin/config.d/* %{buildroot}%{_sysconfdir}/meshtasticd/available.d
# Install systemd service
install -D -m 0644 bin/meshtasticd.service %{buildroot}%{_unitdir}/meshtasticd.service
# Install meshtasticd start wrapper
install -D -m 0755 bin/meshtasticd-start.sh %{buildroot}%{_bindir}/meshtasticd-start.sh
# Install the web files under /usr/share/meshtasticd/web
mkdir -p %{buildroot}%{_datadir}/meshtasticd/web
cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web

View File

@@ -43,13 +43,11 @@ class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase):
self.enabled = self.setup_paths()
if self.config.get("env:" + self.environment, "build_type") != "debug":
print(
"""
print("""
Please build project in debug configuration to get more details about an exception.
See https://docs.platformio.org/page/projectconf/build_configurations.html
"""
)
""")
return self

View File

@@ -54,6 +54,7 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
-DMESHTASTIC_EXCLUDE_POWERMON=1
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-D OLED_PL=1
@@ -113,13 +114,12 @@ lib_deps =
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
# jgromes/RadioLib@7.4.0
https://github.com/jgromes/RadioLib/archive/536c7267362e2c1345be7054ba45e503252975ff.zip
jgromes/RadioLib@7.5.0
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/862ed040c4ab44f0dfbbe492691f144886102588.zip
https://github.com/meshtastic/device-ui/archive/37ad715b76cd6ca4aa500a4a4d9740e3cdf3e3cb.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
@@ -129,7 +129,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
adafruit/Adafruit BMP280 Library@2.6.8
adafruit/Adafruit BMP280 Library@3.0.0
# renovate: datasource=custom.pio depName=Adafruit BMP085 packageName=adafruit/library/Adafruit BMP085 Library
adafruit/Adafruit BMP085 Library@1.2.4
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
@@ -142,8 +142,6 @@ lib_deps =
adafruit/Adafruit INA260 Library@1.5.3
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor
adafruit/Adafruit PM25 AQI Sensor@2.0.0
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
adafruit/Adafruit MPU6050@2.2.6
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
@@ -167,7 +165,7 @@ lib_deps =
# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
# renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226
robtillaart/INA226@0.6.5
robtillaart/INA226@0.6.6
# renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library
sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2
# renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library
@@ -214,3 +212,30 @@ lib_deps =
sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0
; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
[environmental_extra_no_bsec]
lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library
adafruit/Adafruit BMP3XX Library@2.1.6
# renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X
adafruit/Adafruit MAX1704X@1.0.3
# renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library
adafruit/Adafruit SHTC3 Library@1.0.2
# renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X
adafruit/Adafruit LPS2X@2.0.6
# renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library
adafruit/Adafruit SHT31 Library@2.2.2
# renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library
adafruit/Adafruit VEML7700 Library@2.1.6
# renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library
adafruit/Adafruit SHT4x Library@1.0.5
# renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
closedcube/ClosedCube OPT3001@1.1.2
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0

View File

@@ -41,7 +41,8 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...)
}
#if HAS_NETWORKING
namespace meshtastic
{
Syslog::Syslog(UDP &client)
{
this->_client = &client;
@@ -195,4 +196,6 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
return true;
}
}; // namespace meshtastic
#endif

View File

@@ -162,6 +162,8 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
#if HAS_NETWORKING
namespace meshtastic
{
class Syslog
{
private:
@@ -195,4 +197,6 @@ class Syslog
bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0)));
};
#endif // HAS_NETWORKING
}; // namespace meshtastic
#endif // HAS_NETWORKING

View File

@@ -13,6 +13,11 @@
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
#endif
// Default autosave interval 2 hours, override per device later with -DMESSAGE_AUTOSAVE_INTERVAL_SEC=300 (etc)
#ifndef MESSAGE_AUTOSAVE_INTERVAL_SEC
#define MESSAGE_AUTOSAVE_INTERVAL_SEC (2 * 60 * 60)
#endif
// Global message text pool and state
static char *g_messagePool = nullptr;
static size_t g_poolWritePos = 0;
@@ -102,6 +107,60 @@ void MessageStore::addLiveMessage(const StoredMessage &msg)
pushWithLimit(liveMessages, msg);
}
#if ENABLE_MESSAGE_PERSISTENCE
static bool g_messageStoreHasUnsavedChanges = false;
static uint32_t g_lastAutoSaveMs = 0; // last time we actually saved
static inline uint32_t autosaveIntervalMs()
{
uint32_t sec = (uint32_t)MESSAGE_AUTOSAVE_INTERVAL_SEC;
if (sec < 60)
sec = 60;
return sec * 1000UL;
}
static inline bool reachedMs(uint32_t now, uint32_t target)
{
return (int32_t)(now - target) >= 0;
}
// Mark new messages in RAM that need to be saved later
static inline void markMessageStoreUnsaved()
{
g_messageStoreHasUnsavedChanges = true;
if (g_lastAutoSaveMs == 0) {
g_lastAutoSaveMs = millis();
}
}
// Called periodically from the main loop in main.cpp
static inline void autosaveTick(MessageStore *store)
{
if (!store)
return;
uint32_t now = millis();
if (g_lastAutoSaveMs == 0) {
g_lastAutoSaveMs = now;
return;
}
if (!reachedMs(now, g_lastAutoSaveMs + autosaveIntervalMs()))
return;
// Autosave interval reached, only save if there are unsaved messages.
if (g_messageStoreHasUnsavedChanges) {
LOG_INFO("Autosaving MessageStore to flash");
store->saveToFlash();
} else {
LOG_INFO("Autosave skipped, no changes to save");
g_lastAutoSaveMs = now;
}
}
#endif
// Add from incoming/outgoing packet
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
{
@@ -131,6 +190,11 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa
}
addLiveMessage(sm);
#if ENABLE_MESSAGE_PERSISTENCE
markMessageStoreUnsaved();
#endif
return liveMessages.back();
}
@@ -155,6 +219,10 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
sm.ackStatus = AckStatus::NONE;
addLiveMessage(sm);
#if ENABLE_MESSAGE_PERSISTENCE
markMessageStoreUnsaved();
#endif
}
#if ENABLE_MESSAGE_PERSISTENCE
@@ -239,6 +307,10 @@ void MessageStore::saveToFlash()
f.close();
#endif
// Reset autosave state after any save
g_messageStoreHasUnsavedChanges = false;
g_lastAutoSaveMs = millis();
}
void MessageStore::loadFromFlash()
@@ -270,6 +342,9 @@ void MessageStore::loadFromFlash()
f.close();
#endif
// Loading messages does not trigger an autosave
g_messageStoreHasUnsavedChanges = false;
g_lastAutoSaveMs = millis();
}
#else
@@ -290,6 +365,11 @@ void MessageStore::clearAllMessages()
f.write(&count, 1); // write "0 messages"
f.close();
#endif
#if ENABLE_MESSAGE_PERSISTENCE
g_messageStoreHasUnsavedChanges = false;
g_lastAutoSaveMs = millis();
#endif
}
// Internal helper: erase first or last message matching a predicate
@@ -421,6 +501,14 @@ uint16_t MessageStore::storeText(const char *src, size_t len)
return storeTextInPool(src, len);
}
#if ENABLE_MESSAGE_PERSISTENCE
void messageStoreAutosaveTick()
{
// Called from the main loop to check autosave timing
autosaveTick(&messageStore);
}
#endif
// Global definition
MessageStore messageStore("default");
#endif
#endif

View File

@@ -125,6 +125,11 @@ class MessageStore
std::string filename; // Flash filename for persistence
};
#if ENABLE_MESSAGE_PERSISTENCE
// Called periodically from main loop to trigger time based autosave
void messageStoreAutosaveTick();
#endif
// Global instance (defined in MessageStore.cpp)
extern MessageStore messageStore;

View File

@@ -1,11 +1,14 @@
/**
* @file Power.cpp
* @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality
* of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The
* Power class is used by the main device class to manage power-related functionality.
* @brief This file contains the implementation of the Power class, which is
* responsible for managing power-related functionality of the device. It
* includes battery level sensing, power management unit (PMU) control, and
* power state machine management. The Power class is used by the main device
* class to manage power-related functionality.
*
* The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes
* the battery voltage is attached via a voltage-divider to an analog input.
* The file also includes implementations of various battery level sensors, such
* as the AnalogBatteryLevel class, which assumes the battery voltage is
* attached via a voltage-divider to an analog input.
*
* This file is part of the Meshtastic project.
* For more information, see: https://meshtastic.org/
@@ -19,6 +22,7 @@
#include "configuration.h"
#include "main.h"
#include "meshUtils.h"
#include "power/PowerHAL.h"
#include "sleep.h"
#if defined(ARCH_PORTDUINO)
@@ -171,22 +175,12 @@ Power *power;
using namespace meshtastic;
#ifndef AREF_VOLTAGE
#if defined(ARCH_NRF52)
/*
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
*
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
* VDD/4, VDD/2 or VDD for the ADC levels.
*
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
*/
#define AREF_VOLTAGE 3.6
#else
// NRF52 has AREF_VOLTAGE defined in architecture.h but
// make sure it's included. If something is wrong with NRF52
// definition - compilation will fail on missing definition
#if !defined(AREF_VOLTAGE) && !defined(ARCH_NRF52)
#define AREF_VOLTAGE 3.3
#endif
#endif
/**
* If this board has a battery level sensor, set this to a valid implementation
@@ -233,7 +227,8 @@ static void battery_adcDisable()
#endif
/**
* A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input
* A simple battery level sensor that assumes the battery voltage is attached
* via a voltage-divider to an analog input
*/
class AnalogBatteryLevel : public HasBatteryLevel
{
@@ -311,7 +306,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
#ifndef BATTERY_SENSE_SAMPLES
#define BATTERY_SENSE_SAMPLES \
15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment.
15 // Set the number of samples, it has an effect of increasing sensitivity in
// complex electromagnetic environment.
#endif
#ifdef BATTERY_PIN
@@ -341,7 +337,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
battery_adcDisable();
if (!initial_read_done) {
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
// Flush the smoothing filter with an ADC reading, if the reading is
// plausibly correct
if (scaled > last_read_value)
last_read_value = scaled;
initial_read_done = true;
@@ -350,8 +347,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
}
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
// (last_read_value));
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u",
// BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value));
}
return last_read_value;
#endif // BATTERY_PIN
@@ -420,7 +417,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
/**
* return true if there is a battery installed in this unit
*/
// if we have a integrated device with a battery, we can assume that the battery is always connected
// if we have a integrated device with a battery, we can assume that the
// battery is always connected
#ifdef BATTERY_IMMUTABLE
virtual bool isBatteryConnect() override { return true; }
#elif defined(ADC_V)
@@ -441,10 +439,10 @@ 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
/// 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
virtual bool isVbusIn() override
{
#ifdef EXT_PWR_DETECT
@@ -461,8 +459,12 @@ class AnalogBatteryLevel : public HasBatteryLevel
}
// if it's not HIGH - check the battery
#endif
#elif defined(MUZI_BASE)
return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
// technically speaking this should work for all(?) NRF52 boards
// but needs testing across multiple devices. NRF52 USB would not even work if
// VBUS was not properly connected and detected by the CPU
#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO)
return powerHAL_isVBUSConnected();
#endif
return getBattVoltage() > chargingVolt;
}
@@ -476,15 +478,18 @@ class AnalogBatteryLevel : public HasBatteryLevel
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
}
#endif
#ifdef EXT_CHRG_DETECT
#if defined(ELECROW_ThinkNode_M6)
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value || isVbusIn();
#elif EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#elif defined(BATTERY_CHARGING_INV)
return !digitalRead(BATTERY_CHARGING_INV);
#else
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
if (hasINA()) {
// get current flow from INA sensor - negative value means power flowing into the battery
// default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD
// get current flow from INA sensor - negative value means power flowing
// into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT
// RESISTOR <--> INA_VIN- <--> LOAD
LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
#if defined(INA_CHARGING_DETECTION_INVERT)
return getINACurrent() > 0;
@@ -500,8 +505,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
}
private:
/// If we see a battery voltage higher than physics allows - assume charger is pumping
/// in power
/// If we see a battery voltage higher than physics allows - assume charger is
/// pumping in power
/// For heltecs with no battery connected, the measured voltage is 2204, so
// need to be higher than that, in this case is 2500mV (3000-500)
@@ -510,7 +515,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
// Start value from minimum voltage for the filter to not start from 0
// that could trigger some events.
// This value is over-written by the first ADC reading, it the voltage seems reasonable.
// This value is over-written by the first ADC reading, it the voltage seems
// reasonable.
bool initial_read_done = false;
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
uint32_t last_read_time_ms = 0;
@@ -652,7 +658,8 @@ bool Power::analogInit()
#ifdef CONFIG_IDF_TARGET_ESP32S3
// ESP32S3
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
LOG_INFO("ADC config based on Two Point values and fitting curve coefficients stored in eFuse");
LOG_INFO("ADC config based on Two Point values and fitting curve "
"coefficients stored in eFuse");
}
#endif
else {
@@ -660,13 +667,7 @@ bool Power::analogInit()
}
#endif // ARCH_ESP32
#ifdef ARCH_NRF52
#ifdef VBAT_AR_INTERNAL
analogReference(VBAT_AR_INTERNAL);
#else
analogReference(AR_INTERNAL); // 3.6V
#endif
#endif // ARCH_NRF52
// NRF52 ADC init moved to powerHAL_init in nrf52 platform
#ifndef ARCH_ESP32
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
@@ -693,6 +694,8 @@ bool Power::setup()
found = true;
} else if (lipoChargerInit()) {
found = true;
} else if (serialBatteryInit()) {
found = true;
} else if (meshSolarInit()) {
found = true;
} else if (analogInit()) {
@@ -719,6 +722,16 @@ bool Power::setup()
runASAP = true;
},
CHANGE);
#endif
#ifdef EXT_CHRG_DETECT
attachInterrupt(
EXT_CHRG_DETECT,
[]() {
power->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
},
CHANGE);
#endif
enabled = found;
low_voltage_counter = 0;
@@ -765,7 +778,8 @@ void Power::reboot()
HAL_NVIC_SystemReset();
#else
rebootAtMsec = -1;
LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied");
LOG_WARN("FIXME implement reboot for this platform. Note that some settings "
"require a restart to be applied");
#endif
}
@@ -775,9 +789,12 @@ void Power::shutdown()
#if HAS_SCREEN
if (screen) {
#ifdef T_DECK_PRO
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!",
0); // T-Deck Pro has no power button
#elif defined(USE_EINK)
screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen
screen->showSimpleBanner("Shutting Down...",
2250); // dismiss after 3 seconds to avoid the
// banner on the sleep screen
#else
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
#endif
@@ -816,7 +833,8 @@ void Power::readPowerStatus()
int32_t batteryVoltageMv = -1; // Assume unknown
int8_t batteryChargePercent = -1;
OptionalBool usbPowered = OptUnknown;
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM
// code doesn't run every time
OptionalBool isChargingNow = OptUnknown;
if (batteryLevel) {
@@ -829,9 +847,10 @@ void Power::readPowerStatus()
if (batteryLevel->getBatteryPercent() >= 0) {
batteryChargePercent = batteryLevel->getBatteryPercent();
} else {
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
// In that case, we compute an estimate of the charge percent based on open circuit voltage table defined
// in power.h
// If the AXP192 returns a percentage less than 0, the feature is either
// not supported or there is an error In that case, we compute an
// estimate of the charge percent based on open circuit voltage table
// defined in power.h
batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) /
((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))),
0, 100);
@@ -839,12 +858,12 @@ void Power::readPowerStatus()
}
}
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass
// (which shares a superclass with the BatteryLevel stuff)
// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current
// practice.
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect
// changes.
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way
// better instead to make a Nrf52IsUsbPowered subclass (which shares a
// superclass with the BatteryLevel stuff) that just provides a few methods. But
// in the interest of fixing this bug I'm going to follow current practice.
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates
// the power states. Takes 20 seconds or so to detect changes.
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
// LOG_DEBUG("NRF Power %d", nrf_usb_state);
@@ -918,8 +937,9 @@ void Power::readPowerStatus()
#endif
// If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in
// a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
// If we have a battery at all and it is less than 0%, force deep sleep if we
// have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage
// is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
//
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
@@ -941,8 +961,8 @@ int32_t Power::runOnce()
readPowerStatus();
#ifdef HAS_PMU
// WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll
// the IRQ status by reading the registers over I2C
// WE no longer use the IRQ line to wake the CPU (due to false wakes from
// sleep), but we do poll the IRQ status by reading the registers over I2C
if (PMU) {
PMU->getIrqStatus();
@@ -984,7 +1004,8 @@ int32_t Power::runOnce()
PMU->clearIrqStatus();
}
#endif
// Only read once every 20 seconds once the power status for the app has been initialized
// Only read once every 20 seconds once the power status for the app has been
// initialized
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
}
@@ -992,10 +1013,12 @@ int32_t Power::runOnce()
* Init the power manager chip
*
* axp192 power
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the
axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this
on!) LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of
days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose
comms to the axp192 because the OLED and the axp192 share the same i2c bus,
instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max ->
ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the
tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
*
*/
bool Power::axpChipInit()
@@ -1040,9 +1063,10 @@ bool Power::axpChipInit()
if (!PMU) {
/*
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will be called at the same time.
* In order not to affect other devices, if the initialization of the PMU fails, Wire needs to be re-initialized once,
* if there are multiple devices sharing the bus.
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will
* be called at the same time. In order not to affect other devices, if the
* initialization of the PMU fails, Wire needs to be re-initialized once, if
* there are multiple devices sharing the bus.
* * */
#ifndef PMU_USE_WIRE1
w->begin(I2C_SDA, I2C_SCL);
@@ -1059,8 +1083,8 @@ bool Power::axpChipInit()
PMU->enablePowerOutput(XPOWERS_LDO2);
// oled module power channel,
// disable it will cause abnormal communication between boot and AXP power supply,
// do not turn it off
// disable it will cause abnormal communication between boot and AXP power
// supply, do not turn it off
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
// enable oled power
PMU->enablePowerOutput(XPOWERS_DCDC1);
@@ -1087,7 +1111,8 @@ bool Power::axpChipInit()
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it uses an AXP2101 power chip*/
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it
* uses an AXP2101 power chip*/
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
// Unuse power channel
PMU->disablePowerOutput(XPOWERS_DCDC2);
@@ -1122,8 +1147,8 @@ bool Power::axpChipInit()
// t-beam s3 core
/**
* gnss module power channel
* The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during
* initialization
* The default ALDO4 is off, you need to turn on the GNSS power first,
* otherwise it will be invalid during initialization
*/
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
PMU->enablePowerOutput(XPOWERS_ALDO4);
@@ -1149,11 +1174,11 @@ bool Power::axpChipInit()
PMU->setPowerChannelVoltage(XPOWERS_ALDO1, 3300);
PMU->enablePowerOutput(XPOWERS_ALDO1);
// sdcard power channel
// sdcard (T-Beam S3) / gnns (T-Watch S3 Plus) power channel
PMU->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
#ifndef T_WATCH_S3
PMU->enablePowerOutput(XPOWERS_BLDO1);
#ifdef T_WATCH_S3
#else
// DRV2605 power channel
PMU->setPowerChannelVoltage(XPOWERS_BLDO2, 3300);
PMU->enablePowerOutput(XPOWERS_BLDO2);
@@ -1173,7 +1198,8 @@ bool Power::axpChipInit()
// disable all axp chip interrupt
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
// Set the constant current charging current of AXP2101, temporarily use 500mA by default
// Set the constant current charging current of AXP2101, temporarily use
// 500mA by default
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
// Set up the charging voltage
@@ -1239,11 +1265,12 @@ bool Power::axpChipInit()
PMU->getPowerChannelVoltage(XPOWERS_BLDO2));
}
// We can safely ignore this approach for most (or all) boards because MCU turned off
// earlier than battery discharged to 2.6V.
// We can safely ignore this approach for most (or all) boards because MCU
// turned off earlier than battery discharged to 2.6V.
//
// Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with
// battery voltage measurement. Probably it sometimes drops to low values.
// Unfortunately for now we can't use this killswitch for RAK4630-based boards
// because they have a bug with battery voltage measurement. Probably it
// sometimes drops to low values.
#ifndef RAK4630
// Set PMU shutdown voltage at 2.6V to maximize battery utilization
PMU->setSysPowerDownVoltage(2600);
@@ -1262,10 +1289,12 @@ bool Power::axpChipInit()
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 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
PMU->enableIRQ(pmuIrqMask);
PMU->clearIrqStatus();
@@ -1381,8 +1410,8 @@ class LipoCharger : public HasBatteryLevel
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
if (result) {
LOG_INFO("PPM BQ25896 init succeeded");
// Set the minimum operating voltage. Below this voltage, the PPM will protect
// PPM->setSysPowerDownVoltage(3100);
// Set the minimum operating voltage. Below this voltage, the PPM will
// protect PPM->setSysPowerDownVoltage(3100);
// Set input current limit, default is 500mA
// PPM->setInputCurrentLimit(800);
@@ -1405,7 +1434,8 @@ class LipoCharger : public HasBatteryLevel
PPM->enableMeasure();
// Turn on charging function
// If there is no battery connected, do not turn on the charging function
// If there is no battery connected, do not turn on the charging
// function
PPM->enableCharge();
} else {
LOG_WARN("PPM BQ25896 init failed");
@@ -1440,7 +1470,8 @@ class LipoCharger : public HasBatteryLevel
virtual int getBatteryPercent() override
{
return -1;
// return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated
// return bq->getChargePercent(); // don't use BQ27220 for battery percent,
// it is not calibrated
}
/**
@@ -1562,10 +1593,143 @@ bool Power::meshSolarInit()
#else
/**
* The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
* The meshSolar battery level sensor is unavailable - default to
* AnalogBatteryLevel
*/
bool Power::meshSolarInit()
{
return false;
}
#endif
#ifdef HAS_SERIAL_BATTERY_LEVEL
#include <SoftwareSerial.h>
/**
* SerialBatteryLevel class for pulling battery information from a secondary MCU over serial.
*/
class SerialBatteryLevel : public HasBatteryLevel
{
public:
/**
* Init the I2C meshSolar battery level sensor
*/
bool runOnce()
{
BatterySerial.begin(4800);
return true;
}
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
*/
virtual int getBatteryPercent() override { return v_percent; }
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override { return voltage * 1000; }
/**
* return true if there is a battery installed in this unit
*/
virtual bool isBatteryConnect() override
{
// definitely need to gobble up more bytes at once
if (BatterySerial.available() > 5) {
// LOG_WARN("SerialBatteryLevel: %u bytes available", BatterySerial.available());
while (BatterySerial.available() > 11) {
BatterySerial.read(); // flush old data
}
// LOG_WARN("SerialBatteryLevel: %u bytes now available", BatterySerial.available());
int tries = 0;
while (BatterySerial.read() != 0xFE) {
tries++; // wait for start byte
if (tries > 10) {
LOG_WARN("SerialBatteryLevel: no start byte found");
return 1;
}
}
Data[1] = BatterySerial.read();
Data[2] = BatterySerial.read();
Data[3] = BatterySerial.read();
Data[4] = BatterySerial.read();
Data[5] = BatterySerial.read();
if (Data[5] != 0xFD) {
LOG_WARN("SerialBatteryLevel: invalid end byte %02x", Data[5]);
return true;
}
v_percent = Data[1];
voltage = Data[2] + (((float)Data[3]) / 100) + (((float)Data[4]) / 10000);
voltage *= 2;
// LOG_WARN("SerialBatteryLevel: received data %u, %f, %02x", v_percent, voltage, Data[5]);
return true;
}
// This function runs first, so use it to grab the latest data from the secondary MCU
return true;
}
/**
* return true if there is an external power source detected
*/
virtual bool isVbusIn() override
{
#if defined(EXT_CHRG_DETECT)
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#endif
return false;
}
virtual bool isCharging() override
{
#ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#endif
// by default, we check the battery voltage only
return isVbusIn();
}
private:
SoftwareSerial BatterySerial = SoftwareSerial(SERIAL_BATTERY_RX, SERIAL_BATTERY_TX);
uint8_t Data[6] = {0};
int v_percent = 0;
float voltage = 0.0;
};
SerialBatteryLevel serialBatteryLevel;
/**
* Init the serial battery level sensor
*/
bool Power::serialBatteryInit()
{
#ifdef EXT_PWR_DETECT
pinMode(EXT_PWR_DETECT, INPUT);
#endif
#ifdef EXT_CHRG_DETECT
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
#endif
bool result = serialBatteryLevel.runOnce();
LOG_DEBUG("Power::serialBatteryInit serial battery sensor is %s", result ? "ready" : "not ready yet");
if (!result)
return false;
batteryLevel = &serialBatteryLevel;
return true;
}
#else
/**
* If this device has no serial battery level sensor, don't try to use it.
*/
bool Power::serialBatteryInit()
{
return false;
}
#endif

View File

@@ -18,7 +18,7 @@
#endif
#if HAS_NETWORKING
extern Syslog syslog;
extern meshtastic::Syslog syslog;
#endif
void RedirectablePrint::rpInit()
{

View File

@@ -54,7 +54,7 @@ size_t SafeFile::write(const uint8_t *buffer, size_t size)
}
/**
* Atomically close the file (deleting any old versions) and readback the contents to confirm the hash matches
* Atomically close the file (overwriting any old version) and readback the contents to confirm the hash matches
*
* @return false for failure
*/
@@ -73,15 +73,7 @@ bool SafeFile::close()
if (!testReadback())
return false;
{ // Scope for lock
concurrency::LockGuard g(spiLock);
// brief window of risk here ;-)
if (fullAtomic && FSCom.exists(filename.c_str()) && !FSCom.remove(filename.c_str())) {
LOG_ERROR("Can't remove old pref file");
return false;
}
}
// Rename or overwrite (atomic operation)
String filenameTmp = filename;
filenameTmp += ".tmp";
if (!renameFile(filenameTmp.c_str(), filename.c_str())) {

View File

@@ -22,12 +22,19 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
// Handle different input events with appropriate buzzer feedback
switch (event->inputEvent) {
#ifdef INPUTDRIVER_ENCODER_TYPE
case INPUT_BROKER_SELECT:
case INPUT_BROKER_SELECT_LONG:
playClick();
break;
#else
case INPUT_BROKER_USER_PRESS:
case INPUT_BROKER_ALT_PRESS:
case INPUT_BROKER_SELECT:
case INPUT_BROKER_SELECT_LONG:
playBeep(); // Confirmation feedback
playBeep();
break;
#endif
case INPUT_BROKER_UP:
case INPUT_BROKER_UP_LONG:
@@ -58,4 +65,4 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
}
return 0; // Allow other handlers to process the event
}
}

View File

@@ -35,6 +35,14 @@ struct ToneDuration {
#define NOTE_G6 1568
#define NOTE_E7 2637
#define NOTE_C4 262
#define NOTE_E4 330
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_C5 523
#define NOTE_E5 659
#define NOTE_G5 784
const int DURATION_1_16 = 62; // 1/16 note
const int DURATION_1_8 = 125; // 1/8 note
const int DURATION_1_4 = 250; // 1/4 note
@@ -65,7 +73,7 @@ void playTones(const ToneDuration *tone_durations, int size)
void playBeep()
{
ToneDuration melody[] = {{NOTE_B3, DURATION_1_8}};
ToneDuration melody[] = {{NOTE_B3, DURATION_1_16}};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
@@ -113,7 +121,14 @@ void playShutdownMelody()
void playChirp()
{
// A short, friendly "chirp" sound for key presses
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Very short AS3 note
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Short AS3 note
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void playClick()
{
// A very short "click" sound with minimum delay; ideal for rotary encoder events
ToneDuration melody[] = {{NOTE_AS3, 1}}; // Very Short AS3
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
@@ -182,3 +197,17 @@ void playComboTune()
};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void play4ClickDown()
{
ToneDuration melody[] = {{NOTE_G5, 55}, {NOTE_E5, 55}, {NOTE_C5, 60}, {NOTE_A4, 55}, {NOTE_G4, 55},
{NOTE_E4, 65}, {NOTE_C4, 80}, {NOTE_G3, 120}, {NOTE_E3, 160}, {NOTE_SILENT, 120}};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void play4ClickUp()
{
// Quick high-pitched notes with trills
ToneDuration melody[] = {{NOTE_F5, 50}, {NOTE_G6, 45}, {NOTE_E7, 60}};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}

View File

@@ -7,8 +7,11 @@ void playShutdownMelody();
void playGPSEnableBeep();
void playGPSDisableBeep();
void playComboTune();
void play4ClickDown();
void play4ClickUp();
void playBoop();
void playChirp();
void playClick();
void playLongPressLeadUp();
bool playNextLeadUpNote(); // Play the next note in the lead-up sequence
void resetLeadUpSequence(); // Reset the lead-up sequence to start from beginning

View File

@@ -155,6 +155,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
// Default system gain to 0 if not defined
#ifndef NUM_PA_POINTS
#define NUM_PA_POINTS 1
#endif
#ifndef TX_GAIN_LORA
#define TX_GAIN_LORA 0
#endif
@@ -172,11 +176,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------
// OLED & Input
// -----------------------------------------------------------------------------
#define SSD1306_ADDRESS_L 0x3C // Addr = 0
#define SSD1306_ADDRESS_H 0x3D // Addr = 1
#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
#define SSD1306_ADDRESS 0x3D
#define SSD1306_ADDRESS SSD1306_ADDRESS_H
#define USE_SH1106
#else
#define SSD1306_ADDRESS 0x3C
#endif
#define ST7567_ADDRESS 0x3F
@@ -205,7 +210,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define INA_ADDR_WAVESHARE_UPS 0x43
#define INA3221_ADDR 0x42
#define MAX1704X_ADDR 0x36
#define QMC6310_ADDR 0x1C
#define QMC6310U_ADDR 0x1C
#define QMI8658_ADDR 0x6B
#define QMC5883L_ADDR 0x0D
#define HMC5883L_ADDR 0x1E
@@ -214,7 +219,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define LPS22HB_ADDR_ALT 0x5D
#define SHT31_4x_ADDR 0x44
#define SHT31_4x_ADDR_ALT 0x45
#define PMSA0031_ADDR 0x12
#define PMSA003I_ADDR 0x12
#define QMA6100P_ADDR 0x12
#define AHT10_ADDR 0x38
#define RCWL9620_ADDR 0x57
@@ -468,6 +473,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_AUDIO 1
#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1
#define MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR 1
#define MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY 1
#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1
#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1

View File

@@ -43,7 +43,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
ScanI2C::FoundDevice ScanI2C::firstAQI() const
{
ScanI2C::DeviceType types[] = {PMSA0031, SCD4X};
ScanI2C::DeviceType types[] = {PMSA003I, SCD4X};
return firstOfOrNONE(2, types);
}

View File

@@ -35,11 +35,12 @@ class ScanI2C
SHT4X,
SHTC3,
LPS22HB,
QMC6310,
QMC6310U,
QMC6310N,
QMI8658,
QMC5883L,
HMC5883L,
PMSA0031,
PMSA003I,
QMA6100P,
MPU6050,
LIS3DH,

View File

@@ -63,12 +63,16 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const
if (i2cBus->available()) {
r = i2cBus->read();
}
if (r == 0x80) {
LOG_INFO("QMC6310N found at address 0x%02X", addr.address);
return ScanI2C::DeviceType::QMC6310N;
}
r &= 0x0f;
if (r == 0x08 || r == 0x00) {
logFoundDevice("SH1106", (uint8_t)addr.address);
o_probe = SCREEN_SH1106; // SH1106
} else if (r == 0x03 || r == 0x04 || r == 0x06 || r == 0x07) {
} else if (r == 0x03 || r == 0x04 || r == 0x06 || r == 0x07 || r == 0x05) {
logFoundDevice("SSD1306", (uint8_t)addr.address);
o_probe = SCREEN_SSD1306; // SSD1306
}
@@ -106,7 +110,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
if (i2cBus->available())
i2cBus->read();
}
LOG_DEBUG("Register value: 0x%x", value);
LOG_DEBUG("Register value from 0x%x: 0x%x", registerLocation.i2cAddress.address, value);
return value;
}
@@ -175,7 +179,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = NONE;
if (err == 0) {
switch (addr.address) {
case SSD1306_ADDRESS:
case SSD1306_ADDRESS_H:
case SSD1306_ADDRESS_L:
type = probeOLED(addr);
break;
@@ -382,11 +387,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
if (registerValue == 0x5449) {
if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
type = OPT3001;
logFoundDevice("OPT3001", (uint8_t)addr.address);
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 6) !=
0) { // unique SHT4x serial number (6 bytes inc. CRC)
type = SHT4X;
logFoundDevice("SHT4X", (uint8_t)addr.address);
} else {
@@ -412,7 +417,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case LPS22HB_ADDR_ALT:
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address)
SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310", (uint8_t)addr.address)
SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address)
case QMI8658_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID
@@ -442,7 +447,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#ifdef HAS_QMA6100P
SCAN_SIMPLE_CASE(QMA6100P_ADDR, QMA6100P, "QMA6100P", (uint8_t)addr.address)
#else
SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031", (uint8_t)addr.address)
SCAN_SIMPLE_CASE(PMSA003I_ADDR, PMSA003I, "PMSA003I", (uint8_t)addr.address)
#endif
case BMA423_ADDR: // this can also be LIS3DH_ADDR_ALT
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2);
@@ -487,7 +492,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
case TSL25911_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xA0 | 0x12), 1);
if (registerValue == 0x50) {
type = TSL2591;
logFoundDevice("TSL25911", (uint8_t)addr.address);

41
src/detect/reClockI2C.h Normal file
View File

@@ -0,0 +1,41 @@
#ifdef CAN_RECLOCK_I2C
#include "ScanI2CTwoWire.h"
uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus)
{
uint32_t currentClock;
/* See https://github.com/arduino/Arduino/issues/11457
Currently, only ESP32 can getClock()
While all cores can setClock()
https://github.com/sandeepmistry/arduino-nRF5/blob/master/libraries/Wire/Wire.h#L50
https://github.com/earlephilhower/arduino-pico/blob/master/libraries/Wire/src/Wire.h#L60
https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/Wire/src/Wire.h#L103
For cases when I2C speed is different to the ones defined by sensors (see defines in sensor classes)
we need to reclock I2C and set it back to the previous desired speed.
Only for cases where we can know OR predefine the speed, we can do this.
*/
#ifdef ARCH_ESP32
currentClock = i2cBus->getClock();
#elif defined(ARCH_NRF52)
// TODO add getClock function or return a predefined clock speed per variant?
return 0;
#elif defined(ARCH_RP2040)
// TODO add getClock function or return a predefined clock speed per variant
return 0;
#elif defined(ARCH_STM32WL)
// TODO add getClock function or return a predefined clock speed per variant
return 0;
#else
return 0;
#endif
if (currentClock != desiredClock) {
LOG_DEBUG("Changing I2C clock to %u", desiredClock);
i2cBus->setClock(desiredClock);
}
return currentClock;
}
#endif

View File

@@ -896,18 +896,21 @@ void GPS::writePinEN(bool on)
void GPS::writePinStandby(bool standby)
{
#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones
// Determine the new value for the pin
// Normally: active HIGH for awake
#ifdef PIN_GPS_STANDBY_INVERTED
bool val = standby;
#else
bool val = !standby;
#endif
bool val;
if (standby)
val = GPS_STANDBY_ACTIVE;
else
val = !GPS_STANDBY_ACTIVE;
// Write and log
pinMode(PIN_GPS_STANDBY, OUTPUT);
digitalWrite(PIN_GPS_STANDBY, val);
// Enter backup mode on PA1010D; TODO: may be applicable to other MTK GPS too
if (IS_ONE_OF(gnssModel, GNSS_MODEL_MTK_PA1010D)) {
_serial_gps->write("$PMTK225,4*2F\r\n");
}
#ifdef GPS_DEBUG
LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HI" : "LOW");
#endif
@@ -934,8 +937,11 @@ void GPS::setPowerPMU(bool on)
// t-beam v1.2 GNSS power channel
on ? PMU->enablePowerOutput(XPOWERS_ALDO3) : PMU->disablePowerOutput(XPOWERS_ALDO3);
} else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE) {
// t-beam-s3-core GNSS power channel
// t-beam-s3-core GNSS power channel
on ? PMU->enablePowerOutput(XPOWERS_ALDO4) : PMU->disablePowerOutput(XPOWERS_ALDO4);
} else if (HW_VENDOR == meshtastic_HardwareModel_T_WATCH_S3) {
// t-watch-s3-plus GNSS power channel
on ? PMU->enablePowerOutput(XPOWERS_BLDO1) : PMU->disablePowerOutput(XPOWERS_BLDO1);
}
} else if (model == XPOWERS_AXP192) {
// t-beam v1.1 GNSS power channel

View File

@@ -16,6 +16,11 @@
#define GPS_EN_ACTIVE 1
#endif
// Allow defining the polarity of the STANDBY output. default is LOW for standby
#ifndef GPS_STANDBY_ACTIVE
#define GPS_STANDBY_ACTIVE LOW
#endif
static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL;
static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000;

View File

@@ -397,7 +397,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
return (currentQuality >= minQuality) ? getTime(local) : 0;
}
time_t gm_mktime(struct tm *tm)
time_t gm_mktime(const struct tm *tm)
{
#if !MESHTASTIC_EXCLUDE_TZ
time_t result = 0;
@@ -413,8 +413,8 @@ time_t gm_mktime(struct tm *tm)
days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
// Now, within this tm->year, compute the days *before* this tm->month starts.
int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11
static const int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11
// If this is a leap year, and we're past February, add a day:
if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
@@ -435,6 +435,7 @@ time_t gm_mktime(struct tm *tm)
return result;
#else
return mktime(tm);
struct tm tmCopy = *tm;
return mktime(&tmCopy);
#endif
}

View File

@@ -54,7 +54,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false);
RTCSetResult readFromRTC();
time_t gm_mktime(struct tm *tm);
time_t gm_mktime(const struct tm *tm);
#define SEC_PER_DAY 86400
#define SEC_PER_HOUR 3600

View File

@@ -148,7 +148,7 @@ bool EInkDisplay::connect()
#endif
#endif
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE) || defined(TTGO_T_ECHO_PLUS)
{
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);

View File

@@ -9,6 +9,15 @@
#include "GxEPD2Multi.h"
#endif
// Limit how often we push a full E-Ink refresh. T-Deck Pro needs faster updates for typing.
#ifndef EINK_FORCE_DISPLAY_THROTTLE_MS
#if defined(T_DECK_PRO)
#define EINK_FORCE_DISPLAY_THROTTLE_MS 200
#else
#define EINK_FORCE_DISPLAY_THROTTLE_MS 1000
#endif
#endif
/**
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
*
@@ -42,7 +51,7 @@ class EInkDisplay : public OLEDDisplay
*
* @return true if we did draw the screen
*/
virtual bool forceDisplay(uint32_t msecLimit = 1000);
virtual bool forceDisplay(uint32_t msecLimit = EINK_FORCE_DISPLAY_THROTTLE_MS);
/**
* Run any code needed to complete an update, after the physical refresh has completed.

View File

@@ -312,6 +312,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
// 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;
@@ -319,6 +320,16 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
TFT_MESH = COLOR565(static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(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)
@@ -814,7 +825,7 @@ int32_t Screen::runOnce()
#endif
}
#endif
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0 && !suppressRebootBanner) {
showSimpleBanner("Rebooting...", 0);
}
@@ -1429,10 +1440,15 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
}
nodeDB->updateGUI = false;
break;
case STATUS_TYPE_POWER:
forceDisplay(true);
case STATUS_TYPE_POWER: {
bool currentUSB = powerStatus->getHasUSB();
if (currentUSB != lastPowerUSBState) {
lastPowerUSBState = currentUSB;
forceDisplay(true);
}
break;
}
}
return 0;
}

View File

@@ -558,6 +558,42 @@ class Screen : public concurrency::OSThread
if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5)
return (uint8_t)0;
#endif
#if defined(OLED_GR)
switch (last) {
case 0xC3: {
SKIPREST = false;
return (uint8_t)(ch | 0xC0);
}
// Map UTF-8 Greek chars to Windows-1253 (CP-1253) ASCII codes
case 0xCE: {
SKIPREST = false;
// Uppercase Greek: Α-Ρ (U+0391-U+03A1) -> CP-1253 193-209
if (ch >= 145 && ch <= 161)
return (uint8_t)(ch + 48);
// Uppercase Greek: Σ-Ω (U+03A3-U+03A9) -> CP-1253 211-217
else if (ch >= 163 && ch <= 169)
return (uint8_t)(ch + 48);
// Lowercase Greek: α-ρ (U+03B1-U+03C1) -> CP-1253 225-241
else if (ch >= 177 && ch <= 193)
return (uint8_t)(ch + 48);
break;
}
case 0xCF: {
SKIPREST = false;
// Lowercase Greek: ς-ω (U+03C2-U+03C9) -> CP-1253 242-249
if (ch >= 130 && ch <= 137)
return (uint8_t)(ch + 112);
break;
}
}
// We want to strip out prefix chars for two-byte Greek char formats
if (ch == 0xC2 || ch == 0xC3 || ch == 0xCE || ch == 0xCF)
return (uint8_t)0;
#endif
// If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the
@@ -715,6 +751,8 @@ class Screen : public concurrency::OSThread
// Whether we are showing the regular screen (as opposed to booth screen or
// Bluetooth PIN screen)
bool showingNormalScreen = false;
/// Track USB power state to only wake screen on actual power state changes
bool lastPowerUSBState = false;
// Implementation to Adjust Brightness
uint8_t brightness = BRIGHTNESS_DEFAULT; // H = 254, MH = 192, ML = 130 L = 103

View File

@@ -16,10 +16,17 @@
#include "graphics/fonts/OLEDDisplayFontsCS.h"
#endif
#ifdef OLED_GR
#include "graphics/fonts/OLEDDisplayFontsGR.h"
#endif
#if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
#include "graphics/fonts/EinkDisplayFonts.h"
#endif
#ifdef OLED_GR
#define FONT_SMALL_LOCAL ArialMT_Plain_10_GR // Height: 13
#else
#ifdef OLED_PL
#define FONT_SMALL_LOCAL ArialMT_Plain_10_PL
#else
@@ -37,6 +44,10 @@
#endif
#endif
#endif
#endif
#ifdef OLED_GR
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_GR // Height: 19
#else
#ifdef OLED_PL
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_PL // Height: 19
#else
@@ -54,6 +65,10 @@
#endif
#endif
#endif
#endif
#ifdef OLED_GR
#define FONT_LARGE_LOCAL ArialMT_Plain_24_GR // Height: 28
#else
#ifdef OLED_PL
#define FONT_LARGE_LOCAL ArialMT_Plain_24_PL // Height: 28
#else
@@ -71,6 +86,7 @@
#endif
#endif
#endif
#endif
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \

View File

@@ -438,7 +438,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
if (currentResolution == ScreenResolution::UltraLow) {
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
} else {
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz (%d)", freqStr, config.lora.channel_num);
}
}
size_t len = strlen(frequencyslot);

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ class menuHandler
lora_picker,
device_role_picker,
radio_preset_picker,
frequency_slot,
no_timeout_lora_picker,
TZ_picker,
twelve_hour_picker,
@@ -33,7 +34,8 @@ class menuHandler
brightness_picker,
reboot_menu,
shutdown_menu,
add_favorite,
NodePicker_menu,
Manage_Node_menu,
remove_favorite,
test_menu,
number_test,
@@ -55,12 +57,14 @@ class menuHandler
DisplayUnits
};
static screenMenus menuQueue;
static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu
static void OnboardMessage();
static void LoraRegionPicker(uint32_t duration = 30000);
static void loraMenu();
static void DeviceRolePicker();
static void RadioPresetPicker();
static void FrequencySlotPicker();
static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
static void clockMenu();
@@ -90,6 +94,8 @@ class menuHandler
static void BrightnessPickerMenu();
static void rebootMenu();
static void shutdownMenu();
static void NodePicker();
static void ManageNodeMenu();
static void addFavoriteMenu();
static void removeFavoriteMenu();
static void traceRouteMenu();
@@ -128,7 +134,29 @@ template <typename T> 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<meshtastic_Config_LoRaConfig_ModemPreset>;
using LoraRegionOption = MenuOption<meshtastic_Config_LoRaConfig_RegionCode>;
using TimezoneOption = MenuOption<const char *>;
using CompassOption = MenuOption<meshtastic_CompassMode>;
using ScreenColorOption = MenuOption<ScreenColor>;
using GPSToggleOption = MenuOption<meshtastic_Config_PositionConfig_GpsMode>;
using GPSFormatOption = MenuOption<meshtastic_DeviceUIConfig_GpsCoordinateFormat>;
using NodeNameOption = MenuOption<bool>;
using PositionMenuOption = MenuOption<int>;
using ManageNodeOption = MenuOption<int>;
using ClockFaceOption = MenuOption<bool>;
} // namespace graphics
#endif

View File

@@ -6,7 +6,6 @@
#include "MessageStore.h"
#include "NodeDB.h"
#include "UIRenderer.h"
#include "configuration.h"
#include "gps/RTC.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
@@ -20,7 +19,6 @@
// External declarations
extern bool hasUnreadMessage;
extern meshtastic_DeviceState devicestate;
extern graphics::Screen *screen;
using graphics::Emote;
@@ -49,7 +47,7 @@ static inline size_t utf8CharLen(uint8_t c)
}
// Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels
std::string normalizeEmoji(const std::string &s)
static std::string normalizeEmoji(const std::string &s)
{
std::string out;
for (size_t i = 0; i < s.size();) {
@@ -82,6 +80,7 @@ uint32_t pauseStart = 0;
bool waitingToReset = false;
bool scrollStarted = false;
static bool didReset = false;
static constexpr int MESSAGE_BLOCK_GAP = 6;
void scrollUp()
{
@@ -111,22 +110,6 @@ void scrollDown()
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
{
std::string renderLine;
for (size_t i = 0; i < line.size();) {
uint8_t c = (uint8_t)line[i];
size_t len = utf8CharLen(c);
if (c == 0xEF && i + 2 < line.size() && (uint8_t)line[i + 1] == 0xB8 && (uint8_t)line[i + 2] == 0x8F) {
i += 3;
continue;
}
if (c == 0xF0 && i + 3 < line.size() && (uint8_t)line[i + 1] == 0x9F && (uint8_t)line[i + 2] == 0x8F &&
((uint8_t)line[i + 3] >= 0xBB && (uint8_t)line[i + 3] <= 0xBF)) {
i += 4;
continue;
}
renderLine.append(line, i, len);
i += len;
}
int cursorX = x;
const int fontHeight = FONT_HEIGHT_SMALL;
@@ -203,8 +186,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
// Render the emote (if found)
if (matchedEmote && i == nextEmotePos) {
// Vertically center emote relative to font baseline (not just midline)
int iconY = fontY + (fontHeight - matchedEmote->height) / 2;
int iconY = y + (lineHeight - matchedEmote->height) / 2;
display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap);
cursorX += matchedEmote->width + 1;
i += emojiLen;
@@ -423,6 +405,102 @@ static inline int getRenderedLineWidth(OLEDDisplay *display, const std::string &
return totalWidth;
}
struct MessageBlock {
size_t start;
size_t end;
bool mine;
};
static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool isHeaderLine)
{
if (isHeaderLine) {
return lineTopY + (FONT_HEIGHT_SMALL - 1);
}
int tallest = FONT_HEIGHT_SMALL;
for (int e = 0; e < numEmotes; ++e) {
if (line.find(emotes[e].label) != std::string::npos) {
if (emotes[e].height > tallest)
tallest = emotes[e].height;
}
}
const int lineHeight = std::max(FONT_HEIGHT_SMALL, tallest);
const int iconTop = lineTopY + (lineHeight - tallest) / 2;
return iconTop + tallest - 1;
}
static void drawRoundedRectOutline(OLEDDisplay *display, int x, int y, int w, int h, int r)
{
if (w <= 1 || h <= 1)
return;
if (r < 0)
r = 0;
int maxR = (std::min(w, h) / 2) - 1;
if (r > maxR)
r = maxR;
if (r == 0) {
display->drawRect(x, y, w, h);
return;
}
const int x0 = x;
const int y0 = y;
const int x1 = x + w - 1;
const int y1 = y + h - 1;
// sides
if (x0 + r <= x1 - r) {
display->drawLine(x0 + r, y0, x1 - r, y0); // top
display->drawLine(x0 + r, y1, x1 - r, y1); // bottom
}
if (y0 + r <= y1 - r) {
display->drawLine(x0, y0 + r, x0, y1 - r); // left
display->drawLine(x1, y0 + r, x1, y1 - r); // right
}
// corner arcs
display->drawCircleQuads(x0 + r, y0 + r, r, 2); // top left
display->drawCircleQuads(x1 - r, y0 + r, r, 1); // top right
display->drawCircleQuads(x1 - r, y1 - r, r, 8); // bottom right
display->drawCircleQuads(x0 + r, y1 - r, r, 4); // bottom left
}
static std::vector<MessageBlock> buildMessageBlocks(const std::vector<bool> &isHeaderVec, const std::vector<bool> &isMineVec)
{
std::vector<MessageBlock> blocks;
if (isHeaderVec.empty())
return blocks;
size_t start = 0;
bool mine = isMineVec[0];
for (size_t i = 1; i < isHeaderVec.size(); ++i) {
if (isHeaderVec[i]) {
MessageBlock b;
b.start = start;
b.end = i - 1;
b.mine = mine;
blocks.push_back(b);
start = i;
mine = isMineVec[i];
}
}
MessageBlock last;
last.start = start;
last.end = isHeaderVec.size() - 1;
last.mine = mine;
blocks.push_back(last);
return blocks;
}
static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY)
{
if (totalHeight <= visibleHeight)
@@ -482,9 +560,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
constexpr int LEFT_MARGIN = 2;
constexpr int RIGHT_MARGIN = 2;
constexpr int SCROLLBAR_WIDTH = 3;
constexpr int BUBBLE_PAD_X = 3;
constexpr int BUBBLE_PAD_Y = 4;
constexpr int BUBBLE_RADIUS = 4;
constexpr int BUBBLE_MIN_W = 24;
constexpr int BUBBLE_TEXT_INDENT = 2;
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN;
// Derived widths
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (BUBBLE_PAD_X * 2);
const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH;
// Title string depending on mode
@@ -547,7 +630,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
char chanType[32] = "";
if (currentMode == ThreadMode::ALL) {
if (m.dest == NODENUM_BROADCAST) {
snprintf(chanType, sizeof(chanType), "#%s", channels.getName(m.channelIndex));
const char *name = channels.getName(m.channelIndex);
if (currentResolution == ScreenResolution::Low || currentResolution == ScreenResolution::UltraLow) {
if (strcmp(name, "ShortTurbo") == 0)
name = "ShortT";
else if (strcmp(name, "ShortSlow") == 0)
name = "ShortS";
else if (strcmp(name, "ShortFast") == 0)
name = "ShortF";
else if (strcmp(name, "MediumSlow") == 0)
name = "MedS";
else if (strcmp(name, "MediumFast") == 0)
name = "MedF";
else if (strcmp(name, "LongSlow") == 0)
name = "LongS";
else if (strcmp(name, "LongFast") == 0)
name = "LongF";
else if (strcmp(name, "LongTurbo") == 0)
name = "LongT";
else if (strcmp(name, "LongMod") == 0)
name = "LongM";
}
snprintf(chanType, sizeof(chanType), "#%s", name);
} else {
snprintf(chanType, sizeof(chanType), "(DM)");
}
@@ -614,8 +718,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
}
// Shrink Sender name if needed
int availWidth = SCREEN_WIDTH - display->getStringWidth(timeBuf) - display->getStringWidth(chanType) -
display->getStringWidth(" @...") - 10;
int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) -
display->getStringWidth(chanType) - display->getStringWidth(" @...");
if (availWidth < 0)
availWidth = 0;
@@ -667,6 +771,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
cachedLines = allLines;
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine);
// Scrolling logic (unchanged)
int totalHeight = 0;
for (size_t i = 0; i < cachedHeights.size(); ++i)
@@ -714,12 +820,123 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
int finalScroll = (int)scrollY;
int yOffset = -finalScroll + getTextPositions(display)[1];
const int contentTop = getTextPositions(display)[1];
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);
std::vector<int> lineTop;
lineTop.resize(cachedLines.size());
{
int acc = 0;
for (size_t i = 0; i < cachedLines.size(); ++i) {
lineTop[i] = yOffset + acc;
acc += cachedHeights[i];
}
}
// Draw bubbles
for (size_t bi = 0; bi < blocks.size(); ++bi) {
const auto &b = blocks[bi];
if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end)
continue;
int visualTop = lineTop[b.start];
int topY;
if (isHeader[b.start]) {
// Header start
constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2
topY = visualTop - BUBBLE_PAD_TOP_HEADER;
} else {
// Body start
bool thisLineHasEmote = false;
for (int e = 0; e < numEmotes; ++e) {
if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) {
thisLineHasEmote = true;
break;
}
}
if (thisLineHasEmote) {
constexpr int EMOTE_PADDING_ABOVE = 4;
visualTop -= EMOTE_PADDING_ABOVE;
}
topY = visualTop - BUBBLE_PAD_Y;
}
int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]);
int bottomY = visualBottom + BUBBLE_PAD_Y;
if (bi + 1 < blocks.size()) {
int nextHeaderIndex = (int)blocks[bi + 1].start;
int nextTop = lineTop[nextHeaderIndex];
int maxBottom = nextTop - 1 - bubbleGapY;
if (bottomY > maxBottom)
bottomY = maxBottom;
}
if (bottomY <= topY + 2)
continue;
if (bottomY < contentTop || topY > contentBottom - 1)
continue;
int maxLineW = 0;
for (size_t i = b.start; i <= b.end; ++i) {
int w = 0;
if (isHeader[i]) {
w = display->getStringWidth(cachedLines[i].c_str());
if (b.mine)
w += 12; // room for ACK/NACK/relay mark
} else {
w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
}
if (w > maxLineW)
maxLineW = w;
}
int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (BUBBLE_PAD_X * 2));
int bubbleH = (bottomY - topY) + 1;
int bubbleX = 0;
if (b.mine) {
bubbleX = rightEdge - bubbleW;
} else {
bubbleX = x;
}
if (bubbleX < x)
bubbleX = x;
if (bubbleX + bubbleW > rightEdge)
bubbleW = std::max(1, rightEdge - bubbleX);
if (bubbleW > 1 && bubbleH > 1) {
int r = BUBBLE_RADIUS;
int maxR = (std::min(bubbleW, bubbleH) / 2) - 1;
if (maxR < 0)
maxR = 0;
if (r > maxR)
r = maxR;
drawRoundedRectOutline(display, bubbleX, topY, bubbleW, bubbleH, r);
const int extra = 3;
const int rr = r + extra;
int x1 = bubbleX + bubbleW - 1;
int y1 = topY + bubbleH - 1;
if (!b.mine) {
// top-left corner square
display->drawLine(bubbleX, topY, bubbleX + rr, topY);
display->drawLine(bubbleX, topY, bubbleX, topY + rr);
} else {
// bottom-right corner square
display->drawLine(x1 - rr, y1, x1, y1);
display->drawLine(x1, y1 - rr, x1, y1);
}
}
}
// Render visible lines
int lineY = yOffset;
for (size_t i = 0; i < cachedLines.size(); ++i) {
int lineY = yOffset;
for (size_t j = 0; j < i; ++j)
lineY += cachedHeights[j];
if (lineY > -cachedHeights[i] && lineY < scrollBottom) {
if (isHeader[i]) {
@@ -728,14 +945,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
int headerX;
if (isMine[i]) {
// push header left to avoid overlap with scrollbar
headerX = SCREEN_WIDTH - w - SCROLLBAR_WIDTH - RIGHT_MARGIN;
headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - BUBBLE_TEXT_INDENT;
if (headerX < LEFT_MARGIN)
headerX = LEFT_MARGIN;
} else {
headerX = x;
headerX = x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT;
}
display->drawString(headerX, lineY, cachedLines[i].c_str());
// Draw underline just under header text
int underlineY = lineY + FONT_HEIGHT_SMALL;
int underlineW = w;
int maxW = rightEdge - headerX;
if (maxW < 0)
maxW = 0;
if (underlineW > maxW)
underlineW = maxW;
for (int px = 0; px < underlineW; ++px) {
display->setPixel(headerX + px, underlineY);
}
// Draw ACK/NACK mark for our own messages
if (isMine[i]) {
int markX = headerX - 10;
@@ -753,32 +984,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// AckStatus::NONE → show nothing
}
// Draw underline just under header text
int underlineY = lineY + FONT_HEIGHT_SMALL;
for (int px = 0; px < w; ++px) {
display->setPixel(headerX + px, underlineY);
}
} else {
// Render message line
if (isMine[i]) {
// Calculate actual rendered width including emotes
int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
int rightX = SCREEN_WIDTH - renderedWidth - SCROLLBAR_WIDTH - RIGHT_MARGIN;
int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - BUBBLE_TEXT_INDENT;
if (rightX < LEFT_MARGIN)
rightX = LEFT_MARGIN;
drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes);
} else {
drawStringWithEmotes(display, x, lineY, cachedLines[i], emotes, numEmotes);
drawStringWithEmotes(display, x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT, lineY, cachedLines[i], emotes,
numEmotes);
}
}
}
lineY += cachedHeights[i];
}
int totalContentHeight = totalHeight;
int visibleHeight = usableHeight;
// Draw scrollbar
drawMessageScrollbar(display, visibleHeight, totalContentHeight, finalScroll, getTextPositions(display)[1]);
drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]);
graphics::drawCommonHeader(display, x, y, titleStr);
graphics::drawCommonFooter(display, x, y);
}
@@ -841,7 +1068,6 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line
constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn)
constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines
constexpr int MESSAGE_BLOCK_GAP = 4; // gap after a message block before a new header
constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above)
constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line)
@@ -851,6 +1077,7 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
for (size_t idx = 0; idx < lines.size(); ++idx) {
const auto &line = lines[idx];
const int baseHeight = FONT_HEIGHT_SMALL;
int lineHeight = baseHeight;
// Detect if THIS line or NEXT line contains an emote
bool hasEmote = false;
@@ -872,8 +1099,6 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
}
}
int lineHeight = baseHeight;
if (isHeaderVec[idx]) {
// Header line spacing
lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP;
@@ -922,7 +1147,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht
// Banner logic
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
char longName[48] = "???";
char longName[48] = "?";
if (node && node->user.long_name) {
strncpy(longName, node->user.long_name, sizeof(longName) - 1);
longName[sizeof(longName) - 1] = '\0';

View File

@@ -176,6 +176,7 @@ int calculateMaxScroll(int totalEntries, int visibleRows)
void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd)
{
x = (currentResolution == ScreenResolution::High) ? x - 2 : (currentResolution == ScreenResolution::Low) ? x - 1 : x;
for (int y = yStart; y <= yEnd; y += 2) {
display->setPixel(x, y);
}
@@ -205,9 +206,11 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
{
bool isLeftCol = (x < SCREEN_WIDTH / 2);
int nameMaxWidth = columnWidth - 25;
int timeOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char timeStr[10];
uint32_t seconds = sinceLastSeen(node);
@@ -234,6 +237,13 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
int rightEdge = x + columnWidth - timeOffset;
if (timeStr[strlen(timeStr) - 1] == 'm') // Fix the fact that our fonts don't line up well all the time
@@ -253,6 +263,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
int barsXOffset = columnWidth - barsOffset;
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -265,6 +276,13 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
// Draw signal strength bars
int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
@@ -298,6 +316,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char distStr[10] = "";
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -358,6 +377,13 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
if (strlen(distStr) > 0) {
int offset = (currentResolution == ScreenResolution::High)
@@ -392,6 +418,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -403,6 +430,13 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
}
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,

View File

@@ -0,0 +1,429 @@
#ifdef OLED_GR
#include "OLEDDisplayFontsGR.h"
/**
* Greek font for OLED displays - ArialMT Plain 10pt
* Contains ASCII 32-127 + Greek characters mapped to CP-1253 positions (192-254)
*
* Generated using ThingPulse OLED font converter
* Font: Arial, Size: 10px
* Character set: Basic Latin + Greek (Α-Ω, α-ω, accented)
*
* CP-1253 Greek character mapping:
* 193-209: Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ
* 211-217: Σ Τ Υ Φ Χ Ψ Ω
* 225-241: α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ
* 242-249: ς σ τ υ φ χ ψ ω
*/
const uint8_t ArialMT_Plain_10_GR[] PROGMEM = {
0x0A, // Width: 10
0x0D, // Height: 13
0x20, // First char: 32
0xE0, // Number of chars: 224
// Jump Table (4 bytes per character: offset high, offset low, size, width)
// Characters 32-127: Standard ASCII
0xFF, 0xFF, 0x00, 0x03, // 32 space
0x00, 0x00, 0x04, 0x03, // 33 !
0x00, 0x04, 0x05, 0x04, // 34 "
0x00, 0x09, 0x09, 0x06, // 35 #
0x00, 0x12, 0x0A, 0x06, // 36 $
0x00, 0x1C, 0x10, 0x09, // 37 %
0x00, 0x2C, 0x0E, 0x08, // 38 &
0x00, 0x3A, 0x01, 0x02, // 39 '
0x00, 0x3B, 0x06, 0x04, // 40 (
0x00, 0x41, 0x06, 0x04, // 41 )
0x00, 0x47, 0x05, 0x04, // 42 *
0x00, 0x4C, 0x09, 0x06, // 43 +
0x00, 0x55, 0x04, 0x03, // 44 ,
0x00, 0x59, 0x03, 0x03, // 45 -
0x00, 0x5C, 0x04, 0x03, // 46 .
0x00, 0x60, 0x05, 0x04, // 47 /
0x00, 0x65, 0x0A, 0x06, // 48 0
0x00, 0x6F, 0x08, 0x05, // 49 1
0x00, 0x77, 0x0A, 0x06, // 50 2
0x00, 0x81, 0x0A, 0x06, // 51 3
0x00, 0x8B, 0x0B, 0x07, // 52 4
0x00, 0x96, 0x0A, 0x06, // 53 5
0x00, 0xA0, 0x0A, 0x06, // 54 6
0x00, 0xAA, 0x09, 0x06, // 55 7
0x00, 0xB3, 0x0A, 0x06, // 56 8
0x00, 0xBD, 0x0A, 0x06, // 57 9
0x00, 0xC7, 0x04, 0x03, // 58 :
0x00, 0xCB, 0x04, 0x03, // 59 ;
0x00, 0xCF, 0x0A, 0x06, // 60 <
0x00, 0xD9, 0x09, 0x06, // 61 =
0x00, 0xE2, 0x09, 0x06, // 62 >
0x00, 0xEB, 0x0B, 0x07, // 63 ?
0x00, 0xF6, 0x14, 0x0B, // 64 @
0x01, 0x0A, 0x0E, 0x08, // 65 A
0x01, 0x18, 0x0C, 0x07, // 66 B
0x01, 0x24, 0x0C, 0x07, // 67 C
0x01, 0x30, 0x0B, 0x07, // 68 D
0x01, 0x3B, 0x0C, 0x07, // 69 E
0x01, 0x47, 0x09, 0x06, // 70 F
0x01, 0x50, 0x0D, 0x08, // 71 G
0x01, 0x5D, 0x0C, 0x07, // 72 H
0x01, 0x69, 0x04, 0x03, // 73 I
0x01, 0x6D, 0x08, 0x05, // 74 J
0x01, 0x75, 0x0E, 0x08, // 75 K
0x01, 0x83, 0x0C, 0x07, // 76 L
0x01, 0x8F, 0x10, 0x09, // 77 M
0x01, 0x9F, 0x0C, 0x07, // 78 N
0x01, 0xAB, 0x0E, 0x08, // 79 O
0x01, 0xB9, 0x0B, 0x07, // 80 P
0x01, 0xC4, 0x0E, 0x08, // 81 Q
0x01, 0xD2, 0x0C, 0x07, // 82 R
0x01, 0xDE, 0x0C, 0x07, // 83 S
0x01, 0xEA, 0x0B, 0x07, // 84 T
0x01, 0xF5, 0x0C, 0x07, // 85 U
0x02, 0x01, 0x0D, 0x08, // 86 V
0x02, 0x0E, 0x11, 0x0A, // 87 W
0x02, 0x1F, 0x0E, 0x08, // 88 X
0x02, 0x2D, 0x0D, 0x08, // 89 Y
0x02, 0x3A, 0x0C, 0x07, // 90 Z
0x02, 0x46, 0x06, 0x04, // 91 [
0x02, 0x4C, 0x06, 0x04, // 92 backslash
0x02, 0x52, 0x04, 0x03, // 93 ]
0x02, 0x56, 0x09, 0x06, // 94 ^
0x02, 0x5F, 0x0C, 0x07, // 95 _
0x02, 0x6B, 0x03, 0x03, // 96 `
0x02, 0x6E, 0x0A, 0x06, // 97 a
0x02, 0x78, 0x0A, 0x06, // 98 b
0x02, 0x82, 0x0A, 0x06, // 99 c
0x02, 0x8C, 0x0A, 0x06, // 100 d
0x02, 0x96, 0x0A, 0x06, // 101 e
0x02, 0xA0, 0x05, 0x04, // 102 f
0x02, 0xA5, 0x0A, 0x06, // 103 g
0x02, 0xAF, 0x0A, 0x06, // 104 h
0x02, 0xB9, 0x04, 0x03, // 105 i
0x02, 0xBD, 0x04, 0x03, // 106 j
0x02, 0xC1, 0x08, 0x05, // 107 k
0x02, 0xC9, 0x04, 0x03, // 108 l
0x02, 0xCD, 0x10, 0x09, // 109 m
0x02, 0xDD, 0x0A, 0x06, // 110 n
0x02, 0xE7, 0x0A, 0x06, // 111 o
0x02, 0xF1, 0x0A, 0x06, // 112 p
0x02, 0xFB, 0x0A, 0x06, // 113 q
0x03, 0x05, 0x05, 0x04, // 114 r
0x03, 0x0A, 0x08, 0x05, // 115 s
0x03, 0x12, 0x06, 0x04, // 116 t
0x03, 0x18, 0x0A, 0x06, // 117 u
0x03, 0x22, 0x09, 0x06, // 118 v
0x03, 0x2B, 0x0E, 0x08, // 119 w
0x03, 0x39, 0x0A, 0x06, // 120 x
0x03, 0x43, 0x09, 0x06, // 121 y
0x03, 0x4C, 0x0A, 0x06, // 122 z
0x03, 0x56, 0x06, 0x04, // 123 {
0x03, 0x5C, 0x04, 0x03, // 124 |
0x03, 0x60, 0x05, 0x04, // 125 }
0x03, 0x65, 0x09, 0x06, // 126 ~
0xFF, 0xFF, 0x00, 0x03, // 127
// Characters 128-191: Placeholders (extended ASCII)
0xFF, 0xFF, 0x00, 0x03, // 128
0xFF, 0xFF, 0x00, 0x03, // 129
0xFF, 0xFF, 0x00, 0x03, // 130
0xFF, 0xFF, 0x00, 0x03, // 131
0xFF, 0xFF, 0x00, 0x03, // 132
0xFF, 0xFF, 0x00, 0x03, // 133
0xFF, 0xFF, 0x00, 0x03, // 134
0xFF, 0xFF, 0x00, 0x03, // 135
0xFF, 0xFF, 0x00, 0x03, // 136
0xFF, 0xFF, 0x00, 0x03, // 137
0xFF, 0xFF, 0x00, 0x03, // 138
0xFF, 0xFF, 0x00, 0x03, // 139
0xFF, 0xFF, 0x00, 0x03, // 140
0xFF, 0xFF, 0x00, 0x03, // 141
0xFF, 0xFF, 0x00, 0x03, // 142
0xFF, 0xFF, 0x00, 0x03, // 143
0xFF, 0xFF, 0x00, 0x03, // 144
0xFF, 0xFF, 0x00, 0x03, // 145
0xFF, 0xFF, 0x00, 0x03, // 146
0xFF, 0xFF, 0x00, 0x03, // 147
0xFF, 0xFF, 0x00, 0x03, // 148
0xFF, 0xFF, 0x00, 0x03, // 149
0xFF, 0xFF, 0x00, 0x03, // 150
0xFF, 0xFF, 0x00, 0x03, // 151
0xFF, 0xFF, 0x00, 0x03, // 152
0xFF, 0xFF, 0x00, 0x03, // 153
0xFF, 0xFF, 0x00, 0x03, // 154
0xFF, 0xFF, 0x00, 0x03, // 155
0xFF, 0xFF, 0x00, 0x03, // 156
0xFF, 0xFF, 0x00, 0x03, // 157
0xFF, 0xFF, 0x00, 0x03, // 158
0xFF, 0xFF, 0x00, 0x03, // 159
0xFF, 0xFF, 0x00, 0x03, // 160
0xFF, 0xFF, 0x00, 0x03, // 161
0xFF, 0xFF, 0x00, 0x03, // 162
0xFF, 0xFF, 0x00, 0x03, // 163
0xFF, 0xFF, 0x00, 0x03, // 164
0xFF, 0xFF, 0x00, 0x03, // 165
0xFF, 0xFF, 0x00, 0x03, // 166
0xFF, 0xFF, 0x00, 0x03, // 167
0xFF, 0xFF, 0x00, 0x03, // 168
0xFF, 0xFF, 0x00, 0x03, // 169
0xFF, 0xFF, 0x00, 0x03, // 170
0xFF, 0xFF, 0x00, 0x03, // 171
0xFF, 0xFF, 0x00, 0x03, // 172
0xFF, 0xFF, 0x00, 0x03, // 173
0xFF, 0xFF, 0x00, 0x03, // 174
0xFF, 0xFF, 0x00, 0x03, // 175
0xFF, 0xFF, 0x00, 0x03, // 176
0xFF, 0xFF, 0x00, 0x03, // 177
0xFF, 0xFF, 0x00, 0x03, // 178
0xFF, 0xFF, 0x00, 0x03, // 179
0xFF, 0xFF, 0x00, 0x03, // 180
0xFF, 0xFF, 0x00, 0x03, // 181
0xFF, 0xFF, 0x00, 0x03, // 182
0xFF, 0xFF, 0x00, 0x03, // 183
0xFF, 0xFF, 0x00, 0x03, // 184
0xFF, 0xFF, 0x00, 0x03, // 185
0xFF, 0xFF, 0x00, 0x03, // 186
0xFF, 0xFF, 0x00, 0x03, // 187
0xFF, 0xFF, 0x00, 0x03, // 188
0xFF, 0xFF, 0x00, 0x03, // 189
0xFF, 0xFF, 0x00, 0x03, // 190
0xFF, 0xFF, 0x00, 0x03, // 191
// Characters 192-255: Greek letters (CP-1253 positions)
0xFF, 0xFF, 0x00, 0x03, // 192 (unused)
0x03, 0x6E, 0x0E, 0x08, // 193 Α Alpha
0x03, 0x7C, 0x0C, 0x07, // 194 Β Beta
0x03, 0x88, 0x09, 0x06, // 195 Γ Gamma
0x03, 0x91, 0x0C, 0x07, // 196 Δ Delta
0x03, 0x9D, 0x0C, 0x07, // 197 Ε Epsilon
0x03, 0xA9, 0x0A, 0x06, // 198 Ζ Zeta
0x03, 0xB3, 0x0C, 0x07, // 199 Η Eta
0x03, 0xBF, 0x0E, 0x08, // 200 Θ Theta
0x03, 0xCD, 0x04, 0x03, // 201 Ι Iota
0x03, 0xD1, 0x0E, 0x08, // 202 Κ Kappa
0x03, 0xDF, 0x0E, 0x08, // 203 Λ Lambda
0x03, 0xED, 0x10, 0x09, // 204 Μ Mu
0x03, 0xFD, 0x0C, 0x07, // 205 Ν Nu
0x04, 0x09, 0x0C, 0x07, // 206 Ξ Xi
0x04, 0x15, 0x0E, 0x08, // 207 Ο Omicron
0x04, 0x23, 0x0C, 0x07, // 208 Π Pi
0x04, 0x2F, 0x0B, 0x07, // 209 Ρ Rho
0xFF, 0xFF, 0x00, 0x03, // 210 (unused)
0x04, 0x3A, 0x0C, 0x07, // 211 Σ Sigma
0x04, 0x46, 0x0B, 0x07, // 212 Τ Tau
0x04, 0x51, 0x0D, 0x08, // 213 Υ Upsilon
0x04, 0x5E, 0x0E, 0x08, // 214 Φ Phi
0x04, 0x6C, 0x0E, 0x08, // 215 Χ Chi
0x04, 0x7A, 0x0E, 0x08, // 216 Ψ Psi
0x04, 0x88, 0x0E, 0x08, // 217 Ω Omega
0xFF, 0xFF, 0x00, 0x03, // 218
0xFF, 0xFF, 0x00, 0x03, // 219
0xFF, 0xFF, 0x00, 0x03, // 220
0xFF, 0xFF, 0x00, 0x03, // 221
0xFF, 0xFF, 0x00, 0x03, // 222
0xFF, 0xFF, 0x00, 0x03, // 223
0xFF, 0xFF, 0x00, 0x03, // 224
0x04, 0x96, 0x0A, 0x06, // 225 α alpha
0x04, 0xA0, 0x0A, 0x06, // 226 β beta
0x04, 0xAA, 0x09, 0x06, // 227 γ gamma
0x04, 0xB3, 0x0A, 0x06, // 228 δ delta
0x04, 0xBD, 0x08, 0x05, // 229 ε epsilon
0x04, 0xC5, 0x08, 0x05, // 230 ζ zeta
0x04, 0xCD, 0x0A, 0x06, // 231 η eta
0x04, 0xD7, 0x0A, 0x06, // 232 θ theta
0x04, 0xE1, 0x04, 0x03, // 233 ι iota
0x04, 0xE5, 0x08, 0x05, // 234 κ kappa
0x04, 0xED, 0x0A, 0x06, // 235 λ lambda
0x04, 0xF7, 0x0A, 0x06, // 236 μ mu
0x05, 0x01, 0x08, 0x05, // 237 ν nu
0x05, 0x09, 0x0A, 0x06, // 238 ξ xi
0x05, 0x13, 0x0A, 0x06, // 239 ο omicron
0x05, 0x1D, 0x0A, 0x06, // 240 π pi
0x05, 0x27, 0x0A, 0x06, // 241 ρ rho
0x05, 0x31, 0x08, 0x05, // 242 ς final sigma
0x05, 0x39, 0x0A, 0x06, // 243 σ sigma
0x05, 0x43, 0x06, 0x04, // 244 τ tau
0x05, 0x49, 0x0A, 0x06, // 245 υ upsilon
0x05, 0x53, 0x0C, 0x07, // 246 φ phi
0x05, 0x5F, 0x0A, 0x06, // 247 χ chi
0x05, 0x69, 0x0C, 0x07, // 248 ψ psi
0x05, 0x75, 0x0C, 0x07, // 249 ω omega
0xFF, 0xFF, 0x00, 0x03, // 250
0xFF, 0xFF, 0x00, 0x03, // 251
0xFF, 0xFF, 0x00, 0x03, // 252
0xFF, 0xFF, 0x00, 0x03, // 253
0xFF, 0xFF, 0x00, 0x03, // 254
0xFF, 0xFF, 0x00, 0x03, // 255
// Font Data - Basic ASCII (32-127)
0x00, 0x00, 0xF8, 0x02, // 33 !
0x38, 0x00, 0x00, 0x00, 0x38, // 34 "
0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35 #
0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36 $
0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37 %
0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38 &
0x38, // 39 '
0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40 (
0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41 )
0x28, 0x00, 0x18, 0x00, 0x28, // 42 *
0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43 +
0x00, 0x00, 0x00, 0x06, // 44 ,
0x80, 0x00, 0x80, // 45 -
0x00, 0x00, 0x00, 0x02, // 46 .
0x00, 0x03, 0xE0, 0x00, 0x18, // 47 /
0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48 0
0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49 1
0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50 2
0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51 3
0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52 4
0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53 5
0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54 6
0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55 7
0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56 8
0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57 9
0x00, 0x00, 0x20, 0x02, // 58 :
0x00, 0x00, 0x20, 0x06, // 59 ;
0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60 <
0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61 =
0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62 >
0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63 ?
0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0,
0x04, // 64 @
0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65 A
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66 B
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67 C
0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68 D
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69 E
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70 F
0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71 G
0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72 H
0x00, 0x00, 0xF8, 0x03, // 73 I
0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74 J
0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75 K
0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76 L
0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77 M
0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78 N
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79 O
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80 P
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81 Q
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82 R
0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83 S
0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84 T
0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85 U
0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86 V
0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87 W
0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88 X
0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89 Y
0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90 Z
0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91 [
0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92 backslash
0x08, 0x08, 0xF8, 0x0F, // 93 ]
0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94 ^
0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95 _
0x08, 0x00, 0x10, // 96 `
0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97 a
0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98 b
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99 c
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100 d
0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101 e
0x20, 0x00, 0xF0, 0x03, 0x28, // 102 f
0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103 g
0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104 h
0x00, 0x00, 0xE8, 0x03, // 105 i
0x00, 0x08, 0xE8, 0x07, // 106 j
0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107 k
0x00, 0x00, 0xF8, 0x03, // 108 l
0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109 m
0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110 n
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111 o
0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112 p
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113 q
0x00, 0x00, 0xE0, 0x03, 0x20, // 114 r
0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115 s
0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116 t
0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117 u
0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118 v
0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119 w
0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120 x
0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121 y
0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122 z
0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123 {
0x00, 0x00, 0xF8, 0x0F, // 124 |
0x08, 0x08, 0x78, 0x0F, 0x80, // 125 }
0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126 ~
// Greek uppercase letters (193-217 in CP-1253)
0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // Α Alpha (same as A)
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // Β Beta (same as B)
0x00, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, 0x00, 0x18, // Γ Gamma
0x00, 0x02, 0x80, 0x01, 0x60, 0x00, 0x10, 0x00, 0x60, 0x00, 0x80, 0x01, 0x00, 0x02, // Δ Delta
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // Ε Epsilon (same as E)
0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, // Ζ Zeta (same as Z)
0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // Η Eta (same as H)
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0x08, 0x02, 0xF0, 0x01, // Θ Theta
0x00, 0x00, 0xF8, 0x03, // Ι Iota (same as I)
0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // Κ Kappa (same as K)
0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, // Λ Lambda
0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // Μ Mu (same as M)
0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // Ν Nu (same as N)
0x00, 0x00, 0x48, 0x02, 0x48, 0x02, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, // Ξ Xi
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // Ο Omicron (same as O)
0x00, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, // Π Pi
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // Ρ Rho (same as P)
0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // Σ Sigma
0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // Τ Tau (same as T)
0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // Υ Upsilon (same as Y)
0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x88, 0x00, 0x70, 0x00, 0x00, // Φ Phi
0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // Χ Chi (same as X)
0x00, 0x00, 0x08, 0x00, 0xF0, 0x01, 0x08, 0x02, 0xF8, 0x03, 0x08, 0x02, 0xF0, 0x01, // Ψ Psi
0x00, 0x00, 0x08, 0x02, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, 0x08, 0x02, // Ω Omega
// Greek lowercase letters (225-249 in CP-1253)
0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // α alpha
0x00, 0x00, 0xF8, 0x07, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // β beta
0x00, 0x04, 0x20, 0x02, 0xC0, 0x01, 0x20, 0x00, 0x20, // γ gamma
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x50, 0x01, // δ delta
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, // ε epsilon
0x00, 0x04, 0x00, 0x03, 0xE0, 0x00, 0x18, // ζ zeta
0x00, 0x00, 0xE0, 0x05, 0x20, 0x0A, 0x20, 0x02, 0xC0, 0x01, // η eta
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0xA0, 0x02, 0xC0, 0x01, // θ theta
0x00, 0x00, 0xE0, 0x03, // ι iota
0xE0, 0x03, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // κ kappa
0x00, 0x02, 0x80, 0x01, 0x40, 0x00, 0x20, 0x00, 0xE0, 0x03, // λ lambda
0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // μ mu
0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x03, // ν nu
0x00, 0x04, 0xC0, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, // ξ xi
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // ο omicron
0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, // π pi
0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // ρ rho
0x00, 0x04, 0x00, 0x03, 0xA0, 0x02, 0x40, 0x01, // ς final sigma
0x00, 0x00, 0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // σ sigma
0x20, 0x00, 0xE0, 0x03, 0x20, // τ tau
0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // υ upsilon
0x00, 0x00, 0xC0, 0x00, 0x20, 0x01, 0xE0, 0x03, 0x20, 0x01, 0xC0, // φ phi
0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // χ chi
0x00, 0x00, 0x20, 0x00, 0xC0, 0x05, 0x20, 0x02, 0xE0, 0x03, 0x20, // ψ psi
0x00, 0x00, 0x20, 0x02, 0xC0, 0x01, 0x20, 0x02, 0xC0, 0x01, 0x20, 0x02, // ω omega
};
// Placeholder for 16pt font - needs to be generated with font converter tool
const uint8_t ArialMT_Plain_16_GR[] PROGMEM = {
0x10, // Width: 16
0x13, // Height: 19
0x20, // First Char: 32
0x01, // Number of chars: 1 (placeholder)
// Minimal placeholder - replace with full font data
0xFF, 0xFF, 0x00, 0x04, // 32 space
// Font Data:
// (empty placeholder)
};
// Placeholder for 24pt font - needs to be generated with font converter tool
const uint8_t ArialMT_Plain_24_GR[] PROGMEM = {
0x18, // Width: 24
0x1C, // Height: 28
0x20, // First Char: 32
0x01, // Number of chars: 1 (placeholder)
// Minimal placeholder - replace with full font data
0xFF, 0xFF, 0x00, 0x06, // 32 space
// Font Data:
// (empty placeholder)
};
#endif // OLED_GR

View File

@@ -0,0 +1,22 @@
#ifndef OLEDDISPLAYFONTSGR_h
#define OLEDDISPLAYFONTSGR_h
#ifdef ARDUINO
#include <Arduino.h>
#elif __MBED__
#define PROGMEM
#endif
/**
* Localization for Greek language containing glyphs for the Greek alphabet.
* Uses Windows-1253 (CP-1253) encoding for Greek characters.
*
* Supported characters:
* - Uppercase Greek: Α-Ω (U+0391 to U+03A9)
* - Lowercase Greek: α-ω (U+03B1 to U+03C9)
* - Accented Greek: ά, έ, ή, ί, ό, ύ, ώ, etc.
*/
extern const uint8_t ArialMT_Plain_10_GR[] PROGMEM;
extern const uint8_t ArialMT_Plain_16_GR[] PROGMEM;
extern const uint8_t ArialMT_Plain_24_GR[] PROGMEM;
#endif

View File

@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans12pt_Win1253
*/
const uint8_t FreeSans12pt_Win1253Bitmaps[] PROGMEM = {
/* 0x01 */ 0x00, 0x30, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x24, 0x00, 0x04, 0x80, 0x01, 0x90, 0x00, 0x62, 0x00, 0x30, 0xFE, 0x04, 0x10, 0x5F, 0x02, 0x0B, 0x00, 0x7F, 0xE0, 0x0C, 0x1C, 0x02, 0x83, 0x81, 0x9F, 0xF0, 0x02, 0x1E, 0x00, 0x41, 0xC0, 0x0E, 0x7F, 0x81, 0x78, 0x18, 0x62, 0x00, 0xFF, 0xC0,
/* 0x02 */ 0x00, 0xFF, 0x80, 0x61, 0x13, 0xF0, 0x62, 0x60, 0x07, 0xFC, 0x00, 0x83, 0x80, 0x10, 0xF0, 0x33, 0xF6, 0x01, 0x41, 0xC0, 0x18, 0x38, 0x03, 0xFF, 0xE0, 0x47, 0x02, 0x08, 0x20, 0x61, 0xC4, 0x06, 0x17, 0x00, 0x22, 0x00, 0x02, 0x40, 0x00, 0x48, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x3C, 0x00,
/* 0x03 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x04, 0x08, 0x48, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x08, 0x10, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA1, 0x81, 0x8D, 0x87, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x04 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x10, 0x02, 0x48, 0xE0, 0x61, 0xC1, 0xCC, 0x0E, 0x78, 0x1C, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xFC, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x05 */ 0x00, 0x18, 0x00, 0x00, 0x40, 0x01, 0x90, 0x01, 0xF4, 0x08, 0x12, 0x23, 0xC1, 0x91, 0x2C, 0x1C, 0x8A, 0xC3, 0x64, 0x64, 0x13, 0x22, 0x41, 0x98, 0x26, 0x2C, 0xC4, 0x22, 0x60, 0x42, 0x13, 0x04, 0x30, 0x80, 0x61, 0xA4, 0x02, 0x18, 0x20, 0x03, 0x41, 0x00, 0x20, 0x08, 0x02, 0x00, 0x60, 0x40, 0x03, 0xF8,
/* 0x06 */ 0x00, 0x10, 0x00, 0x03, 0x00, 0x1C, 0x48, 0x00, 0xB4, 0x80, 0x09, 0xF9, 0xC0, 0xE0, 0xE4, 0x0C, 0x02, 0x8F, 0x80, 0x38, 0x88, 0x01, 0x0D, 0x00, 0x18, 0x30, 0x01, 0x60, 0x80, 0x13, 0x18, 0x03, 0xF2, 0xC0, 0x20, 0x26, 0x06, 0x07, 0xFF, 0xA0, 0x02, 0x39, 0x00, 0x14, 0x70, 0x01, 0xC3, 0x00, 0x18, 0x00,
/* 0x07 */
/* 0x08 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x10, 0x37, 0xE2, 0x61, 0x00, 0x0C, 0xC6, 0x10, 0x98, 0x0C, 0x63, 0x00, 0x00, 0xC6, 0x00,
/* 0x09 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x00, 0x37, 0xE0,
/* 0x0A */
/* 0x0B */ 0x1F, 0x07, 0xC1, 0x86, 0x41, 0x10, 0x0C, 0x04, 0x80, 0x40, 0x18, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x00, 0x00, 0x30, 0x00, 0x01, 0x40, 0x00, 0x0A, 0x00, 0x00, 0x88, 0x00, 0x04, 0x40, 0x00, 0x41, 0x00, 0x02, 0x04, 0x00, 0x20, 0x20, 0x02, 0x00, 0x80, 0x20, 0x02, 0x02, 0x00, 0x08, 0x20, 0x00, 0x22, 0x00, 0x00, 0xE0, 0x00,
/* 0x0C */ 0x01, 0x00, 0x00, 0x38, 0x00, 0x04, 0xC0, 0x01, 0x08, 0x00, 0x18, 0x80, 0x1C, 0x10, 0x02, 0x07, 0x80, 0x81, 0x10, 0x1F, 0xC2, 0x02, 0x00, 0x60, 0x80, 0x1A, 0x20, 0x1C, 0x42, 0x1C, 0x08, 0xFE, 0x03, 0xA0, 0x01, 0x8C, 0x01, 0xC1, 0x43, 0xD0, 0x27, 0x81, 0xF8,
/* 0x0D */
/* 0x0E */ 0x00, 0xE0, 0x00, 0x11, 0x00, 0x01, 0x10, 0x00, 0x0B, 0x00, 0x03, 0xF8, 0x00, 0x60, 0x60, 0x09, 0x02, 0x00, 0xA0, 0x10, 0x16, 0x01, 0x01, 0x40, 0x10, 0x10, 0x01, 0x01, 0x00, 0x08, 0x10, 0x00, 0x82, 0x1F, 0x08, 0x3F, 0x90, 0x44, 0x00, 0x06, 0xBF, 0xFF, 0xAF, 0xF0, 0xFF, 0xFF, 0x0F, 0xE3, 0xFB, 0xFC,
/* 0x0F */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x40, 0x12, 0x34, 0x00, 0x69, 0x40, 0x01, 0x49, 0xE0, 0xF1, 0xCD, 0x06, 0x8E, 0x28, 0x14, 0x71, 0x40, 0xA3, 0x8B, 0xFD, 0x14, 0x50, 0x68, 0xA2, 0x81, 0x4D, 0x97, 0xFA, 0x44, 0xBF, 0xD6, 0x31, 0x02, 0xE0, 0xC8, 0x16, 0x08, 0x61, 0x08, 0x21, 0xF0, 0x80, 0xF8, 0x78, 0x00,
/* 0x10 */ 0x00, 0xF0, 0x00, 0x3A, 0x00, 0x07, 0xC0, 0x00, 0xA8, 0x00, 0x1F, 0x00, 0x02, 0xB0, 0x00, 0x52, 0x00, 0x0A, 0x40, 0x02, 0x48, 0x00, 0x49, 0x00, 0x09, 0x30, 0x01, 0x22, 0x01, 0xC4, 0x70, 0xF0, 0x85, 0xE1, 0x10, 0x88, 0x37, 0x20, 0x03, 0x9C, 0x00, 0x37, 0x00, 0x06, 0x40, 0x01, 0x86, 0x00,
/* 0x11 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x60, 0x02, 0x36, 0x00, 0x09, 0x04, 0x0C, 0x48, 0x60, 0xC1, 0xC3, 0x0F, 0x0E, 0x00, 0x08, 0x70, 0x00, 0x23, 0x80, 0x63, 0x84, 0x01, 0x9F, 0x20, 0x0C, 0xFD, 0x80, 0x27, 0xE4, 0x03, 0x3F, 0x30, 0x33, 0xE0, 0xC0, 0x00, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x12 */ 0x00, 0xC2, 0x00, 0x1C, 0x24, 0x02, 0x18, 0x60, 0x64, 0x02, 0x02, 0x40, 0x20, 0x00, 0xF2, 0x03, 0x89, 0xE0, 0x7C, 0x80, 0x0E, 0x25, 0x80, 0xE1, 0x00, 0x1A, 0x08, 0x71, 0xB0, 0xC4, 0x39, 0x84, 0xC2, 0xCC, 0x40, 0x76, 0x7C, 0x05, 0xBB, 0x80, 0x4C, 0xE0, 0x0A, 0x78, 0x00, 0x9C, 0x00, 0x0F, 0x00, 0x00,
/* 0x13 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x60, 0xC1, 0xC6, 0xC9, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xF8, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x14 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x20, 0x22, 0x33, 0x01, 0x89, 0x20, 0x02, 0x48, 0x60, 0xE1, 0xC8, 0x80, 0x8E, 0x46, 0x46, 0x72, 0x32, 0x33, 0x9F, 0x9F, 0x94, 0x78, 0x78, 0xA0, 0x00, 0x0D, 0x80, 0x00, 0x44, 0x0E, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x15 */ 0x03, 0xFC, 0x20, 0x38, 0x1C, 0x81, 0x80, 0x1D, 0x08, 0x00, 0x32, 0x60, 0x00, 0x89, 0x00, 0x02, 0x18, 0x00, 0x08, 0x61, 0xC3, 0x22, 0x8D, 0x93, 0x72, 0x00, 0x00, 0x48, 0x00, 0x01, 0x20, 0x00, 0x04, 0x9F, 0xFF, 0x92, 0x60, 0x0E, 0x44, 0xFF, 0xF2, 0x11, 0xC3, 0x88, 0x21, 0xF8, 0x40, 0x40, 0x02, 0x00, 0xC0, 0x30, 0x00, 0xFF, 0x00,
/* 0x16 */ 0x03, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x01, 0xE0, 0x03, 0xF0, 0x03, 0xF0, 0x27, 0xF0, 0x6F, 0x70, 0x6E, 0x60, 0xFC, 0x60, 0xFC, 0x7E, 0xFC, 0x7E, 0xFC, 0x3F, 0xF4, 0x1F, 0xF4, 0x1F, 0xF0, 0x0E, 0x70, 0x0E, 0x30, 0x1C, 0x38, 0x38, 0x0F, 0xF0,
/* 0x17 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x00, 0x21, 0xC0, 0x02, 0x8E, 0x20, 0xF4, 0x70, 0x84, 0x11, 0x82, 0x40, 0x84, 0x01, 0x03, 0x20, 0x0F, 0x85, 0x80, 0x03, 0x04, 0x00, 0x04, 0x30, 0x78, 0x10, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x18 */ 0x00, 0xFC, 0x00, 0x02, 0x06, 0x00, 0x08, 0x24, 0x00, 0x21, 0xA4, 0x00, 0x4C, 0x48, 0x00, 0xA0, 0x50, 0x01, 0x92, 0x60, 0x03, 0x24, 0xC0, 0x06, 0x01, 0x81, 0x28, 0x03, 0x49, 0x6C, 0xC4, 0xAD, 0xD8, 0x16, 0xA4, 0xCC, 0xC4, 0x44, 0x86, 0x13, 0x05, 0x00, 0x28, 0x0A, 0x00, 0x50, 0x14, 0x00, 0x90, 0x48, 0x01, 0x20, 0x90, 0x02, 0x41, 0x20, 0x00, 0x00,
/* 0x19 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x49, 0xC3, 0x81, 0xC0, 0x00, 0x0E, 0x78, 0xF0, 0x77, 0xEF, 0xC3, 0xA7, 0x4E, 0x15, 0x0A, 0x10, 0xA7, 0x8F, 0x0D, 0x80, 0x00, 0x44, 0x00, 0x06, 0x33, 0xF0, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x1A */ 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0C, 0x3E, 0x18, 0x82, 0x32, 0x02, 0x64, 0x04, 0xC8, 0x09, 0x80, 0x23, 0x00, 0x86, 0x02, 0x0C, 0x08, 0x18, 0x10, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x81, 0x80, 0x03, 0x00, 0x07, 0xFF, 0xF8,
/* 0x1B */ 0x00, 0xFE, 0x00, 0x03, 0x81, 0x80, 0x04, 0x00, 0x60, 0x08, 0x00, 0x30, 0x10, 0x00, 0x10, 0x30, 0x07, 0x88, 0x23, 0xC8, 0x08, 0x22, 0x00, 0x04, 0x60, 0x00, 0x44, 0x60, 0x00, 0x84, 0x63, 0x03, 0x04, 0x61, 0xFC, 0x04, 0x6B, 0x00, 0x9E, 0xA5, 0x01, 0x6A, 0xD5, 0x01, 0x43, 0xA8, 0x81, 0x05, 0xD0, 0x82, 0x0A, 0xA0, 0x82, 0x05, 0xC0, 0x82, 0x02, 0x61, 0xFF, 0x0C, 0x1E, 0x00, 0xF0,
/* 0x1C */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x30, 0x02, 0x32, 0x00, 0x09, 0x00, 0x00, 0x48, 0x20, 0x61, 0xC3, 0x84, 0x0E, 0x1C, 0x78, 0x70, 0x40, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA0, 0x03, 0x0D, 0x83, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x1D */ 0x01, 0xFE, 0x00, 0x3A, 0x1C, 0x03, 0x00, 0x30, 0x23, 0x1E, 0xC3, 0x38, 0x03, 0x10, 0xC3, 0x09, 0x00, 0x18, 0x68, 0x00, 0xC1, 0x40, 0x00, 0x0A, 0x07, 0x80, 0x50, 0x46, 0x02, 0x80, 0x00, 0x1A, 0x1E, 0x00, 0xCB, 0x10, 0x0D, 0x03, 0x00, 0x48, 0x60, 0x06, 0x40, 0x00, 0x22, 0x0C, 0x02, 0x10, 0x60, 0x60, 0x43, 0xFC, 0x01, 0xE0, 0x00, 0x00,
/* 0x1E */ 0x01, 0xF0, 0x00, 0xEA, 0xC0, 0x31, 0x5F, 0x04, 0x5F, 0x88, 0x80, 0xA0, 0x48, 0x0E, 0x02, 0x8F, 0x40, 0x3C, 0x10, 0x21, 0x66, 0x87, 0x15, 0x98, 0x71, 0x41, 0x02, 0x14, 0x00, 0x01, 0x40, 0x00, 0x14, 0x00, 0x01, 0x21, 0xFE, 0x12, 0x00, 0x02, 0x10, 0x00, 0x60, 0x80, 0x0C, 0x06, 0x01, 0x80, 0x3F, 0xE0,
/* 0x1F */ 0x0E, 0x00, 0x13, 0x00, 0x23, 0x00, 0xF3, 0x01, 0x31, 0x01, 0x11, 0x03, 0xD3, 0x06, 0xF2, 0x30, 0x34, 0xC7, 0x25, 0x33, 0x2B, 0xC2, 0x57, 0x04, 0x3A, 0x08, 0x72, 0x30, 0xA3, 0xC3, 0x40, 0x04, 0x40, 0x18, 0x40, 0x60, 0x7F, 0x80,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFF, 0xFF, 0xFF, 0xF0, 0xF0,
/* '"' 0x22 */ 0xCF, 0x3C, 0xF3, 0x8A, 0x20,
/* '#' 0x23 */ 0x06, 0x30, 0x31, 0x03, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30,
/* '$' 0x24 */ 0x04, 0x03, 0xE1, 0xFF, 0x72, 0x7C, 0x47, 0x88, 0xF1, 0x07, 0xA0, 0x7E, 0x03, 0xF0, 0x17, 0x02, 0x7C, 0x47, 0x88, 0xF1, 0x1B, 0x26, 0x7F, 0xC3, 0xE0, 0x10, 0x02, 0x00,
/* '%' 0x25 */ 0x00, 0x06, 0x03, 0xC0, 0x40, 0x7E, 0x0C, 0x0E, 0x70, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xE7, 0x30, 0x07, 0xE6, 0x00, 0x3C, 0x40, 0x00, 0x0C, 0x7C, 0x00, 0x8F, 0xE0, 0x19, 0xC7, 0x01, 0x18, 0x30, 0x31, 0x83, 0x02, 0x1C, 0x70, 0x40, 0xFE, 0x04, 0x07, 0xC0,
/* '&' 0x26 */ 0x0F, 0x00, 0x7E, 0x03, 0x9C, 0x0C, 0x30, 0x30, 0xC0, 0xE7, 0x01, 0xF8, 0x03, 0x80, 0x3E, 0x01, 0xCC, 0x6E, 0x39, 0xB0, 0x7C, 0xC0, 0xF3, 0x03, 0xCE, 0x1F, 0x9F, 0xE6, 0x3E, 0x1C,
/* ''' 0x27 */ 0xFF, 0xA0,
/* '(' 0x28 */ 0x08, 0x8C, 0x46, 0x31, 0x98, 0xC6, 0x31, 0x8C, 0x63, 0x08, 0x63, 0x08, 0x61, 0x0C, 0x20,
/* ')' 0x29 */ 0x82, 0x18, 0xC3, 0x18, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x62, 0x31, 0x88, 0xC4, 0x62, 0x00,
/* '*' 0x2A */ 0x10, 0x23, 0x5B, 0xE3, 0x8D, 0x91, 0x00,
/* '+' 0x2B */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0,
/* ',' 0x2C */ 0xF5, 0x60,
/* '-' 0x2D */ 0xFF, 0xF0,
/* '.' 0x2E */ 0xF0,
/* '/' 0x2F */ 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x00,
/* '0' 0x30 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x6C, 0x0F, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3E, 0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00,
/* '1' 0x31 */ 0x08, 0xCF, 0xFF, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18,
/* '2' 0x32 */ 0x1F, 0x0F, 0xF9, 0x87, 0x60, 0x7C, 0x06, 0x00, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x06, 0x00, 0xC0, 0x30, 0x07, 0xFF, 0xFF, 0xE0,
/* '3' 0x33 */ 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0C, 0x01, 0x80, 0x60, 0x78, 0x0F, 0x80, 0x18, 0x01, 0x80, 0x3C, 0x07, 0x80, 0xD8, 0x73, 0xFC, 0x3F, 0x00,
/* '4' 0x34 */ 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0, 0xD8, 0x1B, 0x06, 0x61, 0x8C, 0x21, 0x8C, 0x33, 0x06, 0x7F, 0xFF, 0xFE, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80,
/* '5' 0x35 */ 0x3F, 0xCF, 0xF9, 0x80, 0x30, 0x06, 0x00, 0xDE, 0x1F, 0xE7, 0x0E, 0x00, 0xE0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x81, 0xB8, 0x73, 0xFC, 0x1F, 0x00,
/* '6' 0x36 */ 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x74, 0x01, 0x80, 0x33, 0xC7, 0xFE, 0xF1, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x06, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00,
/* '7' 0x37 */ 0xFF, 0xFF, 0xFC, 0x01, 0x00, 0x60, 0x18, 0x02, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x04, 0x01, 0x80, 0x30, 0x06, 0x01, 0x80, 0x30, 0x00,
/* '8' 0x38 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x66, 0x0C, 0xC1, 0x8C, 0x61, 0xF8, 0x3F, 0x8E, 0x3B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xD8, 0x31, 0xFC, 0x1F, 0x00,
/* '9' 0x39 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C, 0x07, 0x80, 0xF0, 0x1E, 0x07, 0x61, 0xEF, 0xFC, 0x79, 0x80, 0x30, 0x05, 0xC1, 0x98, 0x73, 0xFC, 0x1E, 0x00,
/* ':' 0x3A */ 0xF0, 0x00, 0x03, 0xC0,
/* ';' 0x3B */ 0xF0, 0x00, 0x0F, 0x56,
/* '<' 0x3C */ 0x00, 0x70, 0x1E, 0x0F, 0x83, 0xC0, 0xF0, 0x0E, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x10,
/* '=' 0x3D */ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
/* '>' 0x3E */ 0xE0, 0x07, 0x80, 0x1F, 0x00, 0x7C, 0x00, 0xF0, 0x07, 0x01, 0xE0, 0xF0, 0x3C, 0x0F, 0x00, 0x80, 0x00,
/* '?' 0x3F */ 0x3F, 0x1F, 0xEE, 0x1F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x03, 0x81, 0xC0, 0xE0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00,
/* '@' 0x40 */ 0x00, 0xFE, 0x00, 0x0F, 0xFE, 0x00, 0xF0, 0x3E, 0x07, 0x00, 0x3C, 0x38, 0x00, 0x38, 0xC1, 0xE0, 0x66, 0x0F, 0xD9, 0xD8, 0x61, 0xC3, 0xC3, 0x07, 0x0F, 0x1C, 0x1C, 0x3C, 0x60, 0x60, 0xF1, 0x81, 0x83, 0xC6, 0x06, 0x1B, 0x18, 0x38, 0xEE, 0x71, 0xE7, 0x18, 0xFD, 0xF8, 0x71, 0xE7, 0xC0, 0xE0, 0x00, 0x01, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x01, 0xFC, 0x00,
/* 'A' 0x41 */ 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 'B' 0x42 */ 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00,
/* 'C' 0x43 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0,
/* 'D' 0x44 */ 0xFF, 0xC3, 0xFF, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1B, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00, 0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3, 0xFE, 0x00,
/* 'E' 0x45 */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF,
/* 'F' 0x46 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xDF, 0xFB, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00,
/* 'G' 0x47 */ 0x07, 0xF0, 0x1F, 0xFC, 0x3C, 0x1E, 0x70, 0x07, 0x60, 0x03, 0xE0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xC0, 0x03, 0xC0, 0x03, 0x60, 0x03, 0x60, 0x07, 0x30, 0x0F, 0x3C, 0x1F, 0x1F, 0xFB, 0x07, 0xE1,
/* 'H' 0x48 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0,
/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 'J' 0x4A */ 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00,
/* 'K' 0x4B */ 0xC0, 0x3E, 0x03, 0xB0, 0x39, 0x83, 0x8C, 0x38, 0x63, 0x83, 0x38, 0x19, 0xC0, 0xDE, 0x07, 0xB8, 0x38, 0xE1, 0x83, 0x0C, 0x1C, 0x60, 0x73, 0x01, 0x98, 0x0E, 0xC0, 0x3E, 0x00, 0xC0,
/* 'L' 0x4C */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0,
/* 'M' 0x4D */ 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83,
/* 'N' 0x4E */ 0xE0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE, 0x0F, 0x30, 0x79, 0xC3, 0xC6, 0x1E, 0x18, 0xF0, 0xE7, 0x83, 0x3C, 0x1D, 0xE0, 0x6F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, 0xC0,
/* 'O' 0x4F */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00,
/* 'P' 0x50 */ 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x06, 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00,
/* 'Q' 0x51 */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x01, 0x98, 0xC0, 0xFC, 0x78, 0x3C, 0x1F, 0xFF, 0x03, 0xF9, 0x80, 0x00, 0x40,
/* 'R' 0x52 */ 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x70,
/* 'S' 0x53 */ 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80,
/* 'T' 0x54 */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 'U' 0x55 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x80, 0xEE, 0x0E, 0x3F, 0xE0, 0xFC, 0x00,
/* 'V' 0x56 */ 0xC0, 0x0F, 0x00, 0x7E, 0x01, 0x98, 0x06, 0x60, 0x39, 0xC0, 0xC3, 0x03, 0x0C, 0x1C, 0x38, 0x60, 0x61, 0x81, 0x8E, 0x07, 0x30, 0x0C, 0xC0, 0x37, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1C, 0x00,
/* 'W' 0x57 */ 0xE0, 0x30, 0x1D, 0x80, 0xE0, 0x76, 0x07, 0x81, 0xDC, 0x1E, 0x06, 0x70, 0x7C, 0x18, 0xC1, 0xB0, 0xE3, 0x0C, 0xC3, 0x8C, 0x33, 0x0C, 0x38, 0xC6, 0x30, 0x67, 0x18, 0xC1, 0x98, 0x67, 0x06, 0x61, 0xD8, 0x1D, 0x83, 0x60, 0x3C, 0x0D, 0x80, 0xF0, 0x3E, 0x03, 0xC0, 0x70, 0x0F, 0x01, 0xC0, 0x18, 0x07, 0x00,
/* 'X' 0x58 */ 0xE0, 0x1D, 0x80, 0xE7, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x00, 0xFC, 0x01, 0xE0, 0x07, 0x00, 0x1E, 0x00, 0xF8, 0x03, 0x30, 0x1C, 0xE0, 0xE1, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1B, 0x00, 0x70,
/* 'Y' 0x59 */ 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00,
/* 'Z' 0x5A */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x03, 0x80, 0x18, 0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xC0,
/* '[' 0x5B */ 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xF0,
/* '\' 0x5C */ 0x81, 0x81, 0x02, 0x06, 0x04, 0x08, 0x18, 0x10, 0x20, 0x60, 0x40, 0x81, 0x81, 0x02, 0x06, 0x04,
/* ']' 0x5D */ 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xF0,
/* '^' 0x5E */ 0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19, 0x8C, 0x83, 0xC1, 0x80,
/* '_' 0x5F */ 0xFF, 0xFE,
/* '`' 0x60 */ 0xE3, 0x8C, 0x30,
/* 'a' 0x61 */ 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70,
/* 'b' 0x62 */ 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0xF8, 0xDF, 0xCF, 0x0E, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xE0, 0x6F, 0x0E, 0xDF, 0xCC, 0xF8,
/* 'c' 0x63 */ 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x00,
/* 'd' 0x64 */ 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xCF, 0xFB, 0x8F, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8F, 0x3F, 0x63, 0xCC,
/* 'e' 0x65 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xC1, 0xF0,
/* 'f' 0x66 */ 0x3B, 0xD8, 0xC6, 0x7F, 0xEC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x00,
/* 'g' 0x67 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xB1, 0xE6, 0x00, 0xC0, 0x3E, 0x0E, 0x7F, 0xC7, 0xE0,
/* 'h' 0x68 */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30,
/* 'i' 0x69 */ 0xF0, 0x3F, 0xFF, 0xFF, 0xF0,
/* 'j' 0x6A */ 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0,
/* 'k' 0x6B */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0xB8, 0xC6, 0x31, 0xCC, 0x3B, 0x06, 0xC1, 0xF0, 0x30,
/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 'm' 0x6D */ 0xCF, 0x1F, 0x6F, 0xDF, 0xFC, 0x78, 0xFC, 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0F, 0x03, 0x07, 0x81, 0x83, 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x3C, 0x0C, 0x18,
/* 'n' 0x6E */ 0xCF, 0x37, 0xEF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0,
/* 'o' 0x6F */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0,
/* 'p' 0x70 */ 0xCF, 0x8D, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 'q' 0x71 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xF1, 0xE6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60,
/* 'r' 0x72 */ 0xCF, 0x7F, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 's' 0x73 */ 0x3E, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3F, 0x01, 0xF0, 0x3E, 0x1D, 0xFE, 0x3F, 0x00,
/* 't' 0x74 */ 0x63, 0x19, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0xE7,
/* 'u' 0x75 */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0,
/* 'v' 0x76 */ 0xE0, 0x6C, 0x0D, 0x81, 0xB8, 0x63, 0x0C, 0x61, 0x8E, 0x60, 0xCC, 0x19, 0x83, 0xE0, 0x3C, 0x07, 0x00, 0xE0,
/* 'w' 0x77 */ 0xC1, 0xC1, 0xB0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36, 0x31, 0x9B, 0x18, 0xCD, 0x98, 0x64, 0x6C, 0x16, 0x36, 0x0F, 0x1A, 0x07, 0x8F, 0x03, 0x83, 0x80, 0xC1, 0xC0,
/* 'x' 0x78 */ 0xC1, 0xF8, 0x66, 0x30, 0xCC, 0x3E, 0x07, 0x00, 0xC0, 0x78, 0x36, 0x0C, 0xC6, 0x3B, 0x06, 0xC0, 0xC0,
/* 'y' 0x79 */ 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0C, 0x60, 0xCC, 0x1B, 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, 0x00,
/* 'z' 0x7A */ 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0,
/* '{' 0x7B */ 0x19, 0xCC, 0x63, 0x18, 0xC6, 0x31, 0x99, 0x86, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x1C, 0x60,
/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
/* '}' 0x7D */ 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x0C, 0x33, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x73, 0x00,
/* '~' 0x7E */ 0x70, 0x3E, 0x09, 0xE4, 0x1F, 0x03, 0x80,
/* 0x7F */
/* 0x80 */ 0x01, 0xF0, 0x1F, 0xF0, 0xE0, 0xC7, 0x00, 0x18, 0x00, 0xC0, 0x07, 0xFF, 0x3F, 0xFC, 0x30, 0x01, 0xFF, 0x8F, 0xFC, 0x0C, 0x00, 0x18, 0x00, 0x70, 0x00, 0xE0, 0x81, 0xFE, 0x03, 0xF0,
/* 0x81 */
/* 0x82 */ 0xF5, 0x80,
/* 0x83 */ 0x1C, 0xF3, 0x0C, 0x31, 0xF7, 0xCC, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x33, 0xCE, 0x00,
/* 0x84 */ 0xCF, 0x34, 0x51, 0x88,
/* 0x85 */ 0xC6, 0x3C, 0x63,
/* 0x86 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x00,
/* 0x87 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x0F, 0xFF, 0xFF, 0x0C, 0x03, 0x00, 0xC0, 0x30,
/* 0x88 */ 0x38, 0xD9, 0xB6, 0x30,
/* 0x89 */ 0x38, 0x18, 0x00, 0xF8, 0x30, 0x03, 0x18, 0xC0, 0x04, 0x11, 0x80, 0x0C, 0x66, 0x00, 0x0F, 0x8C, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x40, 0x00, 0x01, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x31, 0xC0, 0xE0, 0x67, 0xC3, 0xC1, 0x98, 0xCC, 0xC3, 0x20, 0x90, 0x8C, 0x63, 0x33, 0x10, 0x7C, 0x3C, 0x60, 0x70, 0x38,
/* 0x8A */ 0x0C, 0x40, 0x1F, 0x00, 0x38, 0x03, 0xF8, 0x1F, 0xF0, 0xE0, 0xE6, 0x01, 0xD8, 0x03, 0x60, 0x01, 0xC0, 0x07, 0x80, 0x0F, 0xE0, 0x0F, 0xF0, 0x03, 0xE0, 0x01, 0xF0, 0x03, 0xC0, 0x0F, 0x80, 0x37, 0x83, 0x8F, 0xFC, 0x0F, 0xE0,
/* 0x8B */ 0x2F, 0x49, 0x99,
/* 0x8C */ 0x07, 0xCF, 0xFC, 0x7F, 0xFF, 0xF3, 0x83, 0xC0, 0x18, 0x07, 0x00, 0x60, 0x0C, 0x03, 0x00, 0x30, 0x0C, 0x00, 0xC0, 0x30, 0x03, 0x00, 0xC0, 0x0F, 0xFF, 0x00, 0x3F, 0xFC, 0x00, 0xC0, 0x30, 0x03, 0x00, 0xC0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x01, 0xC0, 0x0E, 0x0F, 0x00, 0x1F, 0xEF, 0xFC, 0x1F, 0x3F, 0xF0,
/* 0x8D */
/* 0x8E */ 0x0C, 0xC0, 0x3C, 0x00, 0xE1, 0xFF, 0xFF, 0xFF, 0x80, 0x1C, 0x01, 0xC0, 0x1C, 0x00, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x01, 0xFF, 0xFF, 0xFF, 0x80,
/* 0x8F */
/* 0x90 */
/* 0x91 */ 0x6A, 0xF0,
/* 0x92 */ 0xF5, 0x60,
/* 0x93 */ 0x4E, 0x28, 0xA2, 0xCF, 0x30,
/* 0x94 */ 0xCF, 0x34, 0x51, 0x4E, 0x20,
/* 0x95 */ 0x7B, 0xFF, 0xFF, 0xFD, 0xE0,
/* 0x96 */ 0xFF, 0xFF, 0xF0,
/* 0x97 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 0x98 */ 0x63, 0xFE, 0x70,
/* 0x99 */ 0xFF, 0x70, 0x1F, 0xFD, 0xC0, 0x71, 0x87, 0x83, 0xC6, 0x1E, 0x0F, 0x18, 0x68, 0x3C, 0x61, 0xB1, 0xB1, 0x86, 0xC6, 0xC6, 0x19, 0x1B, 0x18, 0x66, 0xCC, 0x61, 0x9B, 0x31, 0x86, 0x3C, 0xC6, 0x18, 0xE3, 0x18, 0x63, 0x8C,
/* 0x9A */ 0x63, 0x0D, 0x83, 0x60, 0x70, 0x00, 0x0F, 0x87, 0xFB, 0x86, 0xC0, 0x30, 0x0F, 0x01, 0xFC, 0x0F, 0xC0, 0x7C, 0x0F, 0x87, 0x7F, 0x8F, 0xC0,
/* 0x9B */ 0x99, 0x92, 0xF4,
/* 0x9C */ 0x1F, 0x0F, 0x83, 0xF9, 0xFC, 0x71, 0xF8, 0x6E, 0x0F, 0x03, 0xC0, 0x60, 0x3C, 0x07, 0xFF, 0xC0, 0x7F, 0xFC, 0x06, 0x00, 0xC0, 0x60, 0x0E, 0x0F, 0x03, 0x71, 0xF8, 0x63, 0xF9, 0xFC, 0x1F, 0x0F, 0x80,
/* 0x9D */
/* 0x9E */ 0x63, 0x0C, 0x83, 0x60, 0x70, 0x00, 0x3F, 0xFF, 0xFC, 0x06, 0x03, 0x01, 0xC0, 0xE0, 0x70, 0x18, 0x0C, 0x06, 0x03, 0x80, 0xFF, 0xFF, 0xF0,
/* 0x9F */ 0x0C, 0xC0, 0x33, 0x00, 0x00, 0x30, 0x03, 0xE0, 0x1D, 0x80, 0x67, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x80, 0xCC, 0x03, 0xE0, 0x07, 0x80, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00,
/* 0xA0 */
/* 0xA1 */ 0xF0, 0xBF, 0xFF, 0xFF, 0xF0,
/* 0xA2 */ 0x04, 0x00, 0x80, 0x7C, 0x1F, 0xE7, 0x4C, 0xC8, 0xF1, 0x1E, 0x20, 0xC4, 0x18, 0x83, 0x10, 0x72, 0x37, 0x4E, 0x7F, 0x87, 0xC0, 0x20, 0x04, 0x00,
/* 0xA3 */ 0x0F, 0xC1, 0xFE, 0x38, 0x76, 0x03, 0x60, 0x36, 0x00, 0x70, 0x03, 0x80, 0xFF, 0x0F, 0xF0, 0x1C, 0x00, 0xC0, 0x0C, 0x01, 0x80, 0x10, 0x02, 0xF1, 0x7F, 0xF6, 0x1F,
/* 0xA4 */ 0xDD, 0xFF, 0xD8, 0xD8, 0x3C, 0x1E, 0x0F, 0x8D, 0xFF, 0xDD, 0x80,
/* 0xA5 */ 0xC0, 0x3E, 0x06, 0x60, 0x63, 0x0C, 0x30, 0xC1, 0x98, 0x19, 0x80, 0xF0, 0x0F, 0x07, 0xFE, 0x06, 0x00, 0x60, 0x7F, 0xE0, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
/* 0xA6 */ 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC,
/* 0xA7 */ 0x0F, 0x03, 0xF0, 0xE7, 0x18, 0x63, 0x0C, 0x70, 0x07, 0x03, 0xF8, 0xC3, 0x98, 0x3B, 0x03, 0xF0, 0x37, 0x06, 0x78, 0xC7, 0xB0, 0x7C, 0x03, 0x80, 0x39, 0x83, 0x30, 0x67, 0x1C, 0x7F, 0x07, 0xC0,
/* 0xA8 */ 0xCF, 0x30,
/* 0xA9 */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xE3, 0x1C, 0x73, 0xF3, 0x99, 0x86, 0x6C, 0xC1, 0x8F, 0x30, 0x03, 0xCC, 0x00, 0xF3, 0x00, 0x3C, 0xC1, 0x8D, 0x98, 0x66, 0x77, 0xF3, 0x8E, 0x79, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00,
/* 0xAA */ 0x79, 0x08, 0x11, 0xEE, 0x50, 0xA3, 0x3B, 0x00, 0x03, 0xF8,
/* 0xAB */ 0x21, 0x63, 0xE7, 0x84, 0x84, 0xE7, 0x63, 0x21,
/* 0xAC */ 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03,
/* 0xAD */ 0xFF, 0xF0,
/* 0xAE */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xFF, 0x1C, 0x7F, 0xF3, 0x9B, 0x04, 0x6C, 0xC1, 0x8F, 0x30, 0x43, 0xCF, 0xF0, 0xF3, 0xFC, 0x3C, 0xC1, 0x0D, 0xB0, 0x66, 0x7C, 0x1B, 0x8F, 0x07, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00,
/* 0xAF */ 0xFF, 0xF0,
/* 0xB0 */ 0x38, 0xFB, 0x1C, 0x18, 0x38, 0xDF, 0x1C,
/* 0xB1 */ 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x7F, 0xE7, 0xFE, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0,
/* 0xB2 */ 0x7D, 0x8F, 0x18, 0x30, 0xC6, 0x18, 0x60, 0xFF, 0xFC,
/* 0xB3 */ 0x7D, 0x8F, 0x18, 0x31, 0x80, 0xC1, 0xE3, 0xC6, 0xF8,
/* 0xB4 */ 0x3B, 0x99, 0x80,
/* 0xB5 */ 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x1C, 0xE3, 0xCF, 0xEF, 0xFC, 0x7C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 0xB6 */ 0x1F, 0xE7, 0xFD, 0xF3, 0x7E, 0x6F, 0xCD, 0xF9, 0xBF, 0x37, 0xE6, 0x7C, 0xCF, 0x98, 0xF3, 0x06, 0x60, 0xCC, 0x19, 0x83, 0x30, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x60, 0xCC,
/* 0xB7 */ 0xF0,
/* 0xB8 */ 0x10, 0xF0, 0xE3, 0x78,
/* 0xB9 */ 0x2F, 0xB6, 0xDB, 0x6C,
/* 0xBA */ 0x79, 0x38, 0x61, 0x86, 0x1C, 0xDE, 0x00, 0x0F, 0xC0,
/* 0xBB */ 0x88, 0xC6, 0xE7, 0x21, 0x21, 0xE7, 0xC6, 0x88,
/* 0xBC */ 0x20, 0x08, 0x30, 0x0C, 0x38, 0x04, 0x0C, 0x06, 0x06, 0x02, 0x03, 0x02, 0x01, 0x81, 0x00, 0xC1, 0x06, 0x61, 0x87, 0x30, 0x83, 0x80, 0xC2, 0xC0, 0x42, 0x60, 0x43, 0x30, 0x21, 0xFC, 0x20, 0x0C, 0x30, 0x06, 0x10, 0x03, 0x00,
/* 0xBD */ 0x20, 0x00, 0x08, 0x02, 0x06, 0x01, 0x83, 0x80, 0x40, 0x60, 0x20, 0x18, 0x18, 0x06, 0x04, 0x01, 0x83, 0x00, 0x61, 0x9F, 0x98, 0x4E, 0x76, 0x33, 0x0C, 0x08, 0x03, 0x04, 0x03, 0x83, 0x01, 0x80, 0x81, 0x80, 0x60, 0xC0, 0x30, 0x3F, 0xC8, 0x0F, 0xF0,
/* 0xBE */ 0x7C, 0x00, 0x18, 0xC0, 0x43, 0x18, 0x18, 0x03, 0x02, 0x00, 0x60, 0xC0, 0x30, 0x10, 0x01, 0x84, 0x00, 0x31, 0x80, 0xC6, 0x20, 0xD8, 0xC8, 0x39, 0xF1, 0x07, 0x00, 0x41, 0x60, 0x18, 0x4C, 0x02, 0x11, 0x80, 0x83, 0xF8, 0x10, 0x06, 0x04, 0x00, 0xC1, 0x00, 0x18,
/* 0xBF */ 0x0C, 0x06, 0x00, 0x00, 0x00, 0xC0, 0x60, 0x60, 0x30, 0x30, 0x38, 0x38, 0x18, 0x0C, 0x06, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00,
/* 0xC0 */ 0x0C, 0xDB, 0xD3, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
/* 0xC1 */ 0x03, 0x80, 0x07, 0x00, 0x1B, 0x00, 0x36, 0x00, 0xEE, 0x01, 0x8C, 0x03, 0x18, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x60, 0xFF, 0xE1, 0xFF, 0xC7, 0x01, 0xCC, 0x01, 0x98, 0x03, 0x60, 0x03, 0xC0, 0x06,
/* 0xC2 */ 0xFF, 0x87, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x01, 0x9F, 0xFC, 0xFF, 0xE6, 0x03, 0xB0, 0x0F, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0xDF, 0xFE, 0xFF, 0xC0,
/* 0xC3 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x00,
/* 0xC4 */ 0x01, 0xC0, 0x01, 0xC0, 0x03, 0x60, 0x03, 0x60, 0x07, 0x60, 0x06, 0x30, 0x06, 0x30, 0x0C, 0x18, 0x0C, 0x18, 0x1C, 0x18, 0x18, 0x0C, 0x18, 0x0C, 0x30, 0x06, 0x30, 0x06, 0x70, 0x06, 0x7F, 0xFF, 0x7F, 0xFF,
/* 0xC5 */ 0xFF, 0xFF, 0xFF, 0xF0, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x1F, 0xFE, 0xFF, 0xF6, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x1F, 0xFF, 0xFF, 0xF8,
/* 0xC6 */ 0x7F, 0xFD, 0xFF, 0xF0, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x80, 0x1C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xE0, 0x07, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x3F, 0xFF, 0xFF, 0xFC,
/* 0xC7 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x18,
/* 0xC8 */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x8E, 0x00, 0xE6, 0x00, 0x37, 0x00, 0x1F, 0x00, 0x07, 0x9F, 0xF3, 0xCF, 0xF9, 0xE0, 0x00, 0xF0, 0x00, 0x7C, 0x00, 0x76, 0x00, 0x33, 0x80, 0x38, 0xF0, 0x78, 0x3F, 0xF8, 0x07, 0xF0, 0x00,
/* 0xC9 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
/* 0xCA */ 0xC0, 0x3B, 0x01, 0xCC, 0x0E, 0x30, 0x70, 0xC3, 0x83, 0x1C, 0x0C, 0xE0, 0x37, 0x80, 0xFF, 0x03, 0xDC, 0x0E, 0x38, 0x30, 0x70, 0xC0, 0xE3, 0x03, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1C,
/* 0xCB */ 0x01, 0xC0, 0x00, 0xE0, 0x00, 0xD8, 0x00, 0x6C, 0x00, 0x37, 0x00, 0x31, 0x80, 0x18, 0xC0, 0x18, 0x30, 0x0C, 0x18, 0x0E, 0x0E, 0x06, 0x03, 0x03, 0x01, 0x83, 0x00, 0x61, 0x80, 0x31, 0xC0, 0x1C, 0xC0, 0x06, 0x60, 0x03, 0x00,
/* 0xCC */ 0xE0, 0x0F, 0xE0, 0x3F, 0xC0, 0x7F, 0x80, 0xFD, 0x83, 0x7B, 0x06, 0xF6, 0x0D, 0xE4, 0x13, 0xCC, 0x67, 0x98, 0xCF, 0x31, 0x9E, 0x36, 0x3C, 0x6C, 0x78, 0xD8, 0xF0, 0xA1, 0xE1, 0xC3, 0xC3, 0x86,
/* 0xCD */ 0xC0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE, 0x0F, 0x30, 0x78, 0xC3, 0xC7, 0x1E, 0x18, 0xF0, 0x67, 0x83, 0xBC, 0x0D, 0xE0, 0x3F, 0x01, 0xF8, 0x07, 0xC0, 0x18,
/* 0xCE */ 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFE, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC,
/* 0xCF */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x8E, 0x00, 0xE6, 0x00, 0x37, 0x00, 0x1F, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x7C, 0x00, 0x76, 0x00, 0x33, 0x80, 0x38, 0xF0, 0x78, 0x3F, 0xF8, 0x07, 0xF0, 0x00,
/* 0xD0 */ 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x18,
/* 0xD1 */ 0xFF, 0xC7, 0xFF, 0xB0, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x00,
/* 0xD2 */
/* 0xD3 */ 0xFF, 0xEF, 0xFE, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0,
/* 0xD4 */ 0xFF, 0xFF, 0xFF, 0xF0, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00,
/* 0xD5 */ 0xE0, 0x07, 0x60, 0x0E, 0x30, 0x1C, 0x38, 0x1C, 0x1C, 0x38, 0x0E, 0x70, 0x06, 0x60, 0x07, 0xE0, 0x03, 0xC0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
/* 0xD6 */ 0x01, 0x80, 0x01, 0x80, 0x0F, 0xF0, 0x3F, 0xFC, 0x71, 0x8E, 0x61, 0x86, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0x61, 0x86, 0x71, 0x8E, 0x3F, 0xFC, 0x0F, 0xF0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
/* 0xD7 */ 0x70, 0x1C, 0x70, 0x70, 0x61, 0xC0, 0xE3, 0x80, 0xEE, 0x00, 0xD8, 0x01, 0xF0, 0x01, 0xC0, 0x03, 0x80, 0x0D, 0x80, 0x3B, 0x80, 0x77, 0x01, 0xC7, 0x07, 0x07, 0x0E, 0x06, 0x38, 0x0E, 0xE0, 0x0E,
/* 0xD8 */ 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0x61, 0x86, 0x79, 0x9E, 0x3F, 0xFC, 0x0F, 0xF0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
/* 0xD9 */ 0x07, 0xE0, 0x1F, 0xF8, 0x38, 0x3C, 0x70, 0x0E, 0x60, 0x06, 0xE0, 0x07, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0x60, 0x06, 0x60, 0x06, 0x30, 0x0C, 0x1C, 0x38, 0xFE, 0x7F, 0xFE, 0x7F,
/* 0xDA */ 0xCF, 0x30, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C,
/* 0xDB */ 0x06, 0x60, 0x06, 0x60, 0x00, 0x00, 0xE0, 0x07, 0x60, 0x0E, 0x30, 0x1C, 0x38, 0x1C, 0x1C, 0x38, 0x0E, 0x70, 0x06, 0x60, 0x07, 0xE0, 0x03, 0xC0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
/* 0xDC */ 0x03, 0x80, 0x30, 0x06, 0x00, 0x00, 0x1E, 0x33, 0xFB, 0x71, 0xFE, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x6E, 0x0E, 0x71, 0xF3, 0xFF, 0x1F, 0x30,
/* 0xDD */ 0x0C, 0x0C, 0x04, 0x00, 0x03, 0xE3, 0xFF, 0x8D, 0x80, 0xE0, 0x3E, 0x1F, 0x1C, 0x0C, 0x06, 0x0B, 0x8E, 0xFE, 0x3E, 0x00,
/* 0xDE */ 0x07, 0x01, 0x80, 0xC0, 0x00, 0xCF, 0x3F, 0xEE, 0x1F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30,
/* 0xDF */ 0x76, 0xC0, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x60,
/* 0xE0 */ 0x0C, 0x1B, 0x66, 0x98, 0x00, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x36, 0x19, 0xFE, 0x1E, 0x00,
/* 0xE1 */ 0x1E, 0x33, 0xFB, 0x71, 0xFE, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x6E, 0x0E, 0x71, 0xF3, 0xFF, 0x1F, 0x30,
/* 0xE2 */ 0x1F, 0x0F, 0xF1, 0x87, 0x60, 0x6C, 0x0D, 0x83, 0x33, 0x86, 0x7C, 0xC1, 0xD8, 0x1F, 0x01, 0xE0, 0x3C, 0x07, 0xC0, 0xFC, 0x36, 0xFE, 0xCF, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x00,
/* 0xE3 */ 0x60, 0x66, 0x06, 0x60, 0x63, 0x0C, 0x30, 0xC3, 0x8C, 0x19, 0x81, 0x98, 0x1F, 0x80, 0xF0, 0x0F, 0x00, 0xF0, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 0xE4 */ 0x7F, 0xCF, 0xF8, 0xE0, 0x07, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x07, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00,
/* 0xE5 */ 0x3E, 0x3F, 0xF8, 0xD8, 0x0E, 0x03, 0xE1, 0xF1, 0xC0, 0xC0, 0x60, 0xB8, 0xEF, 0xE3, 0xE0,
/* 0xE6 */ 0x3F, 0x9F, 0xC0, 0xC1, 0xC1, 0xC0, 0xC0, 0xC0, 0xC0, 0x60, 0x70, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x80, 0xFC, 0x3F, 0x80, 0xC0, 0x60, 0x70, 0xF0, 0x70,
/* 0xE7 */ 0xCF, 0x3F, 0xEE, 0x1F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30,
/* 0xE8 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x6C, 0x07, 0x80, 0xF0, 0x1F, 0xFF, 0xFF, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00,
/* 0xE9 */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 0xEA */ 0xC1, 0xB0, 0xCC, 0x63, 0x30, 0xD8, 0x3C, 0x0F, 0x83, 0x60, 0xCC, 0x31, 0x8C, 0x73, 0x0C, 0xC1, 0x80,
/* 0xEB */ 0x0C, 0x00, 0x60, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x01, 0x80, 0x1C, 0x00, 0xF0, 0x0D, 0x80, 0x6C, 0x06, 0x30, 0x31, 0x83, 0x8E, 0x18, 0x30, 0xC1, 0x8C, 0x06, 0x60, 0x30,
/* 0xEC */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF8, 0x7E, 0x1F, 0xFF, 0xDE, 0xF0, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x00,
/* 0xED */ 0xC0, 0x78, 0x0D, 0x83, 0x30, 0x66, 0x0C, 0x63, 0x0C, 0x60, 0xD8, 0x1B, 0x03, 0x60, 0x38, 0x07, 0x00, 0x40,
/* 0xEE */ 0x1F, 0x1F, 0x9C, 0x0C, 0x07, 0x01, 0xF8, 0x3C, 0x70, 0x70, 0x30, 0x30, 0x18, 0x0C, 0x06, 0x01, 0xC0, 0xFC, 0x1F, 0x00, 0xC0, 0x60, 0x70, 0xF0, 0x70,
/* 0xEF */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0,
/* 0xF0 */ 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
/* 0xF1 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1F, 0xC7, 0x7F, 0xCD, 0xF1, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00,
/* 0xF2 */ 0x07, 0xE3, 0xFC, 0xE0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x1C, 0x01, 0xC0, 0x1F, 0x80, 0xF8, 0x03, 0x80, 0x30, 0x0E, 0x0F, 0x81, 0xE0,
/* 0xF3 */ 0x1F, 0xF9, 0xFF, 0xDC, 0x39, 0xC0, 0xCC, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x06, 0xC0, 0x37, 0x03, 0x9C, 0x38, 0x7F, 0x81, 0xF8, 0x00,
/* 0xF4 */ 0xFF, 0xF3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30,
/* 0xF5 */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x36, 0x19, 0xFE, 0x1E, 0x00,
/* 0xF6 */ 0x19, 0xE0, 0xEF, 0xC6, 0x31, 0xB8, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xE3, 0x1D, 0x8C, 0x67, 0xB7, 0x0F, 0xF8, 0x0F, 0xC0, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00,
/* 0xF7 */ 0x60, 0x33, 0x03, 0x8C, 0x18, 0x71, 0xC1, 0x8C, 0x0E, 0xC0, 0x36, 0x00, 0xE0, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x1B, 0x00, 0xDC, 0x0C, 0x60, 0xE3, 0x86, 0x0C, 0x70, 0x33, 0x01, 0x80,
/* 0xF8 */ 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x8C, 0x77, 0x33, 0x8F, 0xFC, 0x1F, 0xE0, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00,
/* 0xF9 */ 0x30, 0x0C, 0x60, 0x06, 0x60, 0x06, 0xE1, 0x87, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xE1, 0x87, 0x63, 0xC6, 0x7E, 0x7E, 0x3C, 0x38,
/* 0xFA */ 0xCF, 0x30, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C,
/* 0xFB */ 0x33, 0x0C, 0xC0, 0x03, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xD8, 0x67, 0xF8, 0x78,
/* 0xFC */ 0x07, 0x00, 0xC0, 0x30, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x07, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00,
/* 0xFD */ 0x06, 0x03, 0x00, 0x80, 0x00, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x36, 0x19, 0xFE, 0x1E, 0x00,
/* 0xFE */ 0x00, 0xC0, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x60, 0x06, 0x60, 0x06, 0xE1, 0x87, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xE1, 0x87, 0x63, 0xC6, 0x7E, 0x7E, 0x3C, 0x38,
/* 0xFF */
};
const GFXglyph FreeSans12pt_Win1253Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 19, 20, 21, 1, -17 },
/* 0x02 */ { 48, 19, 20, 21, 1, -17 },
/* 0x03 */ { 96, 21, 20, 23, 1, -17 },
/* 0x04 */ { 149, 21, 20, 23, 1, -17 },
/* 0x05 */ { 202, 20, 20, 22, 1, -17 },
/* 0x06 */ { 252, 20, 20, 22, 1, -17 },
/* 0x07 */ { 302, 0, 0, 8, 0, 0 },
/* 0x08 */ { 302, 23, 20, 25, 1, -17 },
/* 0x09 */ { 360, 23, 16, 25, 1, -16 },
/* 0x0A */ { 406, 0, 0, 8, 0, 0 },
/* 0x0B */ { 406, 21, 20, 23, 1, -17 },
/* 0x0C */ { 459, 19, 18, 21, 1, -15 },
/* 0x0D */ { 502, 0, 0, 8, 0, 0 },
/* 0x0E */ { 502, 20, 20, 22, 1, -17 },
/* 0x0F */ { 552, 21, 21, 23, 1, -18 },
/* 0x10 */ { 608, 19, 20, 21, 1, -17 },
/* 0x11 */ { 656, 21, 20, 23, 1, -17 },
/* 0x12 */ { 709, 20, 20, 22, 1, -17 },
/* 0x13 */ { 759, 21, 20, 23, 1, -17 },
/* 0x14 */ { 812, 21, 20, 23, 1, -17 },
/* 0x15 */ { 865, 22, 20, 24, 1, -17 },
/* 0x16 */ { 920, 16, 20, 18, 1, -17 },
/* 0x17 */ { 960, 21, 20, 23, 1, -17 },
/* 0x18 */ { 1013, 23, 20, 25, 1, -17 },
/* 0x19 */ { 1071, 21, 20, 23, 1, -17 },
/* 0x1A */ { 1124, 15, 19, 17, 1, -16 },
/* 0x1B */ { 1160, 24, 21, 26, 1, -18 },
/* 0x1C */ { 1223, 21, 20, 23, 1, -17 },
/* 0x1D */ { 1276, 21, 21, 23, 1, -18 },
/* 0x1E */ { 1332, 20, 20, 22, 1, -17 },
/* 0x1F */ { 1382, 15, 20, 17, 1, -17 },
/* ' ' 0x20 */ { 1420, 0, 0, 6, 0, 0 },
/* '!' 0x21 */ { 1420, 2, 18, 8, 3, -16 },
/* '"' 0x22 */ { 1425, 6, 6, 8, 1, -15 },
/* '#' 0x23 */ { 1430, 13, 16, 13, 0, -14 },
/* '$' 0x24 */ { 1456, 11, 20, 13, 1, -16 },
/* '%' 0x25 */ { 1484, 20, 17, 21, 1, -15 },
/* '&' 0x26 */ { 1527, 14, 17, 16, 1, -15 },
/* ''' 0x27 */ { 1557, 2, 6, 5, 1, -15 },
/* '(' 0x28 */ { 1559, 5, 23, 8, 2, -16 },
/* ')' 0x29 */ { 1574, 5, 23, 8, 1, -16 },
/* '*' 0x2A */ { 1589, 7, 7, 9, 1, -16 },
/* '+' 0x2B */ { 1596, 10, 11, 14, 2, -9 },
/* ',' 0x2C */ { 1610, 2, 6, 7, 2, 0 },
/* '-' 0x2D */ { 1612, 6, 2, 8, 1, -6 },
/* '.' 0x2E */ { 1614, 2, 2, 6, 2, 0 },
/* '/' 0x2F */ { 1615, 7, 18, 7, 0, -16 },
/* '0' 0x30 */ { 1631, 11, 17, 13, 1, -15 },
/* '1' 0x31 */ { 1655, 5, 17, 13, 3, -15 },
/* '2' 0x32 */ { 1666, 11, 17, 13, 1, -15 },
/* '3' 0x33 */ { 1690, 11, 17, 13, 1, -15 },
/* '4' 0x34 */ { 1714, 11, 17, 13, 1, -15 },
/* '5' 0x35 */ { 1738, 11, 17, 13, 1, -15 },
/* '6' 0x36 */ { 1762, 11, 17, 13, 1, -15 },
/* '7' 0x37 */ { 1786, 11, 17, 13, 1, -15 },
/* '8' 0x38 */ { 1810, 11, 17, 13, 1, -15 },
/* '9' 0x39 */ { 1834, 11, 17, 13, 1, -15 },
/* ':' 0x3A */ { 1858, 2, 13, 6, 2, -11 },
/* ';' 0x3B */ { 1862, 2, 16, 6, 2, -10 },
/* '<' 0x3C */ { 1866, 12, 11, 14, 1, -9 },
/* '=' 0x3D */ { 1883, 12, 6, 14, 1, -7 },
/* '>' 0x3E */ { 1892, 12, 11, 14, 1, -9 },
/* '?' 0x3F */ { 1909, 10, 18, 13, 2, -16 },
/* '@' 0x40 */ { 1932, 22, 21, 24, 1, -16 },
/* 'A' 0x41 */ { 1990, 14, 18, 16, 1, -16 },
/* 'B' 0x42 */ { 2022, 13, 18, 16, 2, -16 },
/* 'C' 0x43 */ { 2052, 15, 18, 17, 1, -16 },
/* 'D' 0x44 */ { 2086, 14, 18, 17, 2, -16 },
/* 'E' 0x45 */ { 2118, 12, 18, 15, 2, -16 },
/* 'F' 0x46 */ { 2145, 11, 18, 14, 2, -16 },
/* 'G' 0x47 */ { 2170, 16, 18, 18, 1, -16 },
/* 'H' 0x48 */ { 2206, 13, 18, 17, 2, -16 },
/* 'I' 0x49 */ { 2236, 2, 18, 7, 2, -16 },
/* 'J' 0x4A */ { 2241, 9, 18, 13, 1, -16 },
/* 'K' 0x4B */ { 2262, 13, 18, 16, 2, -16 },
/* 'L' 0x4C */ { 2292, 10, 18, 14, 2, -16 },
/* 'M' 0x4D */ { 2315, 16, 18, 20, 2, -16 },
/* 'N' 0x4E */ { 2351, 13, 18, 18, 2, -16 },
/* 'O' 0x4F */ { 2381, 17, 18, 19, 1, -16 },
/* 'P' 0x50 */ { 2420, 12, 18, 16, 2, -16 },
/* 'Q' 0x51 */ { 2447, 17, 19, 19, 1, -16 },
/* 'R' 0x52 */ { 2488, 14, 18, 17, 2, -16 },
/* 'S' 0x53 */ { 2520, 14, 18, 16, 1, -16 },
/* 'T' 0x54 */ { 2552, 12, 18, 15, 1, -16 },
/* 'U' 0x55 */ { 2579, 13, 18, 17, 2, -16 },
/* 'V' 0x56 */ { 2609, 14, 18, 15, 1, -16 },
/* 'W' 0x57 */ { 2641, 22, 18, 22, 0, -16 },
/* 'X' 0x58 */ { 2691, 14, 18, 16, 1, -16 },
/* 'Y' 0x59 */ { 2723, 14, 18, 16, 1, -16 },
/* 'Z' 0x5A */ { 2755, 13, 18, 15, 1, -16 },
/* '[' 0x5B */ { 2785, 4, 23, 7, 2, -16 },
/* '\' 0x5C */ { 2797, 7, 18, 7, 0, -16 },
/* ']' 0x5D */ { 2813, 4, 23, 7, 1, -16 },
/* '^' 0x5E */ { 2825, 9, 9, 11, 1, -15 },
/* '_' 0x5F */ { 2836, 15, 1, 13, -1, 5 },
/* '`' 0x60 */ { 2838, 5, 4, 6, 1, -16 },
/* 'a' 0x61 */ { 2841, 12, 13, 13, 1, -11 },
/* 'b' 0x62 */ { 2861, 12, 18, 13, 1, -16 },
/* 'c' 0x63 */ { 2888, 10, 13, 12, 1, -11 },
/* 'd' 0x64 */ { 2905, 11, 18, 13, 1, -16 },
/* 'e' 0x65 */ { 2930, 11, 13, 13, 1, -11 },
/* 'f' 0x66 */ { 2948, 5, 18, 7, 1, -16 },
/* 'g' 0x67 */ { 2960, 11, 18, 13, 1, -11 },
/* 'h' 0x68 */ { 2985, 10, 18, 13, 1, -16 },
/* 'i' 0x69 */ { 3008, 2, 18, 5, 2, -16 },
/* 'j' 0x6A */ { 3013, 4, 23, 6, 0, -16 },
/* 'k' 0x6B */ { 3025, 10, 18, 12, 1, -16 },
/* 'l' 0x6C */ { 3048, 2, 18, 5, 1, -16 },
/* 'm' 0x6D */ { 3053, 17, 13, 19, 1, -11 },
/* 'n' 0x6E */ { 3081, 10, 13, 13, 1, -11 },
/* 'o' 0x6F */ { 3098, 11, 13, 13, 1, -11 },
/* 'p' 0x70 */ { 3116, 12, 17, 13, 1, -11 },
/* 'q' 0x71 */ { 3142, 11, 17, 13, 1, -11 },
/* 'r' 0x72 */ { 3166, 6, 13, 8, 1, -11 },
/* 's' 0x73 */ { 3176, 10, 13, 12, 1, -11 },
/* 't' 0x74 */ { 3193, 5, 16, 7, 1, -14 },
/* 'u' 0x75 */ { 3203, 10, 13, 13, 1, -11 },
/* 'v' 0x76 */ { 3220, 11, 13, 12, 0, -11 },
/* 'w' 0x77 */ { 3238, 17, 13, 17, 0, -11 },
/* 'x' 0x78 */ { 3266, 10, 13, 11, 1, -11 },
/* 'y' 0x79 */ { 3283, 11, 18, 11, 0, -11 },
/* 'z' 0x7A */ { 3308, 10, 13, 12, 1, -11 },
/* '{' 0x7B */ { 3325, 5, 23, 8, 1, -16 },
/* '|' 0x7C */ { 3340, 2, 23, 6, 2, -16 },
/* '}' 0x7D */ { 3346, 5, 23, 8, 2, -16 },
/* '~' 0x7E */ { 3361, 10, 5, 12, 1, -9 },
/* 0x7F */ { 3368, 0, 0, 0, 0, 0 },
/* 0x80 */ { 3368, 14, 17, 16, 1, -15 },
/* 0x81 */ { 3398, 0, 0, 8, 0, 0 },
/* 0x82 */ { 3398, 2, 5, 6, 2, 0 },
/* 0x83 */ { 3400, 6, 23, 7, 0, -16 },
/* 0x84 */ { 3418, 6, 5, 10, 2, 0 },
/* 0x85 */ { 3422, 12, 2, 16, 2, 0 },
/* 0x86 */ { 3425, 10, 21, 13, 2, -15 },
/* 0x87 */ { 3452, 10, 20, 13, 2, -15 },
/* 0x88 */ { 3477, 7, 4, 8, 0, -16 },
/* 0x89 */ { 3481, 23, 18, 24, 0, -16 },
/* 0x8A */ { 3533, 14, 21, 16, 1, -19 },
/* 0x8B */ { 3570, 3, 8, 6, 1, -9 },
/* 0x8C */ { 3573, 22, 18, 24, 1, -16 },
/* 0x8D */ { 3623, 0, 0, 8, 0, 0 },
/* 0x8E */ { 3623, 13, 21, 15, 1, -19 },
/* 0x8F */ { 3658, 0, 0, 8, 0, 0 },
/* 0x90 */ { 3658, 0, 0, 8, 0, 0 },
/* 0x91 */ { 3658, 2, 6, 6, 2, -16 },
/* 0x92 */ { 3660, 2, 6, 6, 2, -16 },
/* 0x93 */ { 3662, 6, 6, 10, 2, -16 },
/* 0x94 */ { 3667, 6, 6, 10, 2, -16 },
/* 0x95 */ { 3672, 6, 6, 10, 2, -9 },
/* 0x96 */ { 3677, 10, 2, 12, 1, -6 },
/* 0x97 */ { 3680, 22, 2, 24, 1, -6 },
/* 0x98 */ { 3686, 7, 3, 8, 0, -16 },
/* 0x99 */ { 3689, 22, 13, 24, 2, -16 },
/* 0x9A */ { 3725, 10, 18, 12, 1, -16 },
/* 0x9B */ { 3748, 3, 8, 6, 2, -8 },
/* 0x9C */ { 3751, 20, 13, 22, 1, -11 },
/* 0x9D */ { 3784, 0, 0, 8, 0, 0 },
/* 0x9E */ { 3784, 10, 18, 12, 1, -16 },
/* 0x9F */ { 3807, 14, 21, 16, 1, -19 },
/* 0xA0 */ { 3844, 0, 0, 7, 0, 0 },
/* 0xA1 */ { 3844, 2, 18, 8, 3, -11 },
/* 0xA2 */ { 3849, 11, 17, 13, 1, -13 },
/* 0xA3 */ { 3873, 12, 18, 13, 0, -16 },
/* 0xA4 */ { 3900, 9, 9, 13, 2, -11 },
/* 0xA5 */ { 3911, 12, 17, 13, 1, -15 },
/* 0xA6 */ { 3937, 2, 23, 6, 2, -16 },
/* 0xA7 */ { 3943, 11, 23, 13, 1, -16 },
/* 0xA8 */ { 3975, 6, 2, 8, 1, -15 },
/* 0xA9 */ { 3977, 18, 17, 19, 1, -15 },
/* 0xAA */ { 4016, 7, 11, 9, 1, -16 },
/* 0xAB */ { 4026, 8, 8, 12, 2, -9 },
/* 0xAC */ { 4034, 12, 6, 14, 1, -7 },
/* 0xAD */ { 4043, 6, 2, 8, 1, -6 },
/* 0xAE */ { 4045, 18, 17, 19, 1, -15 },
/* 0xAF */ { 4084, 6, 2, 8, 1, -15 },
/* 0xB0 */ { 4086, 7, 8, 15, 4, -15 },
/* 0xB1 */ { 4093, 12, 15, 14, 1, -13 },
/* 0xB2 */ { 4116, 7, 10, 8, 1, -17 },
/* 0xB3 */ { 4125, 7, 10, 8, 1, -17 },
/* 0xB4 */ { 4134, 5, 4, 8, 2, -16 },
/* 0xB5 */ { 4137, 12, 17, 13, 2, -11 },
/* 0xB6 */ { 4163, 11, 21, 13, 2, -16 },
/* 0xB7 */ { 4192, 2, 2, 6, 2, -6 },
/* 0xB8 */ { 4193, 6, 5, 8, 1, 2 },
/* 0xB9 */ { 4197, 3, 10, 8, 3, -18 },
/* 0xBA */ { 4201, 6, 11, 9, 1, -16 },
/* 0xBB */ { 4210, 8, 8, 12, 2, -8 },
/* 0xBC */ { 4218, 17, 17, 21, 3, -15 },
/* 0xBD */ { 4255, 18, 18, 21, 3, -16 },
/* 0xBE */ { 4296, 19, 18, 21, 1, -16 },
/* 0xBF */ { 4339, 9, 18, 13, 3, -11 },
/* 0xC0 */ { 4360, 8, 18, 6, -1, -18 },
/* 0xC1 */ { 4378, 15, 17, 15, 0, -17 },
/* 0xC2 */ { 4410, 13, 17, 16, 2, -17 },
/* 0xC3 */ { 4438, 11, 17, 13, 2, -17 },
/* 0xC4 */ { 4462, 16, 17, 16, -1, -17 },
/* 0xC5 */ { 4496, 13, 17, 16, 2, -17 },
/* 0xC6 */ { 4524, 14, 17, 15, 0, -17 },
/* 0xC7 */ { 4554, 13, 17, 17, 2, -17 },
/* 0xC8 */ { 4582, 17, 17, 19, 1, -17 },
/* 0xC9 */ { 4619, 2, 17, 6, 2, -17 },
/* 0xCA */ { 4624, 14, 17, 16, 2, -17 },
/* 0xCB */ { 4654, 17, 17, 16, -1, -17 },
/* 0xCC */ { 4691, 15, 17, 19, 2, -17 },
/* 0xCD */ { 4723, 13, 17, 17, 2, -17 },
/* 0xCE */ { 4751, 14, 17, 16, 1, -17 },
/* 0xCF */ { 4781, 17, 17, 19, 1, -17 },
/* 0xD0 */ { 4818, 13, 17, 17, 2, -17 },
/* 0xD1 */ { 4846, 13, 17, 16, 2, -17 },
/* 0xD2 */ { 4874, 0, 0, 5, 0, 0 },
/* 0xD3 */ { 4874, 12, 17, 15, 2, -17 },
/* 0xD4 */ { 4900, 14, 17, 14, 0, -17 },
/* 0xD5 */ { 4930, 16, 17, 16, 0, -17 },
/* 0xD6 */ { 4964, 16, 17, 18, 1, -17 },
/* 0xD7 */ { 4998, 15, 17, 15, 0, -17 },
/* 0xD8 */ { 5030, 16, 17, 19, 2, -17 },
/* 0xD9 */ { 5064, 16, 17, 18, 1, -17 },
/* 0xDA */ { 5098, 6, 20, 6, 0, -20 },
/* 0xDB */ { 5113, 16, 20, 16, 0, -20 },
/* 0xDC */ { 5153, 12, 17, 14, 1, -17 },
/* 0xDD */ { 5179, 9, 17, 11, 1, -17 },
/* 0xDE */ { 5199, 10, 22, 14, 2, -17 },
/* 0xDF */ { 5227, 4, 17, 6, 1, -17 },
/* 0xE0 */ { 5236, 10, 17, 14, 2, -17 },
/* 0xE1 */ { 5258, 12, 13, 14, 1, -13 },
/* 0xE2 */ { 5278, 11, 22, 14, 2, -17 },
/* 0xE3 */ { 5309, 12, 18, 11, -1, -13 },
/* 0xE4 */ { 5336, 11, 17, 13, 1, -17 },
/* 0xE5 */ { 5360, 9, 13, 11, 1, -13 },
/* 0xE6 */ { 5375, 9, 22, 11, 1, -17 },
/* 0xE7 */ { 5400, 10, 18, 14, 2, -13 },
/* 0xE8 */ { 5423, 11, 17, 13, 1, -17 },
/* 0xE9 */ { 5447, 2, 13, 6, 2, -13 },
/* 0xEA */ { 5451, 10, 13, 12, 2, -13 },
/* 0xEB */ { 5468, 13, 17, 12, -1, -17 },
/* 0xEC */ { 5496, 10, 18, 14, 2, -13 },
/* 0xED */ { 5519, 11, 13, 11, 0, -13 },
/* 0xEE */ { 5537, 9, 22, 11, 1, -17 },
/* 0xEF */ { 5562, 11, 13, 13, 1, -13 },
/* 0xF0 */ { 5580, 16, 13, 17, 0, -13 },
/* 0xF1 */ { 5606, 11, 18, 14, 2, -13 },
/* 0xF2 */ { 5631, 11, 18, 12, 1, -13 },
/* 0xF3 */ { 5656, 13, 13, 15, 1, -13 },
/* 0xF4 */ { 5678, 6, 13, 9, 1, -13 },
/* 0xF5 */ { 5688, 10, 13, 14, 2, -13 },
/* 0xF6 */ { 5705, 14, 18, 16, 1, -13 },
/* 0xF7 */ { 5737, 13, 18, 13, 0, -13 },
/* 0xF8 */ { 5767, 14, 18, 18, 2, -13 },
/* 0xF9 */ { 5799, 16, 13, 18, 1, -13 },
/* 0xFA */ { 5825, 6, 16, 6, 0, -16 },
/* 0xFB */ { 5837, 10, 16, 14, 2, -16 },
/* 0xFC */ { 5857, 11, 17, 13, 1, -17 },
/* 0xFD */ { 5881, 10, 17, 14, 2, -17 },
/* 0xFE */ { 5903, 16, 17, 18, 1, -17 },
/* 0xFF */ { 5937, 0, 0, 5, 0, 0 },
};
const GFXfont FreeSans12pt_Win1253 PROGMEM = {
(uint8_t*)FreeSans12pt_Win1253Bitmaps,
(GFXglyph*)FreeSans12pt_Win1253Glyphs,
0x01, 0xFF, 19
};

View File

@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans6pt_Win1253
*/
const uint8_t FreeSans6pt_Win1253Bitmaps[] PROGMEM = {
/* 0x01 */ 0x1C, 0x0A, 0x05, 0x04, 0xFE, 0x08, 0x1C, 0x02, 0x07, 0xE0, 0x9F, 0xC0,
/* 0x02 */ 0x3F, 0xF0, 0x40, 0xE0, 0x10, 0x3F, 0x04, 0x9E, 0x28, 0x14, 0x0E, 0x00,
/* 0x03 */ 0x3F, 0x10, 0x28, 0x06, 0x49, 0x80, 0x60, 0x19, 0x26, 0x31, 0x40, 0x8F, 0xC0,
/* 0x04 */ 0x3F, 0x10, 0x2A, 0x16, 0x49, 0xA1, 0x60, 0x19, 0xE6, 0x31, 0x40, 0x8F, 0xC0,
/* 0x05 */ 0x28, 0x15, 0x2A, 0xB5, 0x55, 0xA8, 0x54, 0x12, 0x04, 0x41, 0x08, 0x81, 0xC0,
/* 0x06 */ 0x04, 0x08, 0x88, 0x82, 0x07, 0x01, 0x11, 0xA2, 0xC4, 0x40, 0x70, 0x20, 0x88, 0x88, 0x10, 0x00,
/* 0x07 */
/* 0x08 */ 0x03, 0x83, 0x44, 0x48, 0x28, 0x01, 0x80, 0x17, 0xFE, 0x08, 0x45, 0x28, 0x84, 0x00,
/* 0x09 */ 0x01, 0xC0, 0x68, 0x82, 0x41, 0x10, 0x02, 0x80, 0x06, 0x00, 0x14, 0x00, 0x8F, 0xFC,
/* 0x0A */
/* 0x0B */ 0x22, 0x2A, 0xA2, 0x30, 0x18, 0x0A, 0x09, 0x04, 0x44, 0x14, 0x04, 0x00,
/* 0x0C */ 0x46, 0x00, 0x19, 0x03, 0x21, 0x20, 0x93, 0x04, 0x20, 0x11, 0x80, 0x50, 0x02, 0x7F, 0xE0,
/* 0x0D */
/* 0x0E */ 0x08, 0x0E, 0x08, 0x88, 0x24, 0x12, 0x09, 0x05, 0x01, 0xFF, 0x8A, 0x02, 0x00,
/* 0x0F */ 0x3F, 0x14, 0xAA, 0x16, 0x01, 0x92, 0x60, 0x18, 0xC6, 0x49, 0x40, 0x8F, 0xC0,
/* 0x10 */ 0x1B, 0x02, 0xA0, 0x54, 0x12, 0x42, 0x48, 0x49, 0x31, 0x1E, 0x23, 0xEA, 0xFE, 0x3C,
/* 0x11 */ 0x3F, 0x02, 0x00, 0x20, 0x6D, 0x27, 0xF8, 0x3F, 0xC1, 0xFE, 0x37, 0xD0, 0xBE, 0x40, 0xE1, 0xE2, 0x00,
/* 0x12 */ 0x12, 0x42, 0x20, 0x24, 0xC0, 0x29, 0x99, 0x05, 0x23, 0x30, 0xB0, 0x30, 0x00,
/* 0x13 */ 0x3F, 0x88, 0x0A, 0x44, 0xD5, 0x58, 0x03, 0x00, 0x67, 0xCC, 0x71, 0x40, 0x47, 0xF0,
/* 0x14 */ 0x3F, 0x18, 0x69, 0x26, 0x85, 0xA1, 0x6C, 0xD8, 0x06, 0x31, 0x40, 0x8F, 0xC0,
/* 0x15 */ 0x3F, 0x11, 0x00, 0xE8, 0x03, 0xA0, 0x1F, 0xB3, 0x7E, 0x00, 0xE9, 0xE0, 0x23, 0x00, 0x40, 0x40, 0xFE, 0x00,
/* 0x16 */ 0x30, 0x38, 0x3A, 0x3E, 0x6E, 0xEB, 0xC3, 0xC3, 0x66, 0x3C,
/* 0x17 */ 0x3F, 0x04, 0x00, 0x82, 0x88, 0x5C, 0xA4, 0x49, 0x22, 0x81, 0x98, 0xC4, 0x40, 0xA3, 0xF0,
/* 0x18 */ 0x07, 0x80, 0x42, 0x04, 0x08, 0x21, 0x41, 0x42, 0x60, 0x0E, 0x8C, 0xB2, 0x89, 0x50, 0x52, 0x82, 0x80,
/* 0x19 */ 0x3F, 0xC4, 0x02, 0x80, 0x18, 0x01, 0xB3, 0x1B, 0xB9, 0x80, 0x19, 0xE1, 0x40, 0x23, 0xFC,
/* 0x1A */ 0xFF, 0xC0, 0x67, 0x34, 0x58, 0x4C, 0x46, 0x03, 0x11, 0x80, 0xFF, 0xC0,
/* 0x1B */ 0x0F, 0xC0, 0x40, 0x82, 0x49, 0x08, 0x04, 0x00, 0x00, 0x12, 0x02, 0x31, 0x34, 0x0B, 0x88, 0x45, 0x00, 0x20,
/* 0x1C */ 0x3F, 0x88, 0x0A, 0x44, 0xC9, 0x19, 0x3B, 0x00, 0x60, 0x4C, 0x71, 0x40, 0x47, 0xF0,
/* 0x1D */ 0x3F, 0x8B, 0x0A, 0x00, 0xC8, 0x18, 0x13, 0x00, 0x48, 0xCA, 0xC1, 0x44, 0x53, 0x30,
/* 0x1E */ 0x19, 0xC2, 0x02, 0x50, 0x1E, 0x49, 0x80, 0x12, 0x01, 0x27, 0x92, 0x01, 0x10, 0x20, 0xFC,
/* 0x1F */ 0x30, 0x1C, 0x0C, 0x3E, 0x7E, 0xCF, 0x07, 0xC7, 0x7F, 0x3F,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFC, 0x80,
/* '"' 0x22 */ 0xB6, 0x80,
/* '#' 0x23 */ 0x24, 0x51, 0xF9, 0x42, 0x9F, 0x92, 0x28,
/* '$' 0x24 */ 0x10, 0xE5, 0x55, 0x50, 0xE1, 0x65, 0x55, 0xE1, 0x00,
/* '%' 0x25 */ 0x71, 0x24, 0x89, 0x22, 0x50, 0x74, 0x02, 0x70, 0xA4, 0x49, 0x11, 0xC0,
/* '&' 0x26 */ 0x71, 0x24, 0x9C, 0x62, 0x58, 0xA7, 0xF4,
/* ''' 0x27 */ 0xE0,
/* '(' 0x28 */ 0x5A, 0xAA, 0x94,
/* ')' 0x29 */ 0x89, 0x12, 0x49, 0x29, 0x00,
/* '*' 0x2A */ 0x5E, 0x80,
/* '+' 0x2B */ 0x21, 0x3E, 0x42, 0x00,
/* ',' 0x2C */ 0xE0,
/* '-' 0x2D */ 0xC0,
/* '.' 0x2E */ 0x80,
/* '/' 0x2F */ 0x24, 0xA4, 0xA4, 0x80,
/* '0' 0x30 */ 0x76, 0xE3, 0x18, 0xC6, 0x3B, 0x70,
/* '1' 0x31 */ 0x27, 0x92, 0x49, 0x20,
/* '2' 0x32 */ 0x79, 0x10, 0x41, 0x08, 0xC6, 0x10, 0xFC,
/* '3' 0x33 */ 0x79, 0x30, 0x43, 0x18, 0x10, 0x71, 0x78,
/* '4' 0x34 */ 0x08, 0x61, 0x8A, 0x49, 0x2F, 0xC2, 0x08,
/* '5' 0x35 */ 0xFC, 0x21, 0xE8, 0x84, 0x31, 0xF0,
/* '6' 0x36 */ 0x74, 0x61, 0xE8, 0xC6, 0x31, 0x70,
/* '7' 0x37 */ 0xF8, 0x44, 0x22, 0x11, 0x08, 0x40,
/* '8' 0x38 */ 0x39, 0x34, 0x53, 0x39, 0x1C, 0x51, 0x38,
/* '9' 0x39 */ 0x39, 0x3C, 0x71, 0x4C, 0xF0, 0x53, 0x78,
/* ':' 0x3A */ 0x82,
/* ';' 0x3B */ 0x87,
/* '<' 0x3C */ 0x3E, 0x30, 0x60, 0x80,
/* '=' 0x3D */ 0xF8, 0x3E,
/* '>' 0x3E */ 0xE0, 0xC6, 0xC8, 0x00,
/* '?' 0x3F */ 0x74, 0x42, 0x11, 0x10, 0x80, 0x20,
/* '@' 0x40 */ 0x0F, 0x86, 0x19, 0x9A, 0xA4, 0xD9, 0x13, 0x22, 0x56, 0xDA, 0x6E, 0x60, 0x06, 0x00, 0x3C, 0x00,
/* 'A' 0x41 */ 0x18, 0x18, 0x24, 0x24, 0x24, 0x7E, 0x42, 0x42, 0xC3,
/* 'B' 0x42 */ 0xFA, 0x18, 0x61, 0xFA, 0x18, 0x61, 0xFC,
/* 'C' 0x43 */ 0x3E, 0x63, 0x40, 0x40, 0xC0, 0x40, 0x41, 0x63, 0x3E,
/* 'D' 0x44 */ 0xF9, 0x0A, 0x1C, 0x18, 0x30, 0x61, 0xC2, 0xF8,
/* 'E' 0x45 */ 0xFE, 0x08, 0x20, 0xFE, 0x08, 0x20, 0xFC,
/* 'F' 0x46 */ 0xFE, 0x08, 0x20, 0xFA, 0x08, 0x20, 0x80,
/* 'G' 0x47 */ 0x1E, 0x61, 0x40, 0x40, 0xC7, 0x41, 0x41, 0x63, 0x1D,
/* 'H' 0x48 */ 0x83, 0x06, 0x0C, 0x1F, 0xF0, 0x60, 0xC1, 0x82,
/* 'I' 0x49 */ 0xFF, 0x80,
/* 'J' 0x4A */ 0x08, 0x42, 0x10, 0x87, 0x29, 0x70,
/* 'K' 0x4B */ 0x85, 0x12, 0x45, 0x0D, 0x13, 0x22, 0x42, 0x86,
/* 'L' 0x4C */ 0x84, 0x21, 0x08, 0x42, 0x10, 0xF8,
/* 'M' 0x4D */ 0xC3, 0xC3, 0xC3, 0xA5, 0xA5, 0xA5, 0x99, 0x99, 0x99,
/* 'N' 0x4E */ 0x83, 0x86, 0x8D, 0x19, 0x33, 0x62, 0xC3, 0x86,
/* 'O' 0x4F */ 0x1E, 0x31, 0x90, 0x68, 0x1C, 0x0A, 0x05, 0x06, 0xC6, 0x1E, 0x00,
/* 'P' 0x50 */ 0xFA, 0x18, 0x61, 0xFA, 0x08, 0x20, 0x80,
/* 'Q' 0x51 */ 0x1E, 0x31, 0x90, 0x68, 0x1C, 0x0A, 0x05, 0x16, 0xC6, 0x1F, 0x00, 0x40,
/* 'R' 0x52 */ 0xFD, 0x0E, 0x1C, 0x2F, 0x90, 0xA1, 0x42, 0x86,
/* 'S' 0x53 */ 0x7A, 0x18, 0x30, 0x78, 0x38, 0x61, 0x78,
/* 'T' 0x54 */ 0xFE, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10,
/* 'U' 0x55 */ 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xE2, 0x78,
/* 'V' 0x56 */ 0xC2, 0x85, 0x0B, 0x22, 0x44, 0x8E, 0x0C, 0x18,
/* 'W' 0x57 */ 0xC4, 0x28, 0xCD, 0x29, 0x25, 0x24, 0xA4, 0x52, 0x8C, 0x61, 0x8C, 0x31, 0x80,
/* 'X' 0x58 */ 0x87, 0x34, 0x8C, 0x30, 0xC4, 0xA3, 0x84,
/* 'Y' 0x59 */ 0xC3, 0x42, 0x24, 0x34, 0x18, 0x08, 0x08, 0x08, 0x08,
/* 'Z' 0x5A */ 0x7E, 0x0C, 0x30, 0x41, 0x06, 0x18, 0x20, 0xFE,
/* '[' 0x5B */ 0xEA, 0xAA, 0xAB,
/* '\' 0x5C */ 0x92, 0x24, 0x89, 0x20,
/* ']' 0x5D */ 0xD5, 0x55, 0x57,
/* '^' 0x5E */ 0x46, 0xA9,
/* '_' 0x5F */ 0xFE,
/* '`' 0x60 */ 0x80,
/* 'a' 0x61 */ 0x79, 0x20, 0x4F, 0xC6, 0x37, 0x40,
/* 'b' 0x62 */ 0x84, 0x3D, 0x18, 0xC6, 0x31, 0xF0,
/* 'c' 0x63 */ 0x39, 0x3C, 0x20, 0xC1, 0x33, 0x80,
/* 'd' 0x64 */ 0x04, 0x13, 0xD3, 0xC6, 0x1C, 0x53, 0x3C,
/* 'e' 0x65 */ 0x39, 0x38, 0x7F, 0x81, 0x13, 0x80,
/* 'f' 0x66 */ 0x6B, 0xA4, 0x92, 0x40,
/* 'g' 0x67 */ 0x35, 0x3C, 0x61, 0xC5, 0x33, 0x41, 0x4D, 0xE0,
/* 'h' 0x68 */ 0x84, 0x3D, 0x38, 0xC6, 0x31, 0x88,
/* 'i' 0x69 */ 0xBF, 0x80,
/* 'j' 0x6A */ 0x45, 0x55, 0x57,
/* 'k' 0x6B */ 0x84, 0x25, 0x4E, 0x52, 0xD2, 0x88,
/* 'l' 0x6C */ 0xFF, 0x80,
/* 'm' 0x6D */ 0xF7, 0x99, 0x91, 0x91, 0x91, 0x91, 0x91,
/* 'n' 0x6E */ 0xF4, 0x63, 0x18, 0xC6, 0x20,
/* 'o' 0x6F */ 0x39, 0x3C, 0x61, 0xC5, 0x33, 0x80,
/* 'p' 0x70 */ 0xF4, 0x63, 0x18, 0xC7, 0xD0, 0x80,
/* 'q' 0x71 */ 0x3D, 0x3C, 0x61, 0xC5, 0x37, 0x41, 0x04,
/* 'r' 0x72 */ 0xF2, 0x49, 0x20,
/* 's' 0x73 */ 0x7A, 0x50, 0xE0, 0xE5, 0xE0,
/* 't' 0x74 */ 0x5D, 0x24, 0x93,
/* 'u' 0x75 */ 0x8C, 0x63, 0x18, 0xCF, 0xA0,
/* 'v' 0x76 */ 0x85, 0x24, 0x92, 0x30, 0xC3, 0x00,
/* 'w' 0x77 */ 0x89, 0x59, 0x59, 0x55, 0x56, 0x26, 0x26,
/* 'x' 0x78 */ 0x4A, 0x4C, 0x43, 0x27, 0x20,
/* 'y' 0x79 */ 0x8A, 0x52, 0xA5, 0x18, 0x84, 0x22, 0x00,
/* 'z' 0x7A */ 0x78, 0x44, 0x46, 0x23, 0xE0,
/* '{' 0x7B */ 0x6A, 0xAA, 0xA9,
/* '|' 0x7C */ 0xFF, 0xE0,
/* '}' 0x7D */ 0x95, 0x55, 0x56,
/* '~' 0x7E */ 0x66, 0x60,
/* 0x7F */
/* 0x80 */ 0x1C, 0x45, 0x07, 0xE4, 0x1F, 0x10, 0x10, 0x1E,
/* 0x81 */
/* 0x82 */ 0xE0,
/* 0x83 */ 0x6B, 0xA4, 0x92, 0x49, 0x60,
/* 0x84 */ 0xB6, 0x80,
/* 0x85 */ 0xA8,
/* 0x86 */ 0x21, 0x09, 0xF2, 0x10, 0x84, 0x21, 0x08,
/* 0x87 */ 0x21, 0x09, 0xF2, 0x10, 0x84, 0xF9, 0x08,
/* 0x88 */ 0x54,
/* 0x89 */ 0x62, 0x09, 0x40, 0x98, 0x06, 0x80, 0x10, 0x01, 0x66, 0x29, 0x92, 0x99, 0x06, 0x60,
/* 0x8A */ 0x28, 0x47, 0xA1, 0x83, 0x07, 0x83, 0x87, 0x17, 0x80,
/* 0x8B */ 0x64,
/* 0x8C */ 0x3B, 0xE8, 0xC2, 0x08, 0x41, 0x08, 0x3F, 0x04, 0x20, 0x82, 0x30, 0x3B, 0xE0,
/* 0x8D */
/* 0x8E */ 0x14, 0x11, 0xF8, 0x30, 0xC1, 0x04, 0x18, 0x61, 0xFC,
/* 0x8F */
/* 0x90 */
/* 0x91 */ 0xE0,
/* 0x92 */ 0xE0,
/* 0x93 */ 0xB6, 0x80,
/* 0x94 */ 0xB6, 0x80,
/* 0x95 */ 0xFF, 0x80,
/* 0x96 */ 0xFC,
/* 0x97 */ 0xFF, 0xF0,
/* 0x98 */ 0xDB,
/* 0x99 */ 0xE6, 0x28, 0xCD, 0x19, 0xA3, 0x34, 0x6A, 0x8B, 0x51, 0x68,
/* 0x9A */ 0x52, 0x69, 0x8E, 0x19, 0x60,
/* 0x9B */ 0x98,
/* 0x9C */ 0x7B, 0xD9, 0xCE, 0x10, 0xC3, 0xF8, 0x41, 0x9C, 0x5E, 0xF0,
/* 0x9D */
/* 0x9E */ 0x51, 0x1E, 0x11, 0x11, 0x88, 0xF8,
/* 0x9F */ 0x29, 0x05, 0x12, 0x22, 0x87, 0x04, 0x08, 0x10, 0x20,
/* 0xA0 */
/* 0xA1 */ 0xBF, 0x80,
/* 0xA2 */ 0x23, 0xAB, 0x4A, 0x52, 0xAE, 0x20,
/* 0xA3 */ 0x39, 0x14, 0x10, 0xF0, 0x82, 0x1C, 0x4C,
/* 0xA4 */ 0xFC, 0x63, 0xF0,
/* 0xA5 */ 0x8C, 0x54, 0xAF, 0x93, 0xE4, 0x20,
/* 0xA6 */ 0xF9, 0xF0,
/* 0xA7 */ 0x32, 0x91, 0xC9, 0x47, 0x26, 0x14, 0xA4, 0xC0,
/* 0xA8 */ 0xA0,
/* 0xA9 */ 0x3E, 0x3F, 0xB8, 0xF4, 0x1A, 0x0D, 0x17, 0x76, 0xC6, 0x3E, 0x00,
/* 0xAA */ 0x61, 0x79, 0x60,
/* 0xAB */ 0x5A, 0xA5,
/* 0xAC */ 0xFC, 0x10, 0x40,
/* 0xAD */
/* 0xAE */ 0x3E, 0x31, 0xB7, 0x72, 0x99, 0xCC, 0xC7, 0x56, 0xC6, 0x3E, 0x00,
/* 0xAF */ 0xE0,
/* 0xB0 */ 0x69, 0x96,
/* 0xB1 */ 0x21, 0x3E, 0x42, 0x03, 0xE0,
/* 0xB2 */ 0x69, 0x3C, 0xF0,
/* 0xB3 */ 0x79, 0x29, 0x70,
/* 0xB4 */ 0x80,
/* 0xB5 */ 0x8A, 0x28, 0xA2, 0x8A, 0x6E, 0xE0, 0x80,
/* 0xB6 */ 0x7F, 0xAE, 0xBA, 0x68, 0xA2, 0x8A, 0x28, 0xA0,
/* 0xB7 */ 0x80,
/* 0xB8 */ 0x67, 0x80,
/* 0xB9 */ 0x75, 0x50,
/* 0xBA */ 0x69, 0x96, 0xF0,
/* 0xBB */ 0xA5, 0x5A,
/* 0xBC */ 0x42, 0x30, 0x84, 0x41, 0x10, 0x48, 0x82, 0x61, 0x28, 0x8F, 0x20, 0x80,
/* 0xBD */ 0x40, 0x63, 0x11, 0x09, 0x74, 0xA8, 0x84, 0x44, 0x44, 0x43, 0x80,
/* 0xBE */ 0x71, 0x24, 0x82, 0x20, 0x50, 0x98, 0x9A, 0x61, 0x28, 0x4F, 0x20, 0x80,
/* 0xBF */ 0x20, 0x08, 0x44, 0x42, 0x11, 0x70,
/* 0xC0 */ 0x2D, 0x02, 0x22, 0x22, 0x22,
/* 0xC1 */ 0x10, 0x50, 0xA1, 0x44, 0x4F, 0x91, 0x41, 0x82,
/* 0xC2 */ 0xFA, 0x18, 0x61, 0xFE, 0x18, 0x61, 0xF8,
/* 0xC3 */ 0xFE, 0x08, 0x20, 0x82, 0x08, 0x20, 0x80,
/* 0xC4 */ 0x08, 0x0A, 0x05, 0x02, 0x82, 0x21, 0x11, 0x04, 0x82, 0x7F, 0x00,
/* 0xC5 */ 0xFE, 0x08, 0x20, 0xFE, 0x08, 0x20, 0xFC,
/* 0xC6 */ 0x7E, 0x08, 0x20, 0x41, 0x04, 0x08, 0x20, 0xFE,
/* 0xC7 */ 0x83, 0x06, 0x0C, 0x1F, 0xF0, 0x60, 0xC1, 0x82,
/* 0xC8 */ 0x38, 0x8A, 0x0C, 0x1B, 0xB0, 0x60, 0xA2, 0x38,
/* 0xC9 */ 0xFF, 0x80,
/* 0xCA */ 0x83, 0x0A, 0x24, 0x8A, 0x1A, 0x22, 0x42, 0x82,
/* 0xCB */ 0x08, 0x0A, 0x05, 0x02, 0x82, 0x21, 0x11, 0x04, 0x82, 0x41, 0x00,
/* 0xCC */ 0x83, 0x8F, 0x1D, 0x5A, 0xB5, 0x6A, 0xC9, 0x92,
/* 0xCD */ 0x83, 0x86, 0x8D, 0x19, 0x31, 0x62, 0xC3, 0x82,
/* 0xCE */ 0xFC, 0x00, 0x00, 0x78, 0x00, 0x00, 0xFC,
/* 0xCF */ 0x38, 0x8A, 0x0C, 0x18, 0x30, 0x60, 0xA2, 0x38,
/* 0xD0 */ 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x82,
/* 0xD1 */ 0xFA, 0x18, 0x61, 0xFA, 0x08, 0x20, 0x80,
/* 0xD2 */
/* 0xD3 */ 0xFE, 0x04, 0x08, 0x10, 0x84, 0x20, 0xFC,
/* 0xD4 */ 0xFE, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10,
/* 0xD5 */ 0x82, 0x89, 0x11, 0x41, 0x02, 0x04, 0x08, 0x10,
/* 0xD6 */ 0x10, 0xFA, 0x4C, 0x99, 0x32, 0x64, 0xBE, 0x10,
/* 0xD7 */ 0x82, 0x89, 0x11, 0x41, 0x05, 0x11, 0x22, 0x82,
/* 0xD8 */ 0x93, 0x26, 0x4C, 0x99, 0x2F, 0x84, 0x08, 0x10,
/* 0xD9 */ 0x38, 0x8A, 0x0C, 0x18, 0x30, 0x60, 0xA2, 0xEE,
/* 0xDA */ 0xA1, 0x24, 0x92, 0x49, 0x00,
/* 0xDB */ 0x28, 0x02, 0x0A, 0x24, 0x45, 0x04, 0x08, 0x10, 0x20, 0x40,
/* 0xDC */ 0x11, 0x00, 0xD9, 0x4A, 0x52, 0x93, 0x40,
/* 0xDD */ 0x11, 0x00, 0xF8, 0x41, 0x90, 0x83, 0xC0,
/* 0xDE */ 0x11, 0x01, 0x6C, 0xC6, 0x31, 0x8C, 0x42, 0x10,
/* 0xDF */ 0x62, 0xAA, 0xA0,
/* 0xE0 */ 0x25, 0x81, 0x18, 0xC6, 0x31, 0x8B, 0x80,
/* 0xE1 */ 0x6C, 0xA5, 0x29, 0x49, 0xA0,
/* 0xE2 */ 0x74, 0x63, 0x1B, 0x46, 0x39, 0xB4, 0x20,
/* 0xE3 */ 0x44, 0x89, 0x11, 0x42, 0x85, 0x04, 0x08, 0x10,
/* 0xE4 */ 0x71, 0x1D, 0x18, 0xC6, 0x31, 0x70,
/* 0xE5 */ 0x7C, 0x20, 0xC8, 0x41, 0xE0,
/* 0xE6 */ 0x72, 0x44, 0x88, 0x88, 0x71, 0x20,
/* 0xE7 */ 0xB6, 0x63, 0x18, 0xC6, 0x21, 0x08,
/* 0xE8 */ 0x74, 0x63, 0x1F, 0xC6, 0x31, 0x70,
/* 0xE9 */ 0xFE,
/* 0xEA */ 0x8A, 0x4A, 0x38, 0x92, 0x48, 0x80,
/* 0xEB */ 0x20, 0x41, 0x04, 0x28, 0xA2, 0x91, 0x44,
/* 0xEC */ 0x8C, 0x63, 0x18, 0xC7, 0xF0, 0x80,
/* 0xED */ 0x8C, 0x54, 0xA5, 0x10, 0x80,
/* 0xEE */ 0x68, 0x86, 0x48, 0x88, 0x71, 0x20,
/* 0xEF */ 0x74, 0x63, 0x18, 0xC5, 0xC0,
/* 0xF0 */ 0xFF, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
/* 0xF1 */ 0x74, 0x63, 0x18, 0xC7, 0xD0, 0x80,
/* 0xF2 */ 0x34, 0x88, 0x88, 0x71, 0x60,
/* 0xF3 */ 0x7F, 0x12, 0x24, 0x48, 0x91, 0x1C, 0x00,
/* 0xF4 */ 0xE9, 0x24, 0x90,
/* 0xF5 */ 0x8C, 0x63, 0x18, 0xC5, 0xC0,
/* 0xF6 */ 0x5A, 0x59, 0x65, 0x95, 0x53, 0x84, 0x10,
/* 0xF7 */ 0x49, 0x24, 0x8C, 0x30, 0xC4, 0x92, 0x48,
/* 0xF8 */ 0x93, 0x26, 0x4C, 0x99, 0x32, 0x5F, 0x08, 0x10,
/* 0xF9 */ 0x45, 0x06, 0x4C, 0x99, 0x32, 0x5B, 0x00,
/* 0xFA */ 0xA1, 0x24, 0x92, 0x40,
/* 0xFB */ 0x50, 0x23, 0x18, 0xC6, 0x31, 0x70,
/* 0xFC */ 0x11, 0x00, 0xE8, 0xC6, 0x31, 0x8B, 0x80,
/* 0xFD */ 0x21, 0x01, 0x18, 0xC6, 0x31, 0x8B, 0x80,
/* 0xFE */ 0x08, 0x20, 0x02, 0x28, 0x32, 0x64, 0xC9, 0x92, 0xD8,
/* 0xFF */
};
const GFXglyph FreeSans6pt_Win1253Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 9, 10, 11, 1, -9 },
/* 0x02 */ { 12, 9, 10, 11, 1, -8 },
/* 0x03 */ { 24, 10, 10, 12, 1, -8 },
/* 0x04 */ { 37, 10, 10, 12, 1, -8 },
/* 0x05 */ { 50, 10, 10, 12, 1, -9 },
/* 0x06 */ { 63, 11, 11, 13, 1, -9 },
/* 0x07 */ { 79, 0, 0, 8, 0, 0 },
/* 0x08 */ { 79, 12, 9, 14, 1, -8 },
/* 0x09 */ { 93, 14, 8, 16, 1, -7 },
/* 0x0A */ { 107, 0, 0, 8, 0, 0 },
/* 0x0B */ { 107, 9, 10, 11, 1, -9 },
/* 0x0C */ { 119, 13, 9, 15, 1, -8 },
/* 0x0D */ { 134, 0, 0, 8, 0, 0 },
/* 0x0E */ { 134, 9, 11, 11, 1, -9 },
/* 0x0F */ { 147, 10, 10, 12, 1, -9 },
/* 0x10 */ { 160, 11, 10, 13, 1, -9 },
/* 0x11 */ { 174, 13, 10, 15, 1, -9 },
/* 0x12 */ { 191, 10, 10, 12, 1, -9 },
/* 0x13 */ { 204, 11, 10, 13, 1, -9 },
/* 0x14 */ { 218, 10, 10, 12, 1, -9 },
/* 0x15 */ { 231, 14, 10, 16, 1, -9 },
/* 0x16 */ { 249, 8, 10, 10, 1, -9 },
/* 0x17 */ { 259, 12, 10, 14, 1, -9 },
/* 0x18 */ { 274, 13, 10, 15, 1, -9 },
/* 0x19 */ { 291, 12, 10, 14, 1, -9 },
/* 0x1A */ { 306, 9, 10, 11, 1, -8 },
/* 0x1B */ { 318, 14, 10, 16, 1, -9 },
/* 0x1C */ { 336, 11, 10, 13, 1, -9 },
/* 0x1D */ { 350, 11, 10, 13, 1, -9 },
/* 0x1E */ { 364, 12, 10, 14, 1, -9 },
/* 0x1F */ { 379, 8, 10, 11, 2, -9 },
/* ' ' 0x20 */ { 389, 0, 0, 3, 0, 0 },
/* '!' 0x21 */ { 389, 1, 9, 4, 2, -8 },
/* '"' 0x22 */ { 391, 3, 3, 4, 0, -8 },
/* '#' 0x23 */ { 393, 7, 8, 7, 0, -7 },
/* '$' 0x24 */ { 400, 6, 11, 7, 0, -9 },
/* '%' 0x25 */ { 409, 10, 9, 11, 0, -8 },
/* '&' 0x26 */ { 421, 6, 9, 8, 1, -8 },
/* ''' 0x27 */ { 428, 1, 3, 2, 1, -8 },
/* '(' 0x28 */ { 429, 2, 11, 4, 1, -8 },
/* ')' 0x29 */ { 432, 3, 11, 4, 0, -8 },
/* '*' 0x2A */ { 437, 3, 3, 5, 1, -8 },
/* '+' 0x2B */ { 439, 5, 5, 7, 1, -4 },
/* ',' 0x2C */ { 443, 1, 3, 3, 1, 0 },
/* '-' 0x2D */ { 444, 2, 1, 4, 1, -3 },
/* '.' 0x2E */ { 445, 1, 1, 3, 1, 0 },
/* '/' 0x2F */ { 446, 3, 9, 3, 0, -8 },
/* '0' 0x30 */ { 450, 5, 9, 7, 1, -8 },
/* '1' 0x31 */ { 456, 3, 9, 7, 1, -8 },
/* '2' 0x32 */ { 460, 6, 9, 7, 0, -8 },
/* '3' 0x33 */ { 467, 6, 9, 7, 0, -8 },
/* '4' 0x34 */ { 474, 6, 9, 7, 0, -8 },
/* '5' 0x35 */ { 481, 5, 9, 7, 1, -8 },
/* '6' 0x36 */ { 487, 5, 9, 7, 1, -8 },
/* '7' 0x37 */ { 493, 5, 9, 7, 1, -8 },
/* '8' 0x38 */ { 499, 6, 9, 7, 0, -8 },
/* '9' 0x39 */ { 506, 6, 9, 7, 0, -8 },
/* ':' 0x3A */ { 513, 1, 7, 3, 1, -6 },
/* ';' 0x3B */ { 514, 1, 8, 3, 1, -5 },
/* '<' 0x3C */ { 515, 5, 5, 7, 1, -4 },
/* '=' 0x3D */ { 519, 5, 3, 7, 1, -3 },
/* '>' 0x3E */ { 521, 5, 5, 7, 1, -4 },
/* '?' 0x3F */ { 525, 5, 9, 7, 1, -8 },
/* '@' 0x40 */ { 531, 11, 11, 12, 0, -8 },
/* 'A' 0x41 */ { 547, 8, 9, 8, 0, -8 },
/* 'B' 0x42 */ { 556, 6, 9, 8, 1, -8 },
/* 'C' 0x43 */ { 563, 8, 9, 9, 0, -8 },
/* 'D' 0x44 */ { 572, 7, 9, 8, 1, -8 },
/* 'E' 0x45 */ { 580, 6, 9, 8, 1, -8 },
/* 'F' 0x46 */ { 587, 6, 9, 7, 1, -8 },
/* 'G' 0x47 */ { 594, 8, 9, 9, 0, -8 },
/* 'H' 0x48 */ { 603, 7, 9, 9, 1, -8 },
/* 'I' 0x49 */ { 611, 1, 9, 3, 1, -8 },
/* 'J' 0x4A */ { 613, 5, 9, 6, 0, -8 },
/* 'K' 0x4B */ { 619, 7, 9, 8, 1, -8 },
/* 'L' 0x4C */ { 627, 5, 9, 7, 1, -8 },
/* 'M' 0x4D */ { 633, 8, 9, 10, 1, -8 },
/* 'N' 0x4E */ { 642, 7, 9, 9, 1, -8 },
/* 'O' 0x4F */ { 650, 9, 9, 9, 0, -8 },
/* 'P' 0x50 */ { 661, 6, 9, 8, 1, -8 },
/* 'Q' 0x51 */ { 668, 9, 10, 9, 0, -8 },
/* 'R' 0x52 */ { 680, 7, 9, 9, 1, -8 },
/* 'S' 0x53 */ { 688, 6, 9, 8, 1, -8 },
/* 'T' 0x54 */ { 695, 7, 9, 8, 0, -8 },
/* 'U' 0x55 */ { 703, 7, 9, 9, 1, -8 },
/* 'V' 0x56 */ { 711, 7, 9, 8, 0, -8 },
/* 'W' 0x57 */ { 719, 11, 9, 11, 0, -8 },
/* 'X' 0x58 */ { 732, 6, 9, 8, 1, -8 },
/* 'Y' 0x59 */ { 739, 8, 9, 8, 0, -8 },
/* 'Z' 0x5A */ { 748, 7, 9, 7, 0, -8 },
/* '[' 0x5B */ { 756, 2, 12, 3, 1, -8 },
/* '\' 0x5C */ { 759, 3, 9, 3, 0, -8 },
/* ']' 0x5D */ { 763, 2, 12, 3, 0, -8 },
/* '^' 0x5E */ { 766, 4, 4, 6, 1, -8 },
/* '_' 0x5F */ { 768, 7, 1, 7, 0, 2 },
/* '`' 0x60 */ { 769, 1, 1, 3, 1, -8 },
/* 'a' 0x61 */ { 770, 6, 7, 7, 0, -6 },
/* 'b' 0x62 */ { 776, 5, 9, 7, 1, -8 },
/* 'c' 0x63 */ { 782, 6, 7, 6, 0, -6 },
/* 'd' 0x64 */ { 788, 6, 9, 7, 0, -8 },
/* 'e' 0x65 */ { 795, 6, 7, 6, 0, -6 },
/* 'f' 0x66 */ { 801, 3, 9, 3, 0, -8 },
/* 'g' 0x67 */ { 805, 6, 10, 7, 0, -6 },
/* 'h' 0x68 */ { 813, 5, 9, 6, 1, -8 },
/* 'i' 0x69 */ { 819, 1, 9, 3, 1, -8 },
/* 'j' 0x6A */ { 821, 2, 12, 3, 0, -8 },
/* 'k' 0x6B */ { 824, 5, 9, 6, 1, -8 },
/* 'l' 0x6C */ { 830, 1, 9, 3, 1, -8 },
/* 'm' 0x6D */ { 832, 8, 7, 10, 1, -6 },
/* 'n' 0x6E */ { 839, 5, 7, 6, 1, -6 },
/* 'o' 0x6F */ { 844, 6, 7, 6, 0, -6 },
/* 'p' 0x70 */ { 850, 5, 9, 7, 1, -6 },
/* 'q' 0x71 */ { 856, 6, 9, 7, 0, -6 },
/* 'r' 0x72 */ { 863, 3, 7, 4, 1, -6 },
/* 's' 0x73 */ { 866, 5, 7, 6, 0, -6 },
/* 't' 0x74 */ { 871, 3, 8, 3, 0, -7 },
/* 'u' 0x75 */ { 874, 5, 7, 6, 1, -6 },
/* 'v' 0x76 */ { 879, 6, 7, 6, 0, -6 },
/* 'w' 0x77 */ { 885, 8, 7, 9, 0, -6 },
/* 'x' 0x78 */ { 892, 5, 7, 6, 0, -6 },
/* 'y' 0x79 */ { 897, 5, 10, 6, 0, -6 },
/* 'z' 0x7A */ { 904, 5, 7, 6, 0, -6 },
/* '{' 0x7B */ { 909, 2, 12, 4, 1, -8 },
/* '|' 0x7C */ { 912, 1, 11, 3, 1, -8 },
/* '}' 0x7D */ { 914, 2, 12, 4, 1, -8 },
/* '~' 0x7E */ { 917, 6, 2, 6, 0, -4 },
/* 0x7F */ { 919, 0, 0, 0, 0, 0 },
/* 0x80 */ { 919, 7, 9, 8, 0, -8 },
/* 0x81 */ { 927, 0, 0, 8, 0, 0 },
/* 0x82 */ { 927, 1, 3, 3, 1, 0 },
/* 0x83 */ { 928, 3, 12, 3, 0, -8 },
/* 0x84 */ { 933, 3, 3, 5, 1, 0 },
/* 0x85 */ { 935, 5, 1, 7, 1, 0 },
/* 0x86 */ { 936, 5, 11, 7, 1, -8 },
/* 0x87 */ { 943, 5, 11, 7, 1, -8 },
/* 0x88 */ { 950, 3, 2, 4, 0, -9 },
/* 0x89 */ { 951, 12, 9, 12, 0, -8 },
/* 0x8A */ { 965, 6, 11, 8, 1, -9 },
/* 0x8B */ { 974, 2, 3, 4, 1, -4 },
/* 0x8C */ { 975, 11, 9, 12, 0, -8 },
/* 0x8D */ { 988, 0, 0, 8, 0, 0 },
/* 0x8E */ { 988, 7, 10, 7, 0, -9 },
/* 0x8F */ { 997, 0, 0, 8, 0, 0 },
/* 0x90 */ { 997, 0, 0, 8, 0, 0 },
/* 0x91 */ { 997, 1, 3, 3, 1, -8 },
/* 0x92 */ { 998, 1, 3, 2, 1, -8 },
/* 0x93 */ { 999, 3, 3, 5, 1, -8 },
/* 0x94 */ { 1001, 3, 3, 5, 1, -8 },
/* 0x95 */ { 1003, 3, 3, 5, 1, -5 },
/* 0x96 */ { 1005, 6, 1, 6, 0, -3 },
/* 0x97 */ { 1006, 12, 1, 12, 0, -3 },
/* 0x98 */ { 1008, 4, 2, 4, 0, -8 },
/* 0x99 */ { 1009, 11, 7, 12, 1, -8 },
/* 0x9A */ { 1019, 4, 9, 6, 1, -8 },
/* 0x9B */ { 1024, 2, 3, 3, 1, -4 },
/* 0x9C */ { 1025, 11, 7, 11, 0, -6 },
/* 0x9D */ { 1035, 0, 0, 8, 0, 0 },
/* 0x9E */ { 1035, 5, 9, 6, 0, -8 },
/* 0x9F */ { 1041, 7, 10, 8, 1, -9 },
/* 0xA0 */ { 1050, 0, 0, 3, 0, 0 },
/* 0xA1 */ { 1050, 1, 9, 4, 1, -5 },
/* 0xA2 */ { 1052, 5, 9, 7, 1, -7 },
/* 0xA3 */ { 1058, 6, 9, 7, 0, -8 },
/* 0xA4 */ { 1065, 5, 4, 7, 1, -5 },
/* 0xA5 */ { 1068, 5, 9, 7, 1, -8 },
/* 0xA6 */ { 1074, 1, 12, 3, 1, -8 },
/* 0xA7 */ { 1076, 5, 12, 7, 1, -8 },
/* 0xA8 */ { 1084, 3, 1, 4, 0, -7 },
/* 0xA9 */ { 1085, 9, 9, 10, 0, -8 },
/* 0xAA */ { 1096, 4, 5, 4, 0, -8 },
/* 0xAB */ { 1099, 4, 4, 6, 1, -4 },
/* 0xAC */ { 1101, 6, 3, 7, 1, -4 },
/* 0xAD */ { 1104, 0, 0, 0, 0, 0 },
/* 0xAE */ { 1104, 9, 9, 10, 0, -8 },
/* 0xAF */ { 1115, 3, 1, 4, 0, -8 },
/* 0xB0 */ { 1116, 4, 4, 7, 2, -8 },
/* 0xB1 */ { 1118, 5, 7, 7, 1, -6 },
/* 0xB2 */ { 1123, 4, 5, 4, 0, -9 },
/* 0xB3 */ { 1126, 4, 5, 4, 0, -9 },
/* 0xB4 */ { 1129, 1, 1, 4, 1, -8 },
/* 0xB5 */ { 1130, 6, 9, 7, 1, -6 },
/* 0xB6 */ { 1137, 6, 10, 6, 1, -8 },
/* 0xB7 */ { 1145, 1, 1, 3, 1, -2 },
/* 0xB8 */ { 1146, 3, 3, 4, 1, 1 },
/* 0xB9 */ { 1148, 2, 6, 4, 1, -9 },
/* 0xBA */ { 1150, 4, 5, 4, 0, -8 },
/* 0xBB */ { 1153, 4, 4, 6, 1, -5 },
/* 0xBC */ { 1155, 10, 9, 10, 1, -8 },
/* 0xBD */ { 1167, 9, 9, 10, 1, -8 },
/* 0xBE */ { 1178, 10, 9, 11, 0, -8 },
/* 0xBF */ { 1190, 5, 9, 7, 1, -5 },
/* 0xC0 */ { 1196, 4, 10, 3, -1, -10 },
/* 0xC1 */ { 1201, 7, 9, 7, 0, -9 },
/* 0xC2 */ { 1209, 6, 9, 8, 1, -9 },
/* 0xC3 */ { 1216, 6, 9, 7, 1, -9 },
/* 0xC4 */ { 1223, 9, 9, 7, -1, -9 },
/* 0xC5 */ { 1234, 6, 9, 8, 1, -9 },
/* 0xC6 */ { 1241, 7, 9, 7, 0, -9 },
/* 0xC7 */ { 1249, 7, 9, 9, 1, -9 },
/* 0xC8 */ { 1257, 7, 9, 9, 1, -9 },
/* 0xC9 */ { 1265, 1, 9, 3, 1, -9 },
/* 0xCA */ { 1267, 7, 9, 8, 1, -9 },
/* 0xCB */ { 1275, 9, 9, 7, -1, -9 },
/* 0xCC */ { 1286, 7, 9, 9, 1, -9 },
/* 0xCD */ { 1294, 7, 9, 9, 1, -9 },
/* 0xCE */ { 1302, 6, 9, 8, 1, -9 },
/* 0xCF */ { 1309, 7, 9, 9, 1, -9 },
/* 0xD0 */ { 1317, 7, 9, 9, 1, -9 },
/* 0xD1 */ { 1325, 6, 9, 8, 1, -9 },
/* 0xD2 */ { 1332, 0, 0, 5, 0, 0 },
/* 0xD3 */ { 1332, 6, 9, 7, 1, -9 },
/* 0xD4 */ { 1339, 7, 9, 7, 0, -9 },
/* 0xD5 */ { 1347, 7, 9, 7, 0, -9 },
/* 0xD6 */ { 1355, 7, 9, 9, 1, -9 },
/* 0xD7 */ { 1363, 7, 9, 7, 0, -9 },
/* 0xD8 */ { 1371, 7, 9, 9, 1, -9 },
/* 0xD9 */ { 1379, 7, 9, 9, 1, -9 },
/* 0xDA */ { 1387, 3, 11, 3, 0, -11 },
/* 0xDB */ { 1392, 7, 11, 7, 0, -11 },
/* 0xDC */ { 1402, 5, 10, 7, 1, -10 },
/* 0xDD */ { 1409, 5, 10, 5, 0, -10 },
/* 0xDE */ { 1416, 5, 12, 7, 1, -10 },
/* 0xDF */ { 1424, 2, 10, 3, 1, -10 },
/* 0xE0 */ { 1427, 5, 10, 7, 1, -10 },
/* 0xE1 */ { 1434, 5, 7, 7, 1, -7 },
/* 0xE2 */ { 1439, 5, 11, 7, 1, -9 },
/* 0xE3 */ { 1446, 7, 9, 5, -1, -7 },
/* 0xE4 */ { 1454, 5, 9, 7, 1, -9 },
/* 0xE5 */ { 1460, 5, 7, 5, 0, -7 },
/* 0xE6 */ { 1465, 4, 11, 5, 1, -9 },
/* 0xE7 */ { 1471, 5, 9, 7, 1, -7 },
/* 0xE8 */ { 1477, 5, 9, 7, 1, -9 },
/* 0xE9 */ { 1483, 1, 7, 3, 1, -7 },
/* 0xEA */ { 1484, 6, 7, 7, 1, -7 },
/* 0xEB */ { 1490, 6, 9, 5, -1, -9 },
/* 0xEC */ { 1497, 5, 9, 7, 1, -7 },
/* 0xED */ { 1503, 5, 7, 5, 0, -7 },
/* 0xEE */ { 1508, 4, 11, 5, 1, -9 },
/* 0xEF */ { 1514, 5, 7, 7, 1, -7 },
/* 0xF0 */ { 1519, 8, 7, 8, 0, -7 },
/* 0xF1 */ { 1526, 5, 9, 7, 1, -7 },
/* 0xF2 */ { 1532, 4, 9, 6, 1, -7 },
/* 0xF3 */ { 1537, 7, 7, 7, 1, -7 },
/* 0xF4 */ { 1544, 3, 7, 5, 1, -7 },
/* 0xF5 */ { 1547, 5, 7, 7, 1, -7 },
/* 0xF6 */ { 1552, 6, 9, 8, 1, -7 },
/* 0xF7 */ { 1559, 6, 9, 6, 0, -7 },
/* 0xF8 */ { 1566, 7, 9, 9, 1, -7 },
/* 0xF9 */ { 1574, 7, 7, 9, 1, -7 },
/* 0xFA */ { 1581, 3, 9, 3, 0, -9 },
/* 0xFB */ { 1585, 5, 9, 7, 1, -9 },
/* 0xFC */ { 1591, 5, 10, 7, 1, -10 },
/* 0xFD */ { 1598, 5, 10, 7, 1, -10 },
/* 0xFE */ { 1605, 7, 10, 9, 1, -10 },
/* 0xFF */ { 1614, 0, 0, 5, 0, 0 },
};
const GFXfont FreeSans6pt_Win1253 PROGMEM = {
(uint8_t*)FreeSans6pt_Win1253Bitmaps,
(GFXglyph*)FreeSans6pt_Win1253Glyphs,
0x01, 0xFF, 10
};

View File

@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans9pt_Win1253
*/
const uint8_t FreeSans9pt_Win1253Bitmaps[] PROGMEM = {
/* 0x01 */ 0x07, 0x00, 0x0A, 0x00, 0x24, 0x00, 0x48, 0x01, 0x10, 0x04, 0x40, 0x10, 0xFF, 0x20, 0x02, 0x81, 0xFD, 0x00, 0x06, 0x07, 0xF4, 0x08, 0x24, 0x0F, 0x88, 0x11, 0x0F, 0xDC, 0x00,
/* 0x02 */ 0x3F, 0x70, 0x81, 0x11, 0x03, 0xE4, 0x08, 0x28, 0x1F, 0xD0, 0x00, 0x60, 0x7F, 0x20, 0x02, 0x43, 0xFC, 0x44, 0x00, 0x44, 0x00, 0x48, 0x00, 0x90, 0x00, 0xA0, 0x01, 0xC0, 0x00,
/* 0x03 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x31, 0x8C, 0x63, 0x18, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x20, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x04 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x82, 0x30, 0x88, 0x62, 0x08, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x05 */ 0x0B, 0x10, 0x14, 0xA8, 0x12, 0x50, 0x29, 0x42, 0x24, 0xA5, 0x32, 0x95, 0x5A, 0x09, 0x48, 0x09, 0x24, 0x01, 0x10, 0x01, 0x48, 0x02, 0xA4, 0x02, 0x42, 0x04, 0x01, 0x98, 0x00, 0x60,
/* 0x06 */ 0x00, 0x80, 0x22, 0x80, 0x65, 0x00, 0xBE, 0xE1, 0x82, 0x4E, 0x03, 0x24, 0x04, 0x28, 0x06, 0x30, 0x12, 0x20, 0x3C, 0xA0, 0xC3, 0xFE, 0x80, 0x4D, 0x00, 0xA6, 0x01, 0x80, 0x00,
/* 0x07 */
/* 0x08 */ 0x00, 0xF8, 0x00, 0x82, 0x00, 0x80, 0x83, 0xE0, 0x41, 0x10, 0x21, 0x04, 0x1B, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00, 0xE0, 0x00, 0x4F, 0xE1, 0xC0, 0x0F, 0x02, 0x00, 0x03, 0x01, 0x00, 0x09, 0x88, 0x0C, 0x0C,
/* 0x09 */ 0x00, 0xF8, 0x00, 0x82, 0x00, 0x80, 0x83, 0xE0, 0x41, 0x10, 0x21, 0x04, 0x1B, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00, 0xE0, 0x00, 0x4F, 0xE1, 0xC0, 0x0F, 0x00,
/* 0x0A */
/* 0x0B */ 0x1C, 0x1C, 0x31, 0xB1, 0x90, 0x50, 0x50, 0x10, 0x18, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x02, 0x80, 0x02, 0x40, 0x01, 0x10, 0x01, 0x04, 0x01, 0x01, 0x01, 0x00, 0x41, 0x00, 0x11, 0x00, 0x07, 0x00, 0x01, 0x00,
/* 0x0C */ 0x06, 0x00, 0x0A, 0x00, 0x12, 0x00, 0x32, 0x01, 0x84, 0x04, 0x10, 0x08, 0x98, 0x1C, 0x18, 0x40, 0x48, 0x82, 0x11, 0xF0, 0x74, 0x02, 0x18, 0x70, 0x2F, 0x9F, 0x80,
/* 0x0D */
/* 0x0E */ 0x01, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x3E, 0x00, 0x82, 0x02, 0x82, 0x06, 0x04, 0x10, 0x04, 0x20, 0x08, 0x40, 0x10, 0xFF, 0x22, 0x00, 0x29, 0xFF, 0x3F, 0x8F, 0xDF, 0x9F, 0x01, 0xC0,
/* 0x0F */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x82, 0x36, 0x03, 0x60, 0x00, 0xCC, 0x19, 0xA4, 0x4B, 0x00, 0x06, 0x8E, 0x2B, 0x22, 0x66, 0x7C, 0xCC, 0x71, 0x98, 0x03, 0x00,
/* 0x10 */ 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x54, 0x00, 0xA8, 0x01, 0x50, 0x02, 0xA0, 0x05, 0x20, 0x32, 0x61, 0xC4, 0x74, 0x49, 0x10, 0x6C, 0x00, 0xD8, 0x01, 0x10, 0x00,
/* 0x11 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x40, 0x29, 0x00, 0x31, 0x84, 0x63, 0x18, 0xC0, 0x00, 0x80, 0x15, 0x03, 0x7E, 0x02, 0xFA, 0x04, 0xE4, 0x18, 0x84, 0x00, 0x06, 0x0C, 0x03, 0xE0,
/* 0x12 */ 0x02, 0x08, 0x01, 0x08, 0x40, 0x10, 0xC0, 0x08, 0xC0, 0x60, 0x80, 0x28, 0x04, 0x12, 0x4C, 0x10, 0x80, 0x08, 0x23, 0x0E, 0x08, 0xC4, 0x82, 0x04, 0x20, 0x83, 0x09, 0x82, 0x47, 0x01, 0x1C, 0x01, 0x30, 0x00, 0xE0, 0x00, 0x00,
/* 0x13 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x31, 0x08, 0x65, 0x28, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x14 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x22, 0x29, 0x83, 0x30, 0x00, 0x65, 0x14, 0xD3, 0x4D, 0xBA, 0xEB, 0x38, 0xE6, 0x00, 0x0A, 0x00, 0x24, 0x38, 0x44, 0x01, 0x07, 0x1C, 0x01, 0xC0,
/* 0x15 */ 0x07, 0xC0, 0x30, 0x18, 0x80, 0x32, 0x00, 0xF8, 0x01, 0xF1, 0x09, 0xA5, 0x28, 0x40, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x16 */ 0x0C, 0x00, 0xC0, 0x1C, 0x03, 0x80, 0xF8, 0xBB, 0x36, 0xC7, 0x99, 0xF3, 0xFE, 0x3F, 0xC3, 0xF0, 0x7E, 0x0E, 0xC1, 0x8E, 0xE0, 0x20,
/* 0x17 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x10, 0x01, 0x20, 0x1D, 0x44, 0x42, 0x84, 0x85, 0x00, 0x86, 0x00, 0xC4, 0x00, 0x44, 0x7C, 0x44, 0x00, 0x06, 0x0C, 0x03, 0xE0,
/* 0x18 */ 0x01, 0xE0, 0x00, 0x84, 0x00, 0x40, 0x80, 0x20, 0x10, 0x08, 0x24, 0x02, 0x41, 0x00, 0x86, 0x03, 0x12, 0x03, 0xB4, 0x03, 0x52, 0x81, 0x23, 0x80, 0x70, 0xA0, 0x14, 0x28, 0x05, 0x0A, 0x01, 0x42, 0x80, 0x50,
/* 0x19 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x33, 0x18, 0x60, 0x00, 0xDC, 0xE1, 0xB9, 0xC3, 0x7B, 0xC6, 0x63, 0x0A, 0x00, 0x24, 0xF0, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x1A */ 0xFF, 0xFC, 0x00, 0x63, 0xE3, 0x31, 0x99, 0x04, 0xC8, 0x66, 0x06, 0x30, 0x61, 0x82, 0x0C, 0x10, 0x60, 0x03, 0x04, 0x18, 0x00, 0xFF, 0xFC,
/* 0x1B */ 0x07, 0xF0, 0x06, 0x0C, 0x04, 0x01, 0x04, 0x00, 0x44, 0x22, 0x12, 0x2A, 0x89, 0x00, 0x04, 0x80, 0x02, 0x44, 0x11, 0x01, 0xF0, 0x04, 0x01, 0x0D, 0x01, 0x6A, 0x41, 0x2C, 0x00, 0x05, 0xC0, 0x0E, 0x18, 0x18,
/* 0x1C */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0xC0, 0x2A, 0x00, 0x33, 0x00, 0x66, 0x00, 0xCC, 0x39, 0x80, 0x83, 0x00, 0x06, 0x00, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x1D */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x70, 0x28, 0x00, 0x31, 0x80, 0x63, 0x18, 0xC0, 0x31, 0x80, 0x03, 0x00, 0x06, 0x60, 0x0D, 0x33, 0x12, 0x10, 0x48, 0x21, 0x23, 0x8C, 0x00,
/* 0x1E */ 0x03, 0x00, 0x07, 0x9E, 0x07, 0x00, 0x86, 0x00, 0x27, 0xC0, 0x0F, 0xC0, 0x07, 0x8C, 0x62, 0x06, 0x31, 0x20, 0x00, 0x90, 0x00, 0x48, 0x00, 0x24, 0x3E, 0x11, 0x00, 0x10, 0x40, 0x10, 0x18, 0x30, 0x03, 0xE0,
/* 0x1F */ 0x18, 0x02, 0x80, 0x4C, 0x16, 0x41, 0x24, 0x3C, 0x88, 0x6E, 0x65, 0xF2, 0x78, 0x46, 0x88, 0xCF, 0x18, 0x02, 0x80, 0x8C, 0x60, 0x70,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFF, 0xFF, 0xF0, 0xC0,
/* '"' 0x22 */ 0xDE, 0xF7, 0x20,
/* '#' 0x23 */ 0x09, 0x86, 0x41, 0x91, 0xFF, 0x13, 0x04, 0xC3, 0x20, 0xC8, 0xFF, 0x89, 0x82, 0x61, 0x90,
/* '$' 0x24 */ 0x10, 0x1F, 0x14, 0xDA, 0x3D, 0x1E, 0x83, 0x40, 0x78, 0x17, 0x08, 0xF4, 0x7A, 0x35, 0x33, 0xF0, 0x40, 0x20,
/* '%' 0x25 */ 0x38, 0x10, 0xEC, 0x20, 0xC6, 0x20, 0xC6, 0x40, 0xC6, 0x40, 0x6C, 0x80, 0x39, 0x00, 0x01, 0x3C, 0x02, 0x77, 0x02, 0x63, 0x04, 0x63, 0x04, 0x77, 0x08, 0x3C,
/* '&' 0x26 */ 0x0E, 0x0C, 0xC3, 0x30, 0xCC, 0x1E, 0x03, 0x03, 0xC1, 0x9B, 0xC2, 0xF0, 0xEC, 0x19, 0x8F, 0x3C, 0x40,
/* ''' 0x27 */ 0xFE,
/* '(' 0x28 */ 0x13, 0x26, 0x6C, 0xCC, 0xCC, 0xC4, 0x66, 0x23, 0x10,
/* ')' 0x29 */ 0x8C, 0x46, 0x63, 0x33, 0x33, 0x32, 0x66, 0x4C, 0x80,
/* '*' 0x2A */ 0x25, 0x7E, 0xA5, 0x00,
/* '+' 0x2B */ 0x30, 0xC3, 0x3F, 0x30, 0xC3, 0x0C,
/* ',' 0x2C */ 0xD6,
/* '-' 0x2D */ 0xF0,
/* '.' 0x2E */ 0xC0,
/* '/' 0x2F */ 0x08, 0x44, 0x21, 0x10, 0x84, 0x42, 0x11, 0x08, 0x00,
/* '0' 0x30 */ 0x3C, 0x66, 0x42, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x42, 0x66, 0x3C,
/* '1' 0x31 */ 0x11, 0x3F, 0x33, 0x33, 0x33, 0x33, 0x30,
/* '2' 0x32 */ 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x1C, 0x1C, 0x1C, 0x18, 0x18, 0x10, 0x08, 0x07, 0xF8,
/* '3' 0x33 */ 0x3C, 0x66, 0xC3, 0xC3, 0x03, 0x06, 0x1C, 0x07, 0x03, 0xC3, 0xC3, 0x66, 0x3C,
/* '4' 0x34 */ 0x0C, 0x18, 0x71, 0x62, 0xC9, 0xA3, 0x46, 0xFE, 0x18, 0x30, 0x60, 0xC0,
/* '5' 0x35 */ 0x7F, 0x20, 0x10, 0x08, 0x08, 0x07, 0xF3, 0x8C, 0x03, 0x01, 0x80, 0xF0, 0x6C, 0x63, 0xE0,
/* '6' 0x36 */ 0x1E, 0x31, 0x98, 0x78, 0x0C, 0x06, 0xF3, 0x8D, 0x83, 0xC1, 0xE0, 0xD0, 0x6C, 0x63, 0xE0,
/* '7' 0x37 */ 0xFF, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x18, 0x18, 0x18, 0x10, 0x30, 0x30,
/* '8' 0x38 */ 0x3E, 0x31, 0xB0, 0x78, 0x3C, 0x1B, 0x18, 0xF8, 0xC6, 0xC1, 0xE0, 0xF0, 0x6C, 0x63, 0xE0,
/* '9' 0x39 */ 0x3C, 0x66, 0xC2, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0xC2, 0x66, 0x3C,
/* ':' 0x3A */ 0xC0, 0x00, 0x30,
/* ';' 0x3B */ 0xC0, 0x00, 0x00, 0x64, 0xA0,
/* '<' 0x3C */ 0x00, 0x81, 0xC7, 0x8E, 0x0C, 0x07, 0x80, 0x70, 0x0E, 0x01, 0x80,
/* '=' 0x3D */ 0xFF, 0x80, 0x00, 0x1F, 0xF0,
/* '>' 0x3E */ 0xE0, 0x1C, 0x03, 0x80, 0x30, 0x70, 0xE3, 0x81, 0x00,
/* '?' 0x3F */ 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x18, 0x38, 0x18, 0x18, 0x0C, 0x00, 0x00, 0x01, 0x80,
/* '@' 0x40 */ 0x03, 0xF0, 0x06, 0x0E, 0x06, 0x01, 0x86, 0x00, 0x66, 0x1D, 0xBB, 0x31, 0xCF, 0x18, 0xC7, 0x98, 0x63, 0xCC, 0x31, 0xE6, 0x11, 0xB3, 0x99, 0xCC, 0xF7, 0x86, 0x00, 0x01, 0x80, 0x00, 0x70, 0x40, 0x0F, 0xE0,
/* 'A' 0x41 */ 0x06, 0x00, 0xF0, 0x0F, 0x00, 0x90, 0x19, 0x81, 0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2, 0x04, 0x60, 0x66, 0x06, 0xC0, 0x30,
/* 'B' 0x42 */ 0xFF, 0x18, 0x33, 0x03, 0x60, 0x6C, 0x0D, 0x83, 0x3F, 0xC6, 0x06, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x6F, 0xF8,
/* 'C' 0x43 */ 0x1F, 0x86, 0x19, 0x81, 0xA0, 0x3C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x68, 0x0D, 0x83, 0x18, 0x61, 0xF0,
/* 'D' 0x44 */ 0xFF, 0x18, 0x33, 0x03, 0x60, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x03, 0x60, 0xCF, 0xF0,
/* 'E' 0x45 */ 0xFF, 0xE0, 0x30, 0x18, 0x0C, 0x06, 0x03, 0xFD, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0F, 0xF8,
/* 'F' 0x46 */ 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
/* 'G' 0x47 */ 0x0F, 0x83, 0x0E, 0x60, 0x66, 0x03, 0xC0, 0x0C, 0x00, 0xC1, 0xFC, 0x03, 0xC0, 0x36, 0x03, 0x60, 0x73, 0x0F, 0x0F, 0x10,
/* 'H' 0x48 */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xFF, 0xFE, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x06,
/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 'J' 0x4A */ 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x07, 0x8F, 0x1E, 0x27, 0x80,
/* 'K' 0x4B */ 0xC0, 0xF0, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0x98, 0xC3, 0x30, 0xCC, 0x1B, 0x03, 0xC0, 0xC0,
/* 'L' 0x4C */ 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF,
/* 'M' 0x4D */ 0xE0, 0x3F, 0x01, 0xFC, 0x1F, 0xE0, 0xFD, 0x05, 0xEC, 0x6F, 0x63, 0x79, 0x13, 0xCD, 0x9E, 0x6C, 0xF1, 0x47, 0x8E, 0x3C, 0x71, 0x80,
/* 'N' 0x4E */ 0xE0, 0x7C, 0x0F, 0xC1, 0xE8, 0x3D, 0x87, 0x98, 0xF1, 0x1E, 0x33, 0xC3, 0x78, 0x6F, 0x07, 0xE0, 0x7C, 0x0E,
/* 'O' 0x4F */ 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x0C, 0x60, 0xC0, 0xF8, 0x00,
/* 'P' 0x50 */ 0xFF, 0x30, 0x6C, 0x0F, 0x03, 0xC0, 0xF0, 0x6F, 0xF3, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x00,
/* 'Q' 0x51 */ 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x6C, 0x60, 0xC0, 0xFB, 0x00, 0x08,
/* 'R' 0x52 */ 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x0C, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x70,
/* 'S' 0x53 */ 0x3F, 0x18, 0x6C, 0x0F, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x0E, 0x00, 0xF0, 0x3C, 0x0D, 0x86, 0x3F, 0x00,
/* 'T' 0x54 */ 0xFF, 0x86, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0,
/* 'U' 0x55 */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xB0, 0x61, 0xF0,
/* 'V' 0x56 */ 0xC0, 0x6C, 0x0D, 0x81, 0x10, 0x63, 0x0C, 0x61, 0x04, 0x60, 0xCC, 0x19, 0x01, 0x60, 0x3C, 0x07, 0x00, 0x60,
/* 'W' 0x57 */ 0xC1, 0x81, 0x61, 0xC3, 0x61, 0xC3, 0x61, 0x43, 0x62, 0x62, 0x22, 0x66, 0x32, 0x26, 0x36, 0x26, 0x14, 0x34, 0x14, 0x34, 0x1C, 0x1C, 0x18, 0x1C, 0x08, 0x18,
/* 'X' 0x58 */ 0xC0, 0xD8, 0x66, 0x18, 0xCC, 0x1E, 0x07, 0x00, 0xC0, 0x78, 0x32, 0x0C, 0xC6, 0x1B, 0x07, 0xC0, 0xC0,
/* 'Y' 0x59 */ 0xC0, 0x36, 0x06, 0x30, 0xC3, 0x0C, 0x19, 0x81, 0xD8, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
/* 'Z' 0x5A */ 0xFF, 0xC0, 0x60, 0x30, 0x0C, 0x06, 0x03, 0x01, 0xC0, 0x60, 0x30, 0x18, 0x06, 0x03, 0x00, 0xFF, 0xC0,
/* '[' 0x5B */ 0xFB, 0x6D, 0xB6, 0xDB, 0x6D, 0xB6, 0xE0,
/* '\' 0x5C */ 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x80,
/* ']' 0x5D */ 0xED, 0xB6, 0xDB, 0x6D, 0xB6, 0xDB, 0xE0,
/* '^' 0x5E */ 0x30, 0x60, 0xA2, 0x44, 0xD8, 0xA1, 0x80,
/* '_' 0x5F */ 0xFF, 0xC0,
/* '`' 0x60 */ 0xC6, 0x30,
/* 'a' 0x61 */ 0x7E, 0x71, 0xB0, 0xC0, 0x60, 0xF3, 0xDB, 0x0D, 0x86, 0xC7, 0x3D, 0xC0,
/* 'b' 0x62 */ 0xC0, 0x60, 0x30, 0x1B, 0xCE, 0x36, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x7C, 0x6D, 0xE0,
/* 'c' 0x63 */ 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 'd' 0x64 */ 0x03, 0x03, 0x03, 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B,
/* 'e' 0x65 */ 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 'f' 0x66 */ 0x36, 0x6F, 0x66, 0x66, 0x66, 0x66, 0x60,
/* 'g' 0x67 */ 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0xC6, 0x7C,
/* 'h' 0x68 */ 0xC0, 0xC0, 0xC0, 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 'i' 0x69 */ 0xC3, 0xFF, 0xFF, 0xC0,
/* 'j' 0x6A */ 0x30, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xE0,
/* 'k' 0x6B */ 0xC0, 0xC0, 0xC0, 0xC2, 0xC4, 0xCC, 0xD8, 0xF8, 0xEC, 0xC4, 0xC6, 0xC3, 0xC3,
/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 'm' 0x6D */ 0xDE, 0xF7, 0x1C, 0xF0, 0xC7, 0x86, 0x3C, 0x31, 0xE1, 0x8F, 0x0C, 0x78, 0x63, 0xC3, 0x1E, 0x18, 0xC0,
/* 'n' 0x6E */ 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 'o' 0x6F */ 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 'p' 0x70 */ 0xDE, 0x71, 0xB0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xE3, 0x6F, 0x30, 0x18, 0x0C, 0x00,
/* 'q' 0x71 */ 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0x03,
/* 'r' 0x72 */ 0xDF, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x00,
/* 's' 0x73 */ 0x3E, 0xE3, 0xC0, 0xC0, 0xE0, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
/* 't' 0x74 */ 0x66, 0xF6, 0x66, 0x66, 0x66, 0x67,
/* 'u' 0x75 */ 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 'v' 0x76 */ 0xC1, 0xA0, 0x98, 0xCC, 0x42, 0x21, 0xB0, 0xD0, 0x28, 0x1C, 0x0C, 0x00,
/* 'w' 0x77 */ 0xC6, 0x1E, 0x38, 0x91, 0xC4, 0xCA, 0x66, 0xD3, 0x16, 0xD0, 0xA6, 0x87, 0x1C, 0x38, 0xC0, 0xC6, 0x00,
/* 'x' 0x78 */ 0x87, 0x89, 0xB1, 0xC3, 0x07, 0x1E, 0x26, 0xC5, 0x0C,
/* 'y' 0x79 */ 0xC1, 0x43, 0x63, 0x62, 0x26, 0x36, 0x34, 0x1C, 0x1C, 0x18, 0x18, 0x18, 0x10, 0x60,
/* 'z' 0x7A */ 0xFE, 0x0C, 0x30, 0xC1, 0x86, 0x18, 0x20, 0xC1, 0xFC,
/* '{' 0x7B */ 0x36, 0x66, 0x66, 0x6E, 0xCE, 0x66, 0x66, 0x66, 0x30,
/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
/* '}' 0x7D */ 0xC6, 0x66, 0x66, 0x67, 0x37, 0x66, 0x66, 0x66, 0xC0,
/* '~' 0x7E */ 0x61, 0x24, 0x38,
/* 0x7F */
/* 0x80 */ 0x07, 0xC6, 0x13, 0x00, 0xC0, 0x60, 0x3F, 0xE6, 0x03, 0xFC, 0x60, 0x0C, 0x03, 0x00, 0x61, 0x07, 0xC0,
/* 0x81 */
/* 0x82 */ 0xDC,
/* 0x83 */ 0x19, 0x8C, 0xF3, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0xE0,
/* 0x84 */ 0xDA, 0x76,
/* 0x85 */ 0xCC, 0xC0,
/* 0x86 */ 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
/* 0x87 */ 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18,
/* 0x88 */ 0x72, 0xA2,
/* 0x89 */ 0x70, 0x80, 0x22, 0x20, 0x08, 0x90, 0x02, 0x24, 0x00, 0x72, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x10, 0x00, 0x09, 0xC7, 0x84, 0x8B, 0x31, 0x22, 0x84, 0x88, 0xB3, 0x21, 0xC7, 0x80,
/* 0x8A */ 0x1B, 0x03, 0x80, 0x00, 0xFC, 0x61, 0xB0, 0x3C, 0x0F, 0x00, 0x78, 0x07, 0xC0, 0x38, 0x03, 0xC0, 0xF0, 0x36, 0x18, 0xFC,
/* 0x8B */ 0x69,
/* 0x8C */ 0x1E, 0xFE, 0x43, 0x81, 0x83, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x3F, 0xE0, 0x60, 0xC0, 0xC1, 0x81, 0x81, 0x83, 0x01, 0x8E, 0x01, 0xEF, 0xE0,
/* 0x8D */
/* 0x8E */ 0x1B, 0x03, 0x80, 0x03, 0xFF, 0x01, 0x80, 0xC0, 0x30, 0x18, 0x0C, 0x07, 0x01, 0x80, 0xC0, 0x60, 0x18, 0x0C, 0x03, 0xFF,
/* 0x8F */
/* 0x90 */
/* 0x91 */ 0x6B,
/* 0x92 */ 0xD6,
/* 0x93 */ 0x4C, 0xA5, 0xB0,
/* 0x94 */ 0xDA, 0x53, 0x20,
/* 0x95 */ 0x6F, 0xFF, 0x60,
/* 0x96 */ 0xFE,
/* 0x97 */ 0xFF, 0xFF,
/* 0x98 */ 0x4D, 0xC0,
/* 0x99 */ 0xFC, 0xE1, 0xCC, 0x38, 0x73, 0x0E, 0x1C, 0xC3, 0x8F, 0x30, 0xD2, 0xCC, 0x34, 0xB3, 0x0D, 0x6C, 0xC3, 0x53, 0x30, 0xCC, 0xCC, 0x33, 0x30,
/* 0x9A */ 0x24, 0x3C, 0x18, 0x7E, 0xE3, 0xC0, 0xC0, 0x60, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
/* 0x9B */ 0x96,
/* 0x9C */ 0x3C, 0xF8, 0xCF, 0x1B, 0x0C, 0x1E, 0x18, 0x3C, 0x3F, 0xF8, 0x60, 0x30, 0xC0, 0x61, 0x83, 0x67, 0x8C, 0x79, 0xF0,
/* 0x9D */
/* 0x9E */ 0x48, 0xF0, 0xC7, 0xF0, 0x61, 0x86, 0x0C, 0x30, 0xC1, 0x06, 0x0F, 0xE0,
/* 0x9F */ 0x19, 0x80, 0x00, 0xC0, 0x36, 0x06, 0x30, 0xC3, 0x0C, 0x19, 0x81, 0xD8, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 0xA0 */
/* 0xA1 */ 0xCF, 0xFF, 0xFF, 0xC0,
/* 0xA2 */ 0x08, 0x04, 0x0F, 0x8D, 0x6C, 0x9E, 0x43, 0x21, 0x90, 0xC8, 0x64, 0xDA, 0xC7, 0xC0, 0x80, 0x40,
/* 0xA3 */ 0x1F, 0x0C, 0x66, 0x0D, 0x83, 0x60, 0x0C, 0x0F, 0xC0, 0x60, 0x18, 0x06, 0x03, 0x01, 0xF1, 0x43, 0xC0,
/* 0xA4 */ 0xFF, 0xDF, 0x1E, 0x3E, 0xFF, 0xC0,
/* 0xA5 */ 0xC3, 0x42, 0x42, 0x24, 0x24, 0x3C, 0x18, 0x7E, 0x18, 0x7E, 0x18, 0x18, 0x18,
/* 0xA6 */ 0xFF, 0xFC, 0x0F, 0xFF, 0xC0,
/* 0xA7 */ 0x0C, 0x09, 0x0C, 0xC6, 0x63, 0x81, 0xE3, 0x19, 0x87, 0xE1, 0xB8, 0xC6, 0x41, 0xC0, 0x73, 0x19, 0x8C, 0x66, 0x1E, 0x00,
/* 0xA8 */ 0xCC,
/* 0xA9 */ 0x0F, 0xC0, 0x61, 0x87, 0x03, 0x9B, 0xC6, 0xD9, 0x8F, 0x60, 0x3D, 0x00, 0xF4, 0x03, 0xD8, 0x0D, 0xE6, 0x67, 0xF3, 0x86, 0x18, 0x0F, 0xC0,
/* 0xAA */ 0x74, 0x8D, 0xA9, 0x7C, 0x1F,
/* 0xAB */ 0x22, 0xCF, 0x26, 0x46, 0x64, 0x40,
/* 0xAC */ 0xFF, 0x80, 0xC0, 0x60, 0x30, 0x18,
/* 0xAD */
/* 0xAE */ 0x0F, 0xC0, 0x61, 0x87, 0x03, 0x9F, 0xE6, 0xD0, 0x8F, 0x42, 0x3D, 0xF0, 0xF4, 0x23, 0xD0, 0x8D, 0xC2, 0x67, 0x0B, 0x86, 0x18, 0x0F, 0xC0,
/* 0xAF */ 0xF8,
/* 0xB0 */ 0x74, 0x63, 0x17, 0x00,
/* 0xB1 */ 0x0C, 0x06, 0x03, 0x07, 0xE0, 0xC0, 0x60, 0x30, 0x18, 0x00, 0x00, 0x3F, 0xE0,
/* 0xB2 */ 0x7B, 0x30, 0xC3, 0x11, 0x84, 0x3F,
/* 0xB3 */ 0x7D, 0x8C, 0x18, 0xC0, 0x60, 0xF1, 0xBE,
/* 0xB4 */ 0x36, 0xC0,
/* 0xB5 */ 0xC3, 0x61, 0xB0, 0xD8, 0x6C, 0x36, 0x1B, 0x0D, 0x86, 0xE7, 0x7D, 0xF0, 0x18, 0x0C, 0x00,
/* 0xB6 */ 0x3F, 0x7E, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0x72, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
/* 0xB7 */ 0xE0,
/* 0xB8 */ 0x21, 0xC7, 0xE0,
/* 0xB9 */ 0x3D, 0xB6, 0xD8,
/* 0xBA */ 0x74, 0x63, 0x18, 0xB8, 0x1F,
/* 0xBB */ 0x89, 0x98, 0x99, 0x3C, 0xD1, 0x00,
/* 0xBC */ 0x20, 0x43, 0x81, 0x06, 0x08, 0x18, 0x20, 0x61, 0x01, 0x84, 0x06, 0x21, 0x80, 0x86, 0x04, 0x78, 0x32, 0x60, 0x87, 0xC4, 0x06, 0x10, 0x18,
/* 0xBD */ 0x20, 0x43, 0x81, 0x06, 0x08, 0x18, 0x20, 0x61, 0x01, 0x8D, 0xE6, 0x2C, 0xC1, 0x03, 0x0C, 0x0C, 0x20, 0x41, 0x86, 0x0C, 0x30, 0x20, 0xFC,
/* 0xBE */ 0x78, 0x11, 0x98, 0x40, 0x31, 0x00, 0x82, 0x00, 0xC8, 0x01, 0x90, 0x33, 0x43, 0x3D, 0x06, 0x02, 0x3C, 0x08, 0x98, 0x10, 0xF8, 0x40, 0x61, 0x00, 0xC0,
/* 0xBF */ 0x0C, 0x00, 0x00, 0x01, 0x80, 0xC0, 0xC0, 0xE0, 0xC0, 0xC0, 0x60, 0xF0, 0x6C, 0x63, 0xE0,
/* 0xC0 */ 0x0C, 0xDB, 0xD3, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
/* 0xC1 */ 0x0E, 0x01, 0xC0, 0x6C, 0x0D, 0x81, 0xB0, 0x63, 0x0C, 0x61, 0xFC, 0x7F, 0xCC, 0x19, 0x83, 0x60, 0x3C, 0x06,
/* 0xC2 */ 0xFF, 0x3F, 0xEC, 0x0F, 0x03, 0xC0, 0xFF, 0xEF, 0xFB, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0xFE, 0xFF, 0x00,
/* 0xC3 */ 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
/* 0xC4 */ 0x07, 0x00, 0x38, 0x01, 0xC0, 0x1B, 0x00, 0xD8, 0x0C, 0x60, 0x63, 0x03, 0x18, 0x30, 0x61, 0x83, 0x18, 0x0C, 0xFF, 0xE7, 0xFF, 0x00,
/* 0xC5 */ 0xFF, 0xFF, 0xFC, 0x03, 0x00, 0xC0, 0x3F, 0xEF, 0xFB, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0xFF, 0xFF, 0xC0,
/* 0xC6 */ 0x7F, 0xDF, 0xF0, 0x18, 0x0C, 0x07, 0x01, 0x80, 0xC0, 0x60, 0x38, 0x0C, 0x06, 0x03, 0xFF, 0xFF, 0xC0,
/* 0xC7 */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x06,
/* 0xC8 */ 0x0F, 0x03, 0xFC, 0x70, 0xE6, 0x06, 0xC0, 0x3C, 0xF3, 0xCF, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x70, 0xE3, 0xFC, 0x1F, 0x80,
/* 0xC9 */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 0xCA */ 0xC1, 0xD8, 0x73, 0x1C, 0x67, 0x0D, 0xC1, 0xF0, 0x3F, 0x07, 0x70, 0xC7, 0x18, 0x63, 0x0E, 0x60, 0xEC, 0x0E,
/* 0xCB */ 0x07, 0x00, 0x38, 0x01, 0xC0, 0x1B, 0x00, 0xD8, 0x0C, 0x60, 0x63, 0x03, 0x18, 0x30, 0x61, 0x83, 0x1C, 0x1C, 0xC0, 0x66, 0x03, 0x00,
/* 0xCC */ 0xE0, 0x3F, 0x83, 0xFC, 0x1F, 0xE0, 0xFD, 0x8D, 0xEC, 0x6F, 0x63, 0x79, 0x13, 0xCD, 0x9E, 0x6C, 0xF3, 0x67, 0x8E, 0x3C, 0x71, 0x80,
/* 0xCD */ 0xC0, 0x7C, 0x0F, 0xC1, 0xF8, 0x3D, 0x87, 0x98, 0xF3, 0x9E, 0x33, 0xC3, 0x78, 0x3F, 0x07, 0xE0, 0x7C, 0x06,
/* 0xCE */ 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xE7, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0,
/* 0xCF */ 0x0F, 0x83, 0xFC, 0x70, 0xE6, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x70, 0xE3, 0xFC, 0x0F, 0x00,
/* 0xD0 */ 0xFF, 0xFF, 0xFF, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x06,
/* 0xD1 */ 0xFF, 0x3F, 0xEC, 0x1F, 0x03, 0xC0, 0xF0, 0x7F, 0xFB, 0xFC, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x00,
/* 0xD2 */
/* 0xD3 */ 0xFF, 0xFF, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0xFF, 0xFF,
/* 0xD4 */ 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x00,
/* 0xD5 */ 0xE0, 0x76, 0x06, 0x30, 0xC3, 0x9C, 0x19, 0x80, 0xF0, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
/* 0xD6 */ 0x06, 0x00, 0x60, 0x1F, 0x87, 0xFE, 0xE6, 0x7C, 0x63, 0xC6, 0x3C, 0x63, 0xE6, 0x77, 0xFE, 0x1F, 0x80, 0x60, 0x06, 0x00,
/* 0xD7 */ 0x71, 0xC6, 0x30, 0x6C, 0x0D, 0x80, 0xE0, 0x1C, 0x03, 0x80, 0xD8, 0x1B, 0x07, 0x70, 0xC6, 0x30, 0x6E, 0x0E,
/* 0xD8 */ 0xC6, 0x3C, 0x63, 0xC6, 0x3C, 0x63, 0xC6, 0x3C, 0x63, 0x46, 0x66, 0x66, 0x3F, 0xC0, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
/* 0xD9 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0x40, 0x4C, 0x18, 0xEE, 0x7D, 0xFF, 0xBE,
/* 0xDA */ 0xCF, 0x30, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C,
/* 0xDB */ 0x19, 0x81, 0x98, 0x00, 0x0E, 0x07, 0x60, 0x63, 0x0C, 0x39, 0xC1, 0x98, 0x0F, 0x00, 0xF0, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 0xDC */ 0x06, 0x0C, 0x00, 0x3B, 0x7B, 0xEE, 0xC6, 0xC6, 0xC6, 0xC6, 0xEE, 0x7B, 0x3B,
/* 0xDD */ 0x18, 0x20, 0x03, 0xCF, 0xF8, 0xB0, 0x38, 0x71, 0x83, 0x17, 0xF7, 0x80,
/* 0xDE */ 0x0C, 0x18, 0x00, 0xDE, 0xFF, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x03, 0x03, 0x03, 0x03,
/* 0xDF */ 0x78, 0x6D, 0xB6, 0xDB, 0x6C,
/* 0xE0 */ 0x0C, 0xDB, 0xD3, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C,
/* 0xE1 */ 0x3B, 0x7B, 0xEE, 0xC6, 0xC6, 0xC6, 0xC6, 0xEE, 0x7B, 0x3B,
/* 0xE2 */ 0x3C, 0x7E, 0xC6, 0xC6, 0xC4, 0xD8, 0xDE, 0xC7, 0xC3, 0xC3, 0xE7, 0xFE, 0xDC, 0xC0, 0xC0, 0xC0, 0xC0,
/* 0xE3 */ 0x61, 0x98, 0x66, 0x18, 0xCC, 0x33, 0x0C, 0xC1, 0xE0, 0x78, 0x1E, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00,
/* 0xE4 */ 0x7E, 0x7E, 0x30, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C,
/* 0xE5 */ 0x79, 0xFF, 0x16, 0x07, 0x0E, 0x30, 0x62, 0xFE, 0xF0,
/* 0xE6 */ 0x7E, 0xFC, 0x30, 0xC3, 0x0C, 0x18, 0x60, 0xC1, 0x83, 0x07, 0xE7, 0xE0, 0xC1, 0x83, 0x0C,
/* 0xE7 */ 0xDE, 0xFF, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x03, 0x03, 0x03, 0x03,
/* 0xE8 */ 0x3C, 0x7E, 0x66, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0x66, 0x7E, 0x3C,
/* 0xE9 */ 0xFF, 0xFF, 0xF0,
/* 0xEA */ 0xC3, 0x63, 0x33, 0x1B, 0x0F, 0x06, 0xC3, 0x31, 0x8C, 0xC6, 0x61, 0x80,
/* 0xEB */ 0x30, 0x0C, 0x06, 0x03, 0x01, 0xC1, 0xE0, 0xD0, 0x6C, 0x36, 0x33, 0x18, 0xCC, 0x66, 0x30,
/* 0xEC */ 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0xFF, 0xDB, 0xC0, 0xC0, 0xC0, 0xC0,
/* 0xED */ 0xC1, 0xE0, 0xD8, 0xCC, 0x66, 0x31, 0xB0, 0xD8, 0x38, 0x1C, 0x04, 0x00,
/* 0xEE */ 0x7D, 0xFB, 0x06, 0x07, 0xC7, 0x9C, 0x70, 0xC1, 0x83, 0x83, 0xE3, 0xE0, 0xC1, 0x8E, 0x18,
/* 0xEF */ 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C,
/* 0xF0 */ 0xFF, 0xFF, 0xFF, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C,
/* 0xF1 */ 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0xFE, 0xDC, 0xC0, 0xC0, 0xC0, 0xC0,
/* 0xF2 */ 0x1E, 0xFD, 0x86, 0x0C, 0x18, 0x30, 0x70, 0x7C, 0x7C, 0x18, 0x33, 0xE7, 0x00,
/* 0xF3 */ 0x3F, 0xDF, 0xFE, 0x63, 0x0C, 0xC3, 0x30, 0xCC, 0x33, 0x9C, 0x7E, 0x0F, 0x00,
/* 0xF4 */ 0xFF, 0xF3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 0xF5 */ 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C,
/* 0xF6 */ 0x2F, 0x1B, 0xEC, 0xDF, 0x33, 0xCC, 0xF3, 0x3C, 0xCD, 0xB6, 0x7F, 0x8F, 0x80, 0xC0, 0x30, 0x0C, 0x03, 0x00,
/* 0xF7 */ 0x63, 0x31, 0x8D, 0x86, 0xC3, 0x60, 0xE0, 0x70, 0x38, 0x1C, 0x1B, 0x0D, 0x86, 0xC6, 0x33, 0x18,
/* 0xF8 */ 0xCC, 0xF3, 0x3C, 0xCF, 0x33, 0xCC, 0xF3, 0x3C, 0xCF, 0x33, 0x6D, 0x8F, 0xC0, 0xC0, 0x30, 0x0C, 0x03, 0x00,
/* 0xF9 */ 0x30, 0xC6, 0x06, 0x66, 0x6C, 0x63, 0xC6, 0x3C, 0x63, 0xC6, 0x3E, 0xF7, 0x79, 0xE3, 0x9C,
/* 0xFA */ 0xCF, 0x30, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30,
/* 0xFB */ 0x66, 0x66, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C,
/* 0xFC */ 0x0C, 0x18, 0x00, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C,
/* 0xFD */ 0x08, 0x10, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C,
/* 0xFE */ 0x03, 0x00, 0x60, 0x00, 0x03, 0x0C, 0x60, 0x66, 0x66, 0xC6, 0x3C, 0x63, 0xC6, 0x3C, 0x63, 0xEF, 0x77, 0x9E, 0x39, 0xC0,
/* 0xFF */
};
const GFXglyph FreeSans9pt_Win1253Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 15, 15, 17, 1, -13 },
/* 0x02 */ { 29, 15, 15, 17, 1, -13 },
/* 0x03 */ { 58, 15, 16, 17, 1, -14 },
/* 0x04 */ { 88, 15, 16, 17, 1, -14 },
/* 0x05 */ { 118, 16, 15, 18, 1, -13 },
/* 0x06 */ { 148, 15, 15, 17, 1, -13 },
/* 0x07 */ { 177, 0, 0, 8, 0, 0 },
/* 0x08 */ { 177, 17, 16, 19, 1, -14 },
/* 0x09 */ { 211, 17, 12, 19, 1, -12 },
/* 0x0A */ { 237, 0, 0, 8, 0, 0 },
/* 0x0B */ { 237, 17, 16, 19, 1, -14 },
/* 0x0C */ { 271, 15, 14, 17, 1, -12 },
/* 0x0D */ { 298, 0, 0, 8, 0, 0 },
/* 0x0E */ { 298, 15, 16, 17, 1, -14 },
/* 0x0F */ { 328, 15, 15, 17, 1, -13 },
/* 0x10 */ { 357, 15, 15, 17, 1, -13 },
/* 0x11 */ { 386, 15, 16, 17, 1, -14 },
/* 0x12 */ { 416, 17, 17, 19, 1, -15 },
/* 0x13 */ { 453, 15, 16, 17, 1, -14 },
/* 0x14 */ { 483, 15, 16, 17, 1, -14 },
/* 0x15 */ { 513, 15, 16, 17, 1, -14 },
/* 0x16 */ { 543, 11, 16, 13, 1, -14 },
/* 0x17 */ { 565, 15, 16, 17, 1, -14 },
/* 0x18 */ { 595, 18, 15, 20, 1, -13 },
/* 0x19 */ { 629, 15, 16, 17, 1, -14 },
/* 0x1A */ { 659, 13, 14, 15, 1, -12 },
/* 0x1B */ { 682, 17, 16, 19, 1, -14 },
/* 0x1C */ { 716, 15, 16, 17, 1, -14 },
/* 0x1D */ { 746, 15, 15, 17, 1, -13 },
/* 0x1E */ { 775, 17, 16, 19, 1, -14 },
/* 0x1F */ { 809, 11, 16, 13, 1, -14 },
/* ' ' 0x20 */ { 831, 0, 0, 5, 0, 0 },
/* '!' 0x21 */ { 831, 2, 13, 6, 2, -12 },
/* '"' 0x22 */ { 835, 5, 4, 6, 1, -12 },
/* '#' 0x23 */ { 838, 10, 12, 10, 0, -11 },
/* '$' 0x24 */ { 853, 9, 16, 10, 1, -13 },
/* '%' 0x25 */ { 871, 16, 13, 16, 1, -12 },
/* '&' 0x26 */ { 897, 10, 13, 12, 1, -12 },
/* ''' 0x27 */ { 914, 2, 4, 4, 1, -12 },
/* '(' 0x28 */ { 915, 4, 17, 6, 1, -12 },
/* ')' 0x29 */ { 924, 4, 17, 6, 1, -12 },
/* '*' 0x2A */ { 933, 5, 5, 7, 1, -12 },
/* '+' 0x2B */ { 937, 6, 8, 11, 3, -7 },
/* ',' 0x2C */ { 943, 2, 4, 5, 2, 0 },
/* '-' 0x2D */ { 944, 4, 1, 6, 1, -4 },
/* '.' 0x2E */ { 945, 2, 1, 5, 1, 0 },
/* '/' 0x2F */ { 946, 5, 13, 5, 0, -12 },
/* '0' 0x30 */ { 955, 8, 13, 10, 1, -12 },
/* '1' 0x31 */ { 968, 4, 13, 10, 3, -12 },
/* '2' 0x32 */ { 975, 9, 13, 10, 1, -12 },
/* '3' 0x33 */ { 990, 8, 13, 10, 1, -12 },
/* '4' 0x34 */ { 1003, 7, 13, 10, 2, -12 },
/* '5' 0x35 */ { 1015, 9, 13, 10, 1, -12 },
/* '6' 0x36 */ { 1030, 9, 13, 10, 1, -12 },
/* '7' 0x37 */ { 1045, 8, 13, 10, 0, -12 },
/* '8' 0x38 */ { 1058, 9, 13, 10, 1, -12 },
/* '9' 0x39 */ { 1073, 8, 13, 10, 1, -12 },
/* ':' 0x3A */ { 1086, 2, 10, 5, 1, -9 },
/* ';' 0x3B */ { 1089, 3, 12, 5, 1, -8 },
/* '<' 0x3C */ { 1094, 9, 9, 11, 1, -8 },
/* '=' 0x3D */ { 1105, 9, 4, 11, 1, -5 },
/* '>' 0x3E */ { 1110, 9, 8, 11, 1, -7 },
/* '?' 0x3F */ { 1119, 9, 13, 10, 1, -12 },
/* '@' 0x40 */ { 1134, 17, 16, 18, 1, -12 },
/* 'A' 0x41 */ { 1168, 12, 13, 12, 0, -12 },
/* 'B' 0x42 */ { 1188, 11, 13, 12, 1, -12 },
/* 'C' 0x43 */ { 1206, 11, 13, 13, 1, -12 },
/* 'D' 0x44 */ { 1224, 11, 13, 13, 1, -12 },
/* 'E' 0x45 */ { 1242, 9, 13, 11, 1, -12 },
/* 'F' 0x46 */ { 1257, 8, 13, 11, 1, -12 },
/* 'G' 0x47 */ { 1270, 12, 13, 14, 1, -12 },
/* 'H' 0x48 */ { 1290, 11, 13, 13, 1, -12 },
/* 'I' 0x49 */ { 1308, 2, 13, 5, 2, -12 },
/* 'J' 0x4A */ { 1312, 7, 13, 10, 1, -12 },
/* 'K' 0x4B */ { 1324, 10, 13, 12, 1, -12 },
/* 'L' 0x4C */ { 1341, 8, 13, 10, 1, -12 },
/* 'M' 0x4D */ { 1354, 13, 13, 15, 1, -12 },
/* 'N' 0x4E */ { 1376, 11, 13, 13, 1, -12 },
/* 'O' 0x4F */ { 1394, 13, 13, 14, 1, -12 },
/* 'P' 0x50 */ { 1416, 10, 13, 12, 1, -12 },
/* 'Q' 0x51 */ { 1433, 13, 14, 14, 1, -12 },
/* 'R' 0x52 */ { 1456, 12, 13, 13, 1, -12 },
/* 'S' 0x53 */ { 1476, 10, 13, 12, 1, -12 },
/* 'T' 0x54 */ { 1493, 9, 13, 11, 1, -12 },
/* 'U' 0x55 */ { 1508, 11, 13, 13, 1, -12 },
/* 'V' 0x56 */ { 1526, 11, 13, 11, 0, -12 },
/* 'W' 0x57 */ { 1544, 16, 13, 17, 0, -12 },
/* 'X' 0x58 */ { 1570, 10, 13, 12, 1, -12 },
/* 'Y' 0x59 */ { 1587, 12, 13, 12, 0, -12 },
/* 'Z' 0x5A */ { 1607, 10, 13, 11, 1, -12 },
/* '[' 0x5B */ { 1624, 3, 17, 5, 1, -12 },
/* '\' 0x5C */ { 1631, 5, 13, 5, 0, -12 },
/* ']' 0x5D */ { 1640, 3, 17, 5, 0, -12 },
/* '^' 0x5E */ { 1647, 7, 7, 8, 1, -12 },
/* '_' 0x5F */ { 1654, 10, 1, 10, 0, 3 },
/* '`' 0x60 */ { 1656, 4, 3, 5, 0, -12 },
/* 'a' 0x61 */ { 1658, 9, 10, 10, 1, -9 },
/* 'b' 0x62 */ { 1670, 9, 13, 10, 1, -12 },
/* 'c' 0x63 */ { 1685, 8, 10, 9, 1, -9 },
/* 'd' 0x64 */ { 1695, 8, 13, 10, 1, -12 },
/* 'e' 0x65 */ { 1708, 8, 10, 10, 1, -9 },
/* 'f' 0x66 */ { 1718, 4, 13, 5, 1, -12 },
/* 'g' 0x67 */ { 1725, 8, 14, 10, 1, -9 },
/* 'h' 0x68 */ { 1739, 8, 13, 10, 1, -12 },
/* 'i' 0x69 */ { 1752, 2, 13, 4, 1, -12 },
/* 'j' 0x6A */ { 1756, 4, 17, 4, 0, -12 },
/* 'k' 0x6B */ { 1765, 8, 13, 9, 1, -12 },
/* 'l' 0x6C */ { 1778, 2, 13, 4, 1, -12 },
/* 'm' 0x6D */ { 1782, 13, 10, 15, 1, -9 },
/* 'n' 0x6E */ { 1799, 8, 10, 10, 1, -9 },
/* 'o' 0x6F */ { 1809, 8, 10, 10, 1, -9 },
/* 'p' 0x70 */ { 1819, 9, 13, 10, 1, -9 },
/* 'q' 0x71 */ { 1834, 8, 13, 10, 1, -9 },
/* 'r' 0x72 */ { 1847, 5, 10, 6, 1, -9 },
/* 's' 0x73 */ { 1854, 8, 10, 9, 1, -9 },
/* 't' 0x74 */ { 1864, 4, 12, 5, 1, -11 },
/* 'u' 0x75 */ { 1870, 8, 10, 10, 1, -9 },
/* 'v' 0x76 */ { 1880, 9, 10, 9, 0, -9 },
/* 'w' 0x77 */ { 1892, 13, 10, 13, 0, -9 },
/* 'x' 0x78 */ { 1909, 7, 10, 9, 1, -9 },
/* 'y' 0x79 */ { 1918, 8, 14, 9, 0, -9 },
/* 'z' 0x7A */ { 1932, 7, 10, 9, 1, -9 },
/* '{' 0x7B */ { 1941, 4, 17, 6, 1, -12 },
/* '|' 0x7C */ { 1950, 2, 17, 4, 2, -12 },
/* '}' 0x7D */ { 1955, 4, 17, 6, 1, -12 },
/* '~' 0x7E */ { 1964, 7, 3, 9, 1, -7 },
/* 0x7F */ { 1967, 0, 0, 0, 0, 0 },
/* 0x80 */ { 1967, 10, 13, 12, 1, -12 },
/* 0x81 */ { 1984, 0, 0, 8, 0, 0 },
/* 0x82 */ { 1984, 2, 3, 5, 1, 0 },
/* 0x83 */ { 1985, 5, 17, 5, 0, -12 },
/* 0x84 */ { 1996, 5, 3, 7, 1, 0 },
/* 0x85 */ { 1998, 10, 1, 12, 1, 0 },
/* 0x86 */ { 2000, 8, 16, 10, 1, -12 },
/* 0x87 */ { 2016, 8, 16, 10, 1, -12 },
/* 0x88 */ { 2032, 5, 3, 6, 0, -12 },
/* 0x89 */ { 2034, 18, 13, 18, 0, -12 },
/* 0x8A */ { 2064, 10, 16, 12, 1, -15 },
/* 0x8B */ { 2084, 2, 4, 4, 1, -6 },
/* 0x8C */ { 2085, 15, 13, 18, 1, -12 },
/* 0x8D */ { 2110, 0, 0, 8, 0, 0 },
/* 0x8E */ { 2110, 10, 16, 11, 1, -15 },
/* 0x8F */ { 2130, 0, 0, 8, 0, 0 },
/* 0x90 */ { 2130, 0, 0, 8, 0, 0 },
/* 0x91 */ { 2130, 2, 4, 4, 2, -12 },
/* 0x92 */ { 2131, 2, 4, 4, 1, -12 },
/* 0x93 */ { 2132, 5, 4, 7, 2, -12 },
/* 0x94 */ { 2135, 5, 4, 7, 1, -12 },
/* 0x95 */ { 2138, 4, 5, 7, 1, -8 },
/* 0x96 */ { 2141, 7, 1, 9, 1, -4 },
/* 0x97 */ { 2142, 16, 1, 18, 1, -4 },
/* 0x98 */ { 2144, 5, 2, 6, 0, -12 },
/* 0x99 */ { 2146, 18, 10, 18, 1, -13 },
/* 0x9A */ { 2169, 8, 13, 9, 1, -12 },
/* 0x9B */ { 2182, 2, 4, 5, 2, -6 },
/* 0x9C */ { 2183, 15, 10, 17, 1, -9 },
/* 0x9D */ { 2202, 0, 0, 8, 0, 0 },
/* 0x9E */ { 2202, 7, 13, 9, 1, -12 },
/* 0x9F */ { 2214, 12, 14, 12, 0, -13 },
/* 0xA0 */ { 2235, 0, 0, 5, 0, 0 },
/* 0xA1 */ { 2235, 2, 13, 6, 2, -8 },
/* 0xA2 */ { 2239, 9, 14, 10, 1, -11 },
/* 0xA3 */ { 2255, 10, 13, 10, 0, -12 },
/* 0xA4 */ { 2272, 7, 6, 10, 2, -8 },
/* 0xA5 */ { 2278, 8, 13, 10, 1, -12 },
/* 0xA6 */ { 2291, 2, 17, 5, 2, -12 },
/* 0xA7 */ { 2296, 9, 17, 10, 1, -12 },
/* 0xA8 */ { 2316, 6, 1, 6, 0, -11 },
/* 0xA9 */ { 2317, 14, 13, 14, 1, -12 },
/* 0xAA */ { 2340, 5, 8, 7, 1, -12 },
/* 0xAB */ { 2345, 7, 6, 9, 1, -7 },
/* 0xAC */ { 2351, 9, 5, 11, 2, -5 },
/* 0xAD */ { 2357, 0, 0, 0, 0, 0 },
/* 0xAE */ { 2357, 14, 13, 14, 1, -12 },
/* 0xAF */ { 2380, 5, 1, 6, 0, -12 },
/* 0xB0 */ { 2381, 5, 5, 11, 3, -11 },
/* 0xB1 */ { 2385, 9, 11, 11, 1, -10 },
/* 0xB2 */ { 2398, 6, 8, 6, 1, -13 },
/* 0xB3 */ { 2404, 7, 8, 6, 0, -13 },
/* 0xB4 */ { 2411, 4, 3, 6, 2, -12 },
/* 0xB5 */ { 2413, 9, 13, 10, 1, -9 },
/* 0xB6 */ { 2428, 8, 16, 10, 2, -12 },
/* 0xB7 */ { 2444, 3, 1, 5, 1, -4 },
/* 0xB8 */ { 2445, 5, 4, 6, 1, 1 },
/* 0xB9 */ { 2448, 3, 7, 6, 2, -13 },
/* 0xBA */ { 2451, 5, 8, 7, 1, -12 },
/* 0xBB */ { 2456, 7, 6, 9, 1, -7 },
/* 0xBC */ { 2462, 14, 13, 16, 2, -12 },
/* 0xBD */ { 2485, 14, 13, 16, 2, -12 },
/* 0xBE */ { 2508, 15, 13, 16, 1, -12 },
/* 0xBF */ { 2533, 9, 13, 10, 1, -8 },
/* 0xC0 */ { 2548, 8, 15, 4, -2, -15 },
/* 0xC1 */ { 2563, 11, 13, 11, 0, -13 },
/* 0xC2 */ { 2581, 10, 13, 12, 1, -13 },
/* 0xC3 */ { 2598, 8, 13, 10, 2, -13 },
/* 0xC4 */ { 2611, 13, 13, 12, -1, -13 },
/* 0xC5 */ { 2633, 10, 13, 12, 1, -13 },
/* 0xC6 */ { 2650, 10, 13, 11, 0, -13 },
/* 0xC7 */ { 2667, 11, 13, 13, 1, -13 },
/* 0xC8 */ { 2685, 12, 13, 14, 1, -13 },
/* 0xC9 */ { 2705, 2, 13, 4, 1, -13 },
/* 0xCA */ { 2709, 11, 13, 12, 1, -13 },
/* 0xCB */ { 2727, 13, 13, 12, -1, -13 },
/* 0xCC */ { 2749, 13, 13, 15, 1, -13 },
/* 0xCD */ { 2771, 11, 13, 13, 1, -13 },
/* 0xCE */ { 2789, 10, 13, 12, 1, -13 },
/* 0xCF */ { 2806, 12, 13, 14, 1, -13 },
/* 0xD0 */ { 2826, 11, 13, 13, 1, -13 },
/* 0xD1 */ { 2844, 10, 13, 12, 1, -13 },
/* 0xD2 */ { 2861, 0, 0, 5, 0, 0 },
/* 0xD3 */ { 2861, 8, 13, 11, 2, -13 },
/* 0xD4 */ { 2874, 10, 13, 12, 1, -13 },
/* 0xD5 */ { 2891, 12, 13, 12, 0, -13 },
/* 0xD6 */ { 2911, 12, 13, 14, 1, -13 },
/* 0xD7 */ { 2931, 11, 13, 11, 0, -13 },
/* 0xD8 */ { 2949, 12, 13, 14, 1, -13 },
/* 0xD9 */ { 2969, 11, 13, 13, 1, -13 },
/* 0xDA */ { 2987, 6, 16, 4, -1, -16 },
/* 0xDB */ { 2999, 12, 16, 12, 0, -16 },
/* 0xDC */ { 3023, 8, 13, 10, 1, -13 },
/* 0xDD */ { 3036, 7, 13, 8, 1, -13 },
/* 0xDE */ { 3048, 8, 17, 10, 1, -13 },
/* 0xDF */ { 3065, 3, 13, 4, 1, -13 },
/* 0xE0 */ { 3070, 8, 14, 10, 1, -14 },
/* 0xE1 */ { 3084, 8, 10, 10, 1, -10 },
/* 0xE2 */ { 3094, 8, 17, 10, 1, -13 },
/* 0xE3 */ { 3111, 10, 14, 8, -1, -10 },
/* 0xE4 */ { 3129, 8, 13, 10, 1, -13 },
/* 0xE5 */ { 3142, 7, 10, 8, 1, -10 },
/* 0xE6 */ { 3151, 7, 17, 8, 1, -13 },
/* 0xE7 */ { 3166, 8, 14, 10, 1, -10 },
/* 0xE8 */ { 3180, 8, 13, 10, 1, -13 },
/* 0xE9 */ { 3193, 2, 10, 4, 1, -10 },
/* 0xEA */ { 3196, 9, 10, 9, 1, -10 },
/* 0xEB */ { 3208, 9, 13, 9, 0, -13 },
/* 0xEC */ { 3223, 8, 14, 10, 1, -10 },
/* 0xED */ { 3237, 9, 10, 9, 0, -10 },
/* 0xEE */ { 3249, 7, 17, 8, 1, -13 },
/* 0xEF */ { 3264, 8, 10, 10, 1, -10 },
/* 0xF0 */ { 3274, 12, 10, 12, 0, -10 },
/* 0xF1 */ { 3289, 8, 14, 10, 1, -10 },
/* 0xF2 */ { 3303, 7, 14, 9, 1, -10 },
/* 0xF3 */ { 3316, 10, 10, 11, 1, -10 },
/* 0xF4 */ { 3329, 6, 10, 8, 1, -10 },
/* 0xF5 */ { 3337, 8, 10, 10, 1, -10 },
/* 0xF6 */ { 3347, 10, 14, 12, 1, -10 },
/* 0xF7 */ { 3365, 9, 14, 9, 0, -10 },
/* 0xF8 */ { 3381, 10, 14, 12, 1, -10 },
/* 0xF9 */ { 3399, 12, 10, 14, 1, -10 },
/* 0xFA */ { 3414, 6, 13, 4, -1, -13 },
/* 0xFB */ { 3424, 8, 13, 10, 1, -13 },
/* 0xFC */ { 3437, 8, 13, 10, 1, -13 },
/* 0xFD */ { 3450, 8, 13, 10, 1, -13 },
/* 0xFE */ { 3463, 12, 13, 14, 1, -13 },
/* 0xFF */ { 3483, 0, 0, 5, 0, 0 },
};
const GFXfont FreeSans9pt_Win1253 PROGMEM = {
(uint8_t*)FreeSans9pt_Win1253Bitmaps,
(GFXglyph*)FreeSans9pt_Win1253Glyphs,
0x01, 0xFF, 16
};

View File

@@ -616,6 +616,101 @@ char InkHUD::AppletFont::applyEncoding(std::string utf8)
}
}
else if (encoding == WINDOWS_1253) {
// Greek
// 1-Byte chars: no remapping
if (utf8.length() == 1)
return utf8.at(0);
// Multi-byte chars:
switch (toUtf32(utf8)) {
// Windows-1253 special characters (0x80-0xBF range)
REMAP(0x20AC, 0x80) // EURO SIGN
REMAP(0x2018, 0x91) // LEFT SINGLE QUOTATION MARK
REMAP(0x2019, 0x92) // RIGHT SINGLE QUOTATION MARK
REMAP(0x201C, 0x93) // LEFT DOUBLE QUOTATION MARK
REMAP(0x201D, 0x94) // RIGHT DOUBLE QUOTATION MARK
REMAP(0x2022, 0x95) // BULLET
REMAP(0x2013, 0x96) // EN DASH
REMAP(0x2014, 0x97) // EM DASH
// Greek accented capitals
REMAP(0x0386, 0xA2) // GREEK CAPITAL LETTER ALPHA WITH TONOS
REMAP(0x0388, 0xB8) // GREEK CAPITAL LETTER EPSILON WITH TONOS
REMAP(0x0389, 0xB9) // GREEK CAPITAL LETTER ETA WITH TONOS
REMAP(0x038A, 0xBA) // GREEK CAPITAL LETTER IOTA WITH TONOS
REMAP(0x038C, 0xBC) // GREEK CAPITAL LETTER OMICRON WITH TONOS
REMAP(0x038E, 0xBE) // GREEK CAPITAL LETTER UPSILON WITH TONOS
REMAP(0x038F, 0xBF) // GREEK CAPITAL LETTER OMEGA WITH TONOS
// Greek capital letters (U+0391-U+03A9 -> 0xC1-0xD1, with gaps)
REMAP(0x0391, 0xC1) // GREEK CAPITAL LETTER ALPHA
REMAP(0x0392, 0xC2) // GREEK CAPITAL LETTER BETA
REMAP(0x0393, 0xC3) // GREEK CAPITAL LETTER GAMMA
REMAP(0x0394, 0xC4) // GREEK CAPITAL LETTER DELTA
REMAP(0x0395, 0xC5) // GREEK CAPITAL LETTER EPSILON
REMAP(0x0396, 0xC6) // GREEK CAPITAL LETTER ZETA
REMAP(0x0397, 0xC7) // GREEK CAPITAL LETTER ETA
REMAP(0x0398, 0xC8) // GREEK CAPITAL LETTER THETA
REMAP(0x0399, 0xC9) // GREEK CAPITAL LETTER IOTA
REMAP(0x039A, 0xCA) // GREEK CAPITAL LETTER KAPPA
REMAP(0x039B, 0xCB) // GREEK CAPITAL LETTER LAMDA
REMAP(0x039C, 0xCC) // GREEK CAPITAL LETTER MU
REMAP(0x039D, 0xCD) // GREEK CAPITAL LETTER NU
REMAP(0x039E, 0xCE) // GREEK CAPITAL LETTER XI
REMAP(0x039F, 0xCF) // GREEK CAPITAL LETTER OMICRON
REMAP(0x03A0, 0xD0) // GREEK CAPITAL LETTER PI
REMAP(0x03A1, 0xD1) // GREEK CAPITAL LETTER RHO
REMAP(0x03A3, 0xD3) // GREEK CAPITAL LETTER SIGMA
REMAP(0x03A4, 0xD4) // GREEK CAPITAL LETTER TAU
REMAP(0x03A5, 0xD5) // GREEK CAPITAL LETTER UPSILON
REMAP(0x03A6, 0xD6) // GREEK CAPITAL LETTER PHI
REMAP(0x03A7, 0xD7) // GREEK CAPITAL LETTER CHI
REMAP(0x03A8, 0xD8) // GREEK CAPITAL LETTER PSI
REMAP(0x03A9, 0xD9) // GREEK CAPITAL LETTER OMEGA
// Greek small letters with tonos (accented)
REMAP(0x03AC, 0xDC) // GREEK SMALL LETTER ALPHA WITH TONOS
REMAP(0x03AD, 0xDD) // GREEK SMALL LETTER EPSILON WITH TONOS
REMAP(0x03AE, 0xDE) // GREEK SMALL LETTER ETA WITH TONOS
REMAP(0x03AF, 0xDF) // GREEK SMALL LETTER IOTA WITH TONOS
// Greek small letters (U+03B1-U+03C9 -> 0xE1-0xF9)
REMAP(0x03B1, 0xE1) // GREEK SMALL LETTER ALPHA
REMAP(0x03B2, 0xE2) // GREEK SMALL LETTER BETA
REMAP(0x03B3, 0xE3) // GREEK SMALL LETTER GAMMA
REMAP(0x03B4, 0xE4) // GREEK SMALL LETTER DELTA
REMAP(0x03B5, 0xE5) // GREEK SMALL LETTER EPSILON
REMAP(0x03B6, 0xE6) // GREEK SMALL LETTER ZETA
REMAP(0x03B7, 0xE7) // GREEK SMALL LETTER ETA
REMAP(0x03B8, 0xE8) // GREEK SMALL LETTER THETA
REMAP(0x03B9, 0xE9) // GREEK SMALL LETTER IOTA
REMAP(0x03BA, 0xEA) // GREEK SMALL LETTER KAPPA
REMAP(0x03BB, 0xEB) // GREEK SMALL LETTER LAMDA
REMAP(0x03BC, 0xEC) // GREEK SMALL LETTER MU
REMAP(0x03BD, 0xED) // GREEK SMALL LETTER NU
REMAP(0x03BE, 0xEE) // GREEK SMALL LETTER XI
REMAP(0x03BF, 0xEF) // GREEK SMALL LETTER OMICRON
REMAP(0x03C0, 0xF0) // GREEK SMALL LETTER PI
REMAP(0x03C1, 0xF1) // GREEK SMALL LETTER RHO
REMAP(0x03C2, 0xF2) // GREEK SMALL LETTER FINAL SIGMA
REMAP(0x03C3, 0xF3) // GREEK SMALL LETTER SIGMA
REMAP(0x03C4, 0xF4) // GREEK SMALL LETTER TAU
REMAP(0x03C5, 0xF5) // GREEK SMALL LETTER UPSILON
REMAP(0x03C6, 0xF6) // GREEK SMALL LETTER PHI
REMAP(0x03C7, 0xF7) // GREEK SMALL LETTER CHI
REMAP(0x03C8, 0xF8) // GREEK SMALL LETTER PSI
REMAP(0x03C9, 0xF9) // GREEK SMALL LETTER OMEGA
// More accented small letters
REMAP(0x03CA, 0xFA) // GREEK SMALL LETTER IOTA WITH DIALYTIKA
REMAP(0x03CB, 0xFB) // GREEK SMALL LETTER UPSILON WITH DIALYTIKA
REMAP(0x03CC, 0xFC) // GREEK SMALL LETTER OMICRON WITH TONOS
REMAP(0x03CD, 0xFD) // GREEK SMALL LETTER UPSILON WITH TONOS
REMAP(0x03CE, 0xFE) // GREEK SMALL LETTER OMEGA WITH TONOS
}
}
else /*ASCII or Unhandled*/ {
if (utf8.length() == 1)
return utf8.at(0);

View File

@@ -26,6 +26,7 @@ class AppletFont
WINDOWS_1250,
WINDOWS_1251,
WINDOWS_1252,
WINDOWS_1253,
};
AppletFont();
@@ -84,4 +85,12 @@ class AppletFont
#define FREESANS_9PT_WIN1252 InkHUD::AppletFont(FreeSans9pt_Win1252, InkHUD::AppletFont::WINDOWS_1252, -2, -1)
#define FREESANS_6PT_WIN1252 InkHUD::AppletFont(FreeSans6pt_Win1252, InkHUD::AppletFont::WINDOWS_1252, -1, -2)
// Greek
#include "graphics/niche/Fonts/FreeSans12pt_Win1253.h"
#include "graphics/niche/Fonts/FreeSans6pt_Win1253.h"
#include "graphics/niche/Fonts/FreeSans9pt_Win1253.h"
#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)
#endif

View File

@@ -155,6 +155,18 @@ void InkHUD::LogoApplet::onShutdown()
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
}
void InkHUD::LogoApplet::onApplyingChanges()
{
bringToForeground();
textLeft = "";
textRight = "";
textTitle = "Applying changes";
fontTitle = fontSmall;
inkhud->forceUpdate(Drivers::EInk::FAST, false);
}
void InkHUD::LogoApplet::onReboot()
{
bringToForeground();

View File

@@ -26,6 +26,7 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
void onBackground() override;
void onShutdown() override;
void onReboot() override;
void onApplyingChanges();
protected:
int32_t runOnce() override;

View File

@@ -22,6 +22,7 @@ enum MenuAction {
STORE_CANNEDMESSAGE_SELECTION,
SEND_CANNEDMESSAGE,
SHUTDOWN,
BACK,
NEXT_TILE,
TOGGLE_BACKLIGHT,
TOGGLE_GPS,
@@ -36,6 +37,84 @@ enum MenuAction {
TOGGLE_NOTIFICATIONS,
TOGGLE_INVERT_COLOR,
TOGGLE_12H_CLOCK,
// Regions
SET_REGION_US,
SET_REGION_EU_868,
SET_REGION_EU_433,
SET_REGION_CN,
SET_REGION_JP,
SET_REGION_ANZ,
SET_REGION_KR,
SET_REGION_TW,
SET_REGION_RU,
SET_REGION_IN,
SET_REGION_NZ_865,
SET_REGION_TH,
SET_REGION_LORA_24,
SET_REGION_UA_433,
SET_REGION_UA_868,
SET_REGION_MY_433,
SET_REGION_MY_919,
SET_REGION_SG_923,
SET_REGION_PH_433,
SET_REGION_PH_868,
SET_REGION_PH_915,
SET_REGION_ANZ_433,
SET_REGION_KZ_433,
SET_REGION_KZ_863,
SET_REGION_NP_865,
SET_REGION_BR_902,
// Device Roles
SET_ROLE_CLIENT,
SET_ROLE_CLIENT_MUTE,
SET_ROLE_ROUTER,
SET_ROLE_REPEATER,
// Presets
SET_PRESET_LONG_SLOW,
SET_PRESET_LONG_MODERATE,
SET_PRESET_LONG_FAST,
SET_PRESET_MEDIUM_SLOW,
SET_PRESET_MEDIUM_FAST,
SET_PRESET_SHORT_SLOW,
SET_PRESET_SHORT_FAST,
SET_PRESET_SHORT_TURBO,
// Timezones
SET_TZ_US_HAWAII,
SET_TZ_US_ALASKA,
SET_TZ_US_PACIFIC,
SET_TZ_US_ARIZONA,
SET_TZ_US_MOUNTAIN,
SET_TZ_US_CENTRAL,
SET_TZ_US_EASTERN,
SET_TZ_BR_BRAZILIA,
SET_TZ_UTC,
SET_TZ_EU_WESTERN,
SET_TZ_EU_CENTRAL,
SET_TZ_EU_EASTERN,
SET_TZ_ASIA_KOLKATA,
SET_TZ_ASIA_HONG_KONG,
SET_TZ_AU_AWST,
SET_TZ_AU_ACST,
SET_TZ_AU_AEST,
SET_TZ_PACIFIC_NZ,
// Power
TOGGLE_POWER_SAVE,
CALIBRATE_ADC,
// Bluetooth
TOGGLE_BLUETOOTH,
TOGGLE_BLUETOOTH_PAIR_MODE,
// Channel
TOGGLE_CHANNEL_UPLINK,
TOGGLE_CHANNEL_DOWNLINK,
TOGGLE_CHANNEL_POSITION,
SET_CHANNEL_PRECISION,
// Display
TOGGLE_DISPLAY_UNITS,
// Network
TOGGLE_WIFI,
// Administration
RESET_NODEDB_ALL,
RESET_NODEDB_KEEP_FAVORITES,
};
} // namespace NicheGraphics::InkHUD

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void onRender() override;
void show(Tile *t); // Open the menu, onto a user tile
void setStartPage(MenuPage page);
protected:
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
@@ -56,6 +57,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
MenuPage startPageOverride = MenuPage::ROOT;
MenuPage currentPage = MenuPage::ROOT;
MenuPage previousPage = MenuPage::EXIT;
uint8_t cursor = 0; // Which menu item is currently highlighted
@@ -63,7 +65,15 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
std::vector<std::string> nodeConfigLabels; // Persistent labels for Node Config pages
uint8_t selectedChannelIndex = 0; // Currently selected LoRa channel (Node Config → Radio → Channel)
bool channelPositionEnabled = false;
bool gpsEnabled = false;
// Recents menu checkbox state (derived from settings.recentlyActiveSeconds)
static constexpr uint8_t RECENTS_COUNT = 6;
bool recentsSelected[RECENTS_COUNT] = {};
// Data for selecting and sending canned messages via the menu
// Placed into a sub-class for organization only

View File

@@ -30,6 +30,7 @@ class MenuItem
MenuAction action = NO_ACTION;
MenuPage nextPage = EXIT;
bool *checkState = nullptr;
bool isHeader = false; // Non-selectable section label
// Various constructors, depending on the intended function of the item
@@ -40,6 +41,12 @@ class MenuItem
: label(label), action(action), nextPage(nextPage), checkState(checkState)
{
}
static MenuItem Header(const char *label)
{
MenuItem item(label, NO_ACTION, EXIT);
item.isHeader = true;
return item;
}
};
} // namespace NicheGraphics::InkHUD

View File

@@ -20,10 +20,27 @@ enum MenuPage : uint8_t {
SEND,
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
OPTIONS,
NODE_CONFIG,
NODE_CONFIG_LORA,
NODE_CONFIG_CHANNELS, // List of channels
NODE_CONFIG_CHANNEL_DETAIL, // Per-channel options
NODE_CONFIG_CHANNEL_PRECISION,
NODE_CONFIG_PRESET,
NODE_CONFIG_DEVICE,
NODE_CONFIG_DEVICE_ROLE,
NODE_CONFIG_POWER,
NODE_CONFIG_POWER_ADC_CAL,
NODE_CONFIG_NETWORK,
NODE_CONFIG_DISPLAY,
NODE_CONFIG_BLUETOOTH,
NODE_CONFIG_POSITION,
NODE_CONFIG_ADMIN_RESET,
TIMEZONE,
APPLETS,
AUTOSHOW,
RECENTS, // Select length of "recentlyActiveSeconds"
EXIT, // Dismiss the menu applet
REGION,
EXIT, // Dismiss the menu applet
};
} // namespace NicheGraphics::InkHUD

View File

@@ -10,34 +10,37 @@ using namespace NicheGraphics;
InkHUD::TipsApplet::TipsApplet()
{
// Decide which tips (if any) should be shown to user after the boot screen
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
bool showTutorialTips = (settings->tips.firstBoot || needsRegion);
// Welcome screen
if (settings->tips.firstBoot)
if (showTutorialTips)
tipQueue.push_back(Tip::WELCOME);
// Antenna, region, timezone
// Shown at boot if region not yet set
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
// Finish setup
if (needsRegion)
tipQueue.push_back(Tip::FINISH_SETUP);
// Using the UI
if (showTutorialTips) {
tipQueue.push_back(Tip::CUSTOMIZATION);
tipQueue.push_back(Tip::BUTTONS);
}
// Shutdown info
// Shown until user performs one valid shutdown
if (!settings->tips.safeShutdownSeen)
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
// Using the UI
if (settings->tips.firstBoot) {
tipQueue.push_back(Tip::CUSTOMIZATION);
tipQueue.push_back(Tip::BUTTONS);
}
// Catch an incorrect attempt at rotating display
if (config.display.flip_screen)
tipQueue.push_back(Tip::ROTATION);
// Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
// LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
// Region picker
if (needsRegion)
tipQueue.push_back(Tip::PICK_REGION);
if (!tipQueue.empty())
bringToForeground();
}
@@ -51,81 +54,109 @@ void InkHUD::TipsApplet::onRender()
case Tip::FINISH_SETUP: {
setFont(fontMedium);
printAt(0, 0, "Tip: Finish Setup");
const char *title = "Tip: Finish Setup";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall);
int16_t cursorY = fontMedium.lineHeight() * 1.5;
printAt(0, cursorY, "- connect antenna");
int16_t cursorY = h + fontSmall.lineHeight();
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- connect a client app");
auto drawBullet = [&](const char *text) {
uint16_t bh = getWrappedTextHeight(0, width(), text);
printWrapped(0, cursorY, width(), text);
cursorY += bh + (fontSmall.lineHeight() / 3);
};
// Only if region not set
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- set region");
}
drawBullet("- connect antenna");
drawBullet("- connect a client app");
// Only if tz not set
if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) {
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- set timezone");
}
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
drawBullet("- set region");
cursorY += fontSmall.lineHeight() * 1.5;
printAt(0, cursorY, "More info at meshtastic.org");
if (!(*config.device.tzdef && config.device.tzdef[0] != 0))
drawBullet("- set timezone");
cursorY += fontSmall.lineHeight() / 2;
drawBullet("More info at meshtastic.org");
setFont(fontSmall);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::PICK_REGION: {
setFont(fontMedium);
printAt(0, 0, "Set Region");
setFont(fontSmall);
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Please select your LoRa region to complete setup.");
printAt(0, Y(1.0), "Press button to choose", LEFT, BOTTOM);
} break;
case Tip::SAFE_SHUTDOWN: {
setFont(fontMedium);
printAt(0, 0, "Tip: Shutdown");
const char *title = "Tip: Shutdown";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall);
std::string shutdown;
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
shutdown += "\n";
shutdown += "This ensures data is saved.";
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown);
int16_t cursorY = h + fontSmall.lineHeight();
const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n"
"This ensures data is saved.";
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
printWrapped(0, cursorY, width(), body);
cursorY += bodyH + (fontSmall.lineHeight() / 2);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::CUSTOMIZATION: {
setFont(fontMedium);
printAt(0, 0, "Tip: Customization");
const char *title = "Tip: Customization";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall);
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more.");
int16_t cursorY = h + fontSmall.lineHeight();
const char *body = "Configure & control display with the InkHUD menu. "
"Optional features, layout, rotation, and more.";
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
printWrapped(0, cursorY, width(), body);
cursorY += bodyH + (fontSmall.lineHeight() / 2);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::BUTTONS: {
setFont(fontMedium);
printAt(0, 0, "Tip: Buttons");
const char *title = "Tip: Buttons";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall);
int16_t cursorY = fontMedium.lineHeight() * 1.5;
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);
};
if (!settings->joystick.enabled) {
printAt(0, cursorY, "User Button");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- short press: next");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- long press: select / open menu");
drawBullet("User Button");
drawBullet("- short press: next");
drawBullet("- long press: select or open menu");
} else {
printAt(0, cursorY, "Joystick");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- open menu / select");
cursorY += fontSmall.lineHeight() * 1.5;
printAt(0, cursorY, "Exit Button");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- switch tile / close menu");
drawBullet("Joystick");
drawBullet("- press: open menu or select");
drawBullet("Exit Button");
drawBullet("- press: switch tile or close menu");
}
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
@@ -133,12 +164,21 @@ void InkHUD::TipsApplet::onRender()
case Tip::ROTATION: {
setFont(fontMedium);
printAt(0, 0, "Tip: Rotation");
const char *title = "Tip: Rotation";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall);
if (!settings->joystick.enabled) {
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
int16_t cursorY = h + fontSmall.lineHeight();
const char *body = "To rotate the display, use the InkHUD menu. "
"Long-press the user button > Options > Rotate.";
uint16_t bh = getWrappedTextHeight(0, width(), body);
printWrapped(0, cursorY, width(), body);
cursorY += bh + (fontSmall.lineHeight() / 2);
} else {
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
@@ -159,12 +199,15 @@ void InkHUD::TipsApplet::renderWelcome()
{
uint16_t padW = X(0.05);
// Detect portrait orientation
bool portrait = height() > width();
// Block 1 - logo & title
// ========================
// Logo size
uint16_t logoWLimit = X(0.3);
uint16_t logoHLimit = Y(0.3);
uint16_t logoWLimit = portrait ? X(0.5) : X(0.3);
uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3);
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
@@ -177,7 +220,7 @@ void InkHUD::TipsApplet::renderWelcome()
// Center the block
// Desired effect: equal margin from display edge for logo left and title right
int16_t block1Y = Y(0.3);
int16_t block1Y = portrait ? Y(0.2) : Y(0.3);
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
@@ -192,7 +235,7 @@ void InkHUD::TipsApplet::renderWelcome()
std::string subtitle = "InkHUD";
if (width() >= 200)
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE);
printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE);
// Block 3 - press to continue
// ============================
@@ -224,26 +267,37 @@ void InkHUD::TipsApplet::onBackground()
// While our SystemApplet::handleInput flag is true
void InkHUD::TipsApplet::onButtonShortPress()
{
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
// If we're prompting the user to pick a region, hand off to the menu
if (!tipQueue.empty() && tipQueue.front() == Tip::PICK_REGION) {
tipQueue.pop_front();
// Signal InkHUD to open the menu on Region page
inkhud->forceRegionMenu = true;
// Close tips and open menu
sendToBackground();
inkhud->openMenu();
return;
}
// Consume current tip
tipQueue.pop_front();
// All tips done
if (tipQueue.empty()) {
// Record that user has now seen the "tutorial" set of tips
// Don't show them on subsequent boots
if (settings->tips.firstBoot) {
if (settings->tips.firstBoot && !needsRegion) {
settings->tips.firstBoot = false;
inkhud->persistence->saveSettings();
}
// Close applet, and full refresh to clean the screen
// Need to force update, because our request would be ignored otherwise, as we are now background
// Close applet and clean the screen
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
// More tips left
else
} else {
requestUpdate();
}
}
// Functions the same as the user button in this instance

View File

@@ -23,6 +23,7 @@ class TipsApplet : public SystemApplet
enum class Tip {
WELCOME,
FINISH_SETUP,
PICK_REGION,
SAFE_SHUTDOWN,
CUSTOMIZATION,
BUTTONS,

View File

@@ -276,6 +276,15 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
return 0; // We agree: deep sleep now
}
// Display an intermediate screen while configuration changes are applied
void InkHUD::Events::applyingChanges()
{
// Bring the logo applet forward with a temporary message
for (SystemApplet *sa : inkhud->systemApplets) {
sa->onApplyingChanges();
}
}
// Callback for rebootObserver
// Same as shutdown, without drawing the logoApplet
// Makes sure we don't lose message history / InkHUD config

View File

@@ -29,12 +29,13 @@ class Events
void onButtonShort(); // User button: short press
void onButtonLong(); // User button: long press
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 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
int beforeDeepSleep(void *unused); // Prepare for shutdown
int beforeReboot(void *unused); // Prepare for reboot

View File

@@ -53,6 +53,13 @@ void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive,
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
}
void InkHUD::InkHUD::notifyApplyingChanges()
{
if (events) {
events->applyingChanges();
}
}
// Start InkHUD!
// Call this only after you have configured InkHUD
void InkHUD::InkHUD::begin()

View File

@@ -47,6 +47,7 @@ class InkHUD
void setDriver(Drivers::EInk *driver);
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
void notifyApplyingChanges();
void begin();
@@ -76,6 +77,9 @@ class InkHUD
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
void toggleBatteryIcon();
// Used by TipsApplet to force menu to start on Region selection
bool forceRegionMenu = false;
// Updating the display
// - called by various InkHUD components

View File

@@ -27,6 +27,7 @@ class SystemApplet : public Applet
bool lockRequests = false; // - prevent other applets from triggering display updates
virtual void onReboot() { onShutdown(); } // - handle reboot specially
virtual void onApplyingChanges() {}
// Other system applets may take precedence over our own system applet though
// The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank)

View File

@@ -37,6 +37,9 @@ bool ButtonThread::initButton(const ButtonConfig &config)
_activeLow = config.activeLow;
_touchQuirk = config.touchQuirk;
_intRoutine = config.intRoutine;
_pressHandler = config.onPress;
_releaseHandler = config.onRelease;
_suppressLeadUp = config.suppressLeadUpSound;
_longLongPress = config.longLongPress;
userButton = OneButton(config.pinNumber, config.activeLow, config.activePullup);
@@ -133,6 +136,8 @@ int32_t ButtonThread::runOnce()
// Detect start of button press
if (buttonCurrentlyPressed && !buttonWasPressed) {
if (_pressHandler)
_pressHandler();
buttonPressStartTime = millis();
leadUpPlayed = false;
leadUpSequenceActive = false;
@@ -140,7 +145,7 @@ int32_t ButtonThread::runOnce()
}
// Progressive lead-up sound system
if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
if (!_suppressLeadUp && buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
// Start the progressive sequence if not already active
if (!leadUpSequenceActive) {
@@ -160,6 +165,8 @@ int32_t ButtonThread::runOnce()
// Reset when button is released
if (!buttonCurrentlyPressed && buttonWasPressed) {
if (_releaseHandler)
_releaseHandler();
leadUpSequenceActive = false;
resetLeadUpSequence();
}
@@ -241,7 +248,21 @@ int32_t ButtonThread::runOnce()
this->notifyObservers(&evt);
playComboTune();
break;
#if !HAS_SCREEN
case 4:
if (moduleConfig.external_notification.enabled && externalNotificationModule) {
externalNotificationModule->setMute(!externalNotificationModule->getMute());
IF_SCREEN(if (!externalNotificationModule->getMute()) externalNotificationModule->stopNow();)
if (externalNotificationModule->getMute()) {
LOG_INFO("Temporarily Muted");
play4ClickDown(); // Disable tone
} else {
LOG_INFO("Unmuted");
play4ClickUp(); // Enable tone
}
}
break;
#endif
// No valid multipress action
default:
break;

View File

@@ -13,6 +13,9 @@ struct ButtonConfig {
bool activePullup = true;
uint32_t pullupSense = 0;
voidFuncPtr intRoutine = nullptr;
voidFuncPtr onPress = nullptr; // Optional edge callbacks
voidFuncPtr onRelease = nullptr; // Optional edge callbacks
bool suppressLeadUpSound = false;
input_broker_event singlePress = INPUT_BROKER_NONE;
input_broker_event longPress = INPUT_BROKER_NONE;
uint16_t longPressTime = 500;
@@ -94,6 +97,9 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
input_broker_event _shortLong = INPUT_BROKER_NONE;
voidFuncPtr _intRoutine = nullptr;
voidFuncPtr _pressHandler = nullptr;
voidFuncPtr _releaseHandler = nullptr;
bool _suppressLeadUp = false;
uint16_t _longPressTime = 500;
uint16_t _longLongPressTime = 3900;
int _pinNum = 0;

View File

@@ -93,6 +93,8 @@ int32_t RotaryEncoderInterruptBase::runOnce()
if (!pressDetected) {
this->action = ROTARY_ACTION_NONE;
} else if (now - pressStartTime < LONG_PRESS_DURATION) {
return (20); // keep checking for long/short until time expires
}
return INT32_MAX;

View File

@@ -1,5 +1,7 @@
#include "TrackballInterruptBase.h"
#include "Throttle.h"
#include "configuration.h"
extern bool osk_found;
TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {}
@@ -55,17 +57,27 @@ int32_t TrackballInterruptBase::runOnce()
{
InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE;
#if TB_THRESHOLD
if (lastInterruptTime && !Throttle::isWithinTimespanMs(lastInterruptTime, 1000)) {
left_counter = 0;
right_counter = 0;
up_counter = 0;
down_counter = 0;
lastInterruptTime = 0;
}
#ifdef INPUT_DEBUG
if (left_counter > 0 || right_counter > 0 || up_counter > 0 || down_counter > 0) {
LOG_DEBUG("L %u R %u U %u D %u, time %u", left_counter, right_counter, up_counter, down_counter, millis());
}
#endif
#endif
// Handle long press detection for press button
if (pressDetected && pressStartTime > 0) {
uint32_t pressDuration = millis() - pressStartTime;
bool buttonStillPressed = false;
#if defined(T_DECK)
buttonStillPressed = (this->action == TB_ACTION_PRESSED);
#else
buttonStillPressed = !digitalRead(_pinPress);
#endif
if (!buttonStillPressed) {
// Button released
@@ -134,23 +146,31 @@ int32_t TrackballInterruptBase::runOnce()
}
}
#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
if (this->action == TB_ACTION_PRESSED && !pressDetected) {
#if TB_THRESHOLD
if (this->action == TB_ACTION_PRESSED && (!pressDetected || pressStartTime == 0)) {
// Start long press detection
pressDetected = true;
pressStartTime = millis();
// Don't send event yet, wait to see if it's a long press
} else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) {
// LOG_DEBUG("Trackball event UP");
} else if (up_counter >= TB_THRESHOLD) {
#ifdef INPUT_DEBUG
LOG_DEBUG("Trackball event UP %u", millis());
#endif
e.inputEvent = this->_eventUp;
} else if (this->action == TB_ACTION_DOWN && lastEvent == TB_ACTION_DOWN) {
// LOG_DEBUG("Trackball event DOWN");
} else if (down_counter >= TB_THRESHOLD) {
#ifdef INPUT_DEBUG
LOG_DEBUG("Trackball event DOWN %u", millis());
#endif
e.inputEvent = this->_eventDown;
} else if (this->action == TB_ACTION_LEFT && lastEvent == TB_ACTION_LEFT) {
// LOG_DEBUG("Trackball event LEFT");
} else if (left_counter >= TB_THRESHOLD) {
#ifdef INPUT_DEBUG
LOG_DEBUG("Trackball event LEFT %u", millis());
#endif
e.inputEvent = this->_eventLeft;
} else if (this->action == TB_ACTION_RIGHT && lastEvent == TB_ACTION_RIGHT) {
// LOG_DEBUG("Trackball event RIGHT");
} else if (right_counter >= TB_THRESHOLD) {
#ifdef INPUT_DEBUG
LOG_DEBUG("Trackball event RIGHT %u", millis());
#endif
e.inputEvent = this->_eventRight;
}
#else
@@ -183,6 +203,12 @@ int32_t TrackballInterruptBase::runOnce()
e.source = this->_originName;
e.kbchar = 0x00;
this->notifyObservers(&e);
#if TB_THRESHOLD
left_counter = 0;
right_counter = 0;
up_counter = 0;
down_counter = 0;
#endif
}
// Only update lastEvent for non-press actions or completed press actions
@@ -198,25 +224,49 @@ int32_t TrackballInterruptBase::runOnce()
void TrackballInterruptBase::intPressHandler()
{
this->action = TB_ACTION_PRESSED;
if (!Throttle::isWithinTimespanMs(lastInterruptTime, 10))
this->action = TB_ACTION_PRESSED;
lastInterruptTime = millis();
}
void TrackballInterruptBase::intDownHandler()
{
this->action = TB_ACTION_DOWN;
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
this->action = TB_ACTION_DOWN;
lastInterruptTime = millis();
#if TB_THRESHOLD
down_counter++;
#endif
}
void TrackballInterruptBase::intUpHandler()
{
this->action = TB_ACTION_UP;
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
this->action = TB_ACTION_UP;
lastInterruptTime = millis();
#if TB_THRESHOLD
up_counter++;
#endif
}
void TrackballInterruptBase::intLeftHandler()
{
this->action = TB_ACTION_LEFT;
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
this->action = TB_ACTION_LEFT;
lastInterruptTime = millis();
#if TB_THRESHOLD
left_counter++;
#endif
}
void TrackballInterruptBase::intRightHandler()
{
this->action = TB_ACTION_RIGHT;
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
this->action = TB_ACTION_RIGHT;
lastInterruptTime = millis();
#if TB_THRESHOLD
right_counter++;
#endif
}

View File

@@ -12,6 +12,10 @@
#endif
#endif
#ifndef TB_THRESHOLD
#define TB_THRESHOLD 0
#endif
class TrackballInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
@@ -25,8 +29,6 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
void intUpHandler();
void intLeftHandler();
void intRightHandler();
uint32_t lastTime = 0;
virtual int32_t runOnce() override;
protected:
@@ -67,4 +69,12 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
const char *_originName;
TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE;
volatile uint32_t lastInterruptTime = 0;
#if TB_THRESHOLD
volatile uint8_t left_counter = 0;
volatile uint8_t right_counter = 0;
volatile uint8_t up_counter = 0;
volatile uint8_t down_counter = 0;
#endif
};

View File

@@ -24,41 +24,26 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe
void TrackballInterruptImpl1::handleIntDown()
{
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
trackballInterruptImpl1->lastTime = millis();
trackballInterruptImpl1->intDownHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}
trackballInterruptImpl1->intDownHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}
void TrackballInterruptImpl1::handleIntUp()
{
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
trackballInterruptImpl1->lastTime = millis();
trackballInterruptImpl1->intUpHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}
trackballInterruptImpl1->intUpHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}
void TrackballInterruptImpl1::handleIntLeft()
{
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
trackballInterruptImpl1->lastTime = millis();
trackballInterruptImpl1->intLeftHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}
trackballInterruptImpl1->intLeftHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}
void TrackballInterruptImpl1::handleIntRight()
{
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
trackballInterruptImpl1->lastTime = millis();
trackballInterruptImpl1->intRightHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}
trackballInterruptImpl1->intRightHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}
void TrackballInterruptImpl1::handleIntPressed()
{
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
trackballInterruptImpl1->lastTime = millis();
trackballInterruptImpl1->intPressHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}
trackballInterruptImpl1->intPressHandler();
trackballInterruptImpl1->setIntervalFromNow(20);
}

View File

@@ -10,6 +10,7 @@
#include "ReliableRouter.h"
#include "airtime.h"
#include "buzz.h"
#include "power/PowerHAL.h"
#include "FSCommon.h"
#include "Led.h"
@@ -38,9 +39,8 @@
#include "target_specific.h"
#include <memory>
#include <utility>
#ifdef ELECROW_ThinkNode_M5
PCA9557 io(0x18, &Wire);
#if HAS_SCREEN
#include "MessageStore.h"
#endif
#ifdef ARCH_ESP32
@@ -73,40 +73,62 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#include "mqtt/MQTT.h"
#endif
#include "LLCC68Interface.h"
#include "LR1110Interface.h"
#include "LR1120Interface.h"
#include "LR1121Interface.h"
#include "RF95Interface.h"
#include "SX1262Interface.h"
#include "SX1268Interface.h"
#include "SX1280Interface.h"
#include "detect/LoRaRadioType.h"
#ifdef ARCH_STM32WL
#include "STM32WLE5JCInterface.h"
#endif
#if defined(ARCH_PORTDUINO)
#include "platform/portduino/SimRadio.h"
#endif
#ifdef ARCH_PORTDUINO
#include "linux/LinuxHardwareI2C.h"
#include "mesh/raspihttp/PiWebServer.h"
#include "platform/portduino/PortduinoGlue.h"
#include "platform/portduino/USBHal.h"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#endif
#ifdef ARCH_ESP32
#ifdef DEBUG_PARTITION_TABLE
#include "esp_partition.h"
void printPartitionTable()
{
printf("\n--- Partition Table ---\n");
// Print Column Headers
printf("| %-16s | %-4s | %-7s | %-10s | %-10s |\n", "Label", "Type", "Subtype", "Offset", "Size");
printf("|------------------|------|---------|------------|------------|\n");
// Create an iterator to find ALL partitions (Type ANY, Subtype ANY)
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
// Loop through the iterator
if (it != NULL) {
do {
const esp_partition_t *part = esp_partition_get(it);
// Print details: Label, Type (Hex), Subtype (Hex), Offset (Hex), Size (Hex)
printf("| %-16s | 0x%02x | 0x%02x | 0x%08x | 0x%08x |\n", part->label, part->type, part->subtype, part->address,
part->size);
// Move to next partition
it = esp_partition_next(it);
} while (it != NULL);
// Release the iterator memory
esp_partition_iterator_release(it);
} else {
printf("No partitions found.\n");
}
printf("-----------------------\n");
}
#endif // DEBUG_PARTITION_TABLE
#endif // ARCH_ESP32
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
#include "input/ButtonThread.h"
#if defined(BUTTON_PIN_TOUCH)
ButtonThread *TouchButtonThread = nullptr;
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
static bool touchBacklightWasOn = false;
static bool touchBacklightActive = false;
#endif
#endif
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
@@ -205,13 +227,10 @@ ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE,
/// The I2C address of our Air Quality Indicator (if found)
ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef HAS_DRV2605
Adafruit_DRV2605 drv;
#endif
// Global LoRa radio type
LoRaRadioType radioType = NO_RADIO;
bool isVibrating = false;
bool eink_found = true;
@@ -248,6 +267,7 @@ const char *getDeviceName()
return name;
}
// TODO remove from main.cpp
static int32_t ledBlinker()
{
// Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if
@@ -288,6 +308,46 @@ __attribute__((weak, noinline)) bool loopCanSleep()
void lateInitVariant() __attribute__((weak));
void lateInitVariant() {}
void earlyInitVariant() __attribute__((weak));
void earlyInitVariant() {}
// NRF52 (and probably other platforms) can report when system is in power failure mode
// (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc).
// For example NRF52 will prevent any flash writes in that case automatically
// (but it causes issues we need to handle).
// This detection is independent from whatever ADC or dividers used in Meshtastic
// boards and is internal to chip.
// we use powerHAL layer to get this info and delay booting until power level is safe
// wait until power level is safe to continue booting (to avoid bootloops)
// blink user led in 3 flashes sequence to indicate what is happening
void waitUntilPowerLevelSafe()
{
#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
#endif
while (powerHAL_isPowerLevelSafe() == false) {
#ifdef LED_PIN
// 3x: blink for 300 ms, pause for 300 ms
for (int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, LED_STATE_ON);
delay(300);
digitalWrite(LED_PIN, LED_STATE_OFF);
delay(300);
}
#endif
// sleep for 2s
delay(2000);
}
}
/**
* Print info as a structured log message (for automated log processing)
*/
@@ -298,26 +358,22 @@ void printInfo()
#ifndef PIO_UNIT_TESTING
void setup()
{
#if defined(R1_NEO)
pinMode(DCDC_EN_HOLD, OUTPUT);
digitalWrite(DCDC_EN_HOLD, HIGH);
pinMode(NRF_ON, OUTPUT);
digitalWrite(NRF_ON, HIGH);
#endif
// initialize power HAL layer as early as possible
powerHAL_init();
// prevent booting if device is in power failure mode
// boot sequence will follow when battery level raises to safe mode
waitUntilPowerLevelSafe();
// Defined in variant.cpp for early init code
earlyInitVariant();
#if defined(PIN_POWER_EN)
pinMode(PIN_POWER_EN, OUTPUT);
digitalWrite(PIN_POWER_EN, HIGH);
#endif
#if defined(ELECROW_ThinkNode_M5)
Wire.begin(48, 47);
io.pinMode(PCA_PIN_EINK_EN, OUTPUT);
io.pinMode(PCA_PIN_POWER_EN, OUTPUT);
io.digitalWrite(PCA_PIN_POWER_EN, HIGH);
// io.pinMode(C2_PIN, OUTPUT);
#endif
#ifdef LED_POWER
pinMode(LED_POWER, OUTPUT);
digitalWrite(LED_POWER, LED_STATE_ON);
@@ -342,68 +398,7 @@ void setup()
#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);
#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 ARCH_PORTDUINO
SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
#else
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
#endif
meshtastic_Config_DisplayConfig_OledType screen_model =
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
@@ -542,6 +537,7 @@ void setup()
OSThread::setup();
// TODO make this ifdef based on defined pins and move from main.cpp
#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
// The ThinkNodes have their own blink logic
// ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
@@ -567,6 +563,7 @@ void setup()
Wire.setSCL(I2C_SCL);
Wire.begin();
#elif defined(I2C_SDA) && !defined(ARCH_RP2040)
LOG_INFO("Starting Bus with (SDA) %d and (SCL) %d: ", I2C_SDA, I2C_SCL);
Wire.begin(I2C_SDA, I2C_SCL);
#elif defined(ARCH_PORTDUINO)
if (portduino_config.i2cdev != "") {
@@ -640,7 +637,11 @@ void setup()
sensor_detected = true;
#endif
}
#ifdef ARCH_ESP32
#ifdef DEBUG_PARTITION_TABLE
printPartitionTable();
#endif
#endif // ARCH_ESP32
#ifdef ARCH_ESP32
// Don't init display if we don't have one or we are waking headless due to a timer event
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {
@@ -751,11 +752,12 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310U, meshtastic_TelemetrySensorType_QMC6310);
// TODO: Types need to be added meshtastic_TelemetrySensorType_QMC6310N
// scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310N, meshtastic_TelemetrySensorType_QMC6310N);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
@@ -795,7 +797,6 @@ void setup()
// We do this as early as possible because this loads preferences from flash
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
nodeDB = new NodeDB;
#if HAS_TFT
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
tftSetup();
@@ -841,7 +842,12 @@ void setup()
#endif
#endif
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef HAS_DRV2605
#if defined(PIN_DRV_EN)
pinMode(PIN_DRV_EN, OUTPUT);
digitalWrite(PIN_DRV_EN, HIGH);
delay(10);
#endif
drv.begin();
drv.selectLibrary(1);
// I2C trigger by sending 'go' command
@@ -877,7 +883,7 @@ void setup()
SPI.begin();
#endif
#else
// ESP32
// ESP32
#if defined(HW_SPI1_DEVICE)
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
@@ -909,6 +915,7 @@ void setup()
}
#endif // HAS_SCREEN
// TODO Remove magic string
// setup TZ prior to time actions.
#if !MESHTASTIC_EXCLUDE_TZ
LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string
@@ -1046,6 +1053,24 @@ void setup()
};
touchConfig.singlePress = INPUT_BROKER_NONE;
touchConfig.longPress = INPUT_BROKER_BACK;
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
// On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds
touchConfig.longPress = INPUT_BROKER_NONE;
touchConfig.suppressLeadUpSound = true;
touchConfig.onPress = []() {
touchBacklightWasOn = uiconfig.screen_brightness == 1;
if (!touchBacklightWasOn) {
digitalWrite(PIN_EINK_EN, HIGH);
}
touchBacklightActive = true;
};
touchConfig.onRelease = []() {
if (touchBacklightActive && !touchBacklightWasOn) {
digitalWrite(PIN_EINK_EN, LOW);
}
touchBacklightActive = false;
};
#endif
TouchButtonThread->initButton(touchConfig);
#endif
@@ -1186,252 +1211,7 @@ void setup()
#endif
#endif
#ifdef ARCH_PORTDUINO
// as one can't use a function pointer to the class constructor:
auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy) {
switch (portduino_config.lora_module) {
case use_rf95:
return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy);
case use_sx1262:
return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy);
case use_sx1268:
return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy);
case use_sx1280:
return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy);
case use_lr1110:
return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy);
case use_lr1120:
return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy);
case use_lr1121:
return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy);
case use_llcc68:
return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy);
case use_simradio:
return (RadioInterface *)new SimRadio;
default:
assert(0); // shouldn't happen
return (RadioInterface *)nullptr;
}
};
LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(),
portduino_config.lora_spi_dev.c_str());
if (portduino_config.lora_spi_dev == "ch341") {
RadioLibHAL = ch341Hal;
} else {
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
}
rIf =
loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin,
portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin);
if (!rIf->init()) {
LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str());
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str());
}
#elif defined(HW_SPI1_DEVICE)
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings);
#else // HW_SPI1_DEVICE
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
#endif
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
#if defined(USE_STM32WLx)
if (!rIf) {
rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No STM32WL radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("STM32WL init success");
radioType = STM32WLx_RADIO;
}
}
#endif
#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1);
if (!rIf->init()) {
LOG_WARN("No RF95 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("RF95 init success");
radioType = RF95_RADIO;
}
}
#endif
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
#ifdef SX126X_DIO3_TCXO_VOLTAGE
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
#endif
if (!sxIf->init()) {
LOG_WARN("No SX1262 radio");
delete sxIf;
rIf = NULL;
} else {
LOG_INFO("SX1262 init success");
rIf = sxIf;
radioType = SX1262_RADIO;
}
}
#endif
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// try using the specified TCXO voltage
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
if (!sxIf->init()) {
LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
delete sxIf;
rIf = NULL;
} else {
LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
rIf = sxIf;
radioType = SX1262_RADIO;
}
}
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instead
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("SX1262 init success, XTAL, Vref 0.0V");
radioType = SX1262_RADIO;
}
}
#endif
#if defined(USE_SX1268)
#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL)
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// try using the specified TCXO voltage
auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
if (!sxIf->init()) {
LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
delete sxIf;
rIf = NULL;
} else {
LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
rIf = sxIf;
radioType = SX1268_RADIO;
}
}
#endif
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No SX1268 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("SX1268 init success");
radioType = SX1268_RADIO;
}
}
#endif
#if defined(USE_LLCC68)
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No LLCC68 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LLCC68 init success");
radioType = LLCC68_RADIO;
}
}
#endif
#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN);
if (!rIf->init()) {
LOG_WARN("No LR1110 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LR1110 init success");
radioType = LR1110_RADIO;
}
}
#endif
#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1
if (!rIf) {
rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN);
if (!rIf->init()) {
LOG_WARN("No LR1120 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LR1120 init success");
radioType = LR1120_RADIO;
}
}
#endif
#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1
if (!rIf) {
rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN);
if (!rIf->init()) {
LOG_WARN("No LR1121 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LR1121 init success");
radioType = LR1121_RADIO;
}
}
#endif
#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1
if (!rIf) {
rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY);
if (!rIf->init()) {
LOG_WARN("No SX1280 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("SX1280 init success");
radioType = SX1280_RADIO;
}
}
#endif
// check if the radio chip matches the selected region
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) {
LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset");
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
nodeDB->saveToDisk(SEGMENT_CONFIG);
if (!rIf->reconfigure()) {
LOG_WARN("Reconfigure failed, rebooting");
if (screen) {
screen->showSimpleBanner("Rebooting...");
}
rebootAtMsec = millis() + 5000;
}
}
initLoRa();
lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation)
@@ -1512,13 +1292,15 @@ void setup()
}
#endif
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for OTA handoff)
// If a thread does something that might need for it to be rescheduled ASAP it can set this flag
// This will suppress the current delay and instead try to run ASAP.
bool runASAP;
// TODO find better home than main.cpp
extern meshtastic_DeviceMetadata getDeviceMetadata()
{
meshtastic_DeviceMetadata deviceMetadata;
@@ -1626,6 +1408,9 @@ void loop()
if (dispdev)
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
}
#endif
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
messageStoreAutosaveTick();
#endif
long delayMsec = mainController.runOrDelay();

Some files were not shown because too many files have changed in this diff Show More