Compare commits

...
20 Commits
Author SHA1 Message Date
kevin 8e03a9579c up
CI / setup (all) (push) Canceled after 0s
CI / setup (check) (push) Canceled after 0s
CI / version (push) Canceled after 0s
Build Debian Source Package / build-debian-src (push) Canceled after 0s
Package PlatformIO Library Dependencies / pkg-pio-libdeps (push) Canceled after 0s
Run Tests on Native platform / Generate Test Reports (push) Canceled after 0s
Run Tests on Native platform / Native PlatformIO Tests (push) Canceled after 0s
Run Tests on Native platform / Native Simulator Tests (push) Canceled after 0s
CI / check (${{ fromJson(needs.setup.outputs.check) }}) (push) Canceled after 0s
CI / build (${{ fromJson(needs.setup.outputs.all) }}) (push) Canceled after 0s
CI / gather-artifacts (esp32) (push) Canceled after 0s
CI / gather-artifacts (esp32c3) (push) Canceled after 0s
CI / gather-artifacts (esp32c6) (push) Canceled after 0s
CI / gather-artifacts (esp32s3) (push) Canceled after 0s
CI / gather-artifacts (nrf52840) (push) Canceled after 0s
CI / gather-artifacts (rp2040) (push) Canceled after 0s
CI / gather-artifacts (rp2350) (push) Canceled after 0s
CI / gather-artifacts (stm32) (push) Canceled after 0s
CI / firmware-size-report (push) Canceled after 0s
CI / release-artifacts (push) Canceled after 0s
CI / release-firmware (esp32) (push) Canceled after 0s
CI / release-firmware (esp32c3) (push) Canceled after 0s
CI / release-firmware (esp32c6) (push) Canceled after 0s
CI / release-firmware (esp32s3) (push) Canceled after 0s
CI / release-firmware (nrf52840) (push) Canceled after 0s
CI / release-firmware (rp2040) (push) Canceled after 0s
CI / release-firmware (rp2350) (push) Canceled after 0s
CI / release-firmware (stm32) (push) Canceled after 0s
CI / publish-firmware (push) Canceled after 0s
CI / build-debian-src (push) Canceled after 0s
CI / MacOS (15) (push) Canceled after 0s
CI / MacOS (26) (push) Canceled after 0s
Build MacOS Binary / build-MacOS (push) Canceled after 0s
CI / package-pio-deps-native-tft (push) Canceled after 0s
CI / test-native (push) Canceled after 0s
CI / docker (alpine, native, linux/amd64) (push) Canceled after 0s
CI / docker (alpine, native, linux/arm64) (push) Canceled after 0s
CI / docker (alpine, native-tft, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/arm/v7) (push) Canceled after 0s
CI / docker (debian, native, linux/arm64) (push) Canceled after 0s
CI / docker (debian, native-tft, linux/amd64) (push) Canceled after 0s
Build Docker / docker-build (push) Canceled after 0s
2026-07-05 12:19:53 +08:00
kevin 59dd837c0e up
CI / setup (all) (push) Canceled after 0s
CI / setup (check) (push) Canceled after 0s
CI / version (push) Canceled after 0s
Build Debian Source Package / build-debian-src (push) Canceled after 0s
Package PlatformIO Library Dependencies / pkg-pio-libdeps (push) Canceled after 0s
Run Tests on Native platform / Generate Test Reports (push) Canceled after 0s
Run Tests on Native platform / Native PlatformIO Tests (push) Canceled after 0s
Run Tests on Native platform / Native Simulator Tests (push) Canceled after 0s
CI / check (${{ fromJson(needs.setup.outputs.check) }}) (push) Canceled after 0s
CI / build (${{ fromJson(needs.setup.outputs.all) }}) (push) Canceled after 0s
CI / gather-artifacts (esp32) (push) Canceled after 0s
CI / gather-artifacts (esp32c3) (push) Canceled after 0s
CI / gather-artifacts (esp32c6) (push) Canceled after 0s
CI / gather-artifacts (esp32s3) (push) Canceled after 0s
CI / gather-artifacts (nrf52840) (push) Canceled after 0s
CI / gather-artifacts (rp2040) (push) Canceled after 0s
CI / gather-artifacts (rp2350) (push) Canceled after 0s
CI / gather-artifacts (stm32) (push) Canceled after 0s
CI / firmware-size-report (push) Canceled after 0s
CI / release-artifacts (push) Canceled after 0s
CI / release-firmware (esp32) (push) Canceled after 0s
CI / release-firmware (esp32c3) (push) Canceled after 0s
CI / release-firmware (esp32c6) (push) Canceled after 0s
CI / release-firmware (esp32s3) (push) Canceled after 0s
CI / release-firmware (nrf52840) (push) Canceled after 0s
CI / release-firmware (rp2040) (push) Canceled after 0s
CI / release-firmware (rp2350) (push) Canceled after 0s
CI / release-firmware (stm32) (push) Canceled after 0s
CI / publish-firmware (push) Canceled after 0s
CI / build-debian-src (push) Canceled after 0s
CI / MacOS (15) (push) Canceled after 0s
CI / MacOS (26) (push) Canceled after 0s
Build MacOS Binary / build-MacOS (push) Canceled after 0s
CI / package-pio-deps-native-tft (push) Canceled after 0s
CI / test-native (push) Canceled after 0s
CI / docker (alpine, native, linux/amd64) (push) Canceled after 0s
CI / docker (alpine, native, linux/arm64) (push) Canceled after 0s
CI / docker (alpine, native-tft, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/arm/v7) (push) Canceled after 0s
CI / docker (debian, native, linux/arm64) (push) Canceled after 0s
CI / docker (debian, native-tft, linux/amd64) (push) Canceled after 0s
Build Docker / docker-build (push) Canceled after 0s
2026-07-05 04:13:37 +08:00
kevin 472809b827 up
CI / setup (all) (push) Canceled after 0s
CI / setup (check) (push) Canceled after 0s
CI / version (push) Canceled after 0s
CI / check (${{ fromJson(needs.setup.outputs.check) }}) (push) Canceled after 0s
CI / build (${{ fromJson(needs.setup.outputs.all) }}) (push) Canceled after 0s
Build Debian Source Package / build-debian-src (push) Canceled after 0s
Package PlatformIO Library Dependencies / pkg-pio-libdeps (push) Canceled after 0s
Run Tests on Native platform / Generate Test Reports (push) Canceled after 0s
Run Tests on Native platform / Native PlatformIO Tests (push) Canceled after 0s
Run Tests on Native platform / Native Simulator Tests (push) Canceled after 0s
CI / gather-artifacts (esp32) (push) Canceled after 0s
CI / gather-artifacts (esp32c3) (push) Canceled after 0s
CI / gather-artifacts (esp32c6) (push) Canceled after 0s
CI / gather-artifacts (esp32s3) (push) Canceled after 0s
CI / gather-artifacts (nrf52840) (push) Canceled after 0s
CI / gather-artifacts (rp2040) (push) Canceled after 0s
CI / gather-artifacts (rp2350) (push) Canceled after 0s
CI / gather-artifacts (stm32) (push) Canceled after 0s
CI / firmware-size-report (push) Canceled after 0s
CI / release-artifacts (push) Canceled after 0s
CI / release-firmware (esp32) (push) Canceled after 0s
CI / release-firmware (esp32c3) (push) Canceled after 0s
CI / release-firmware (esp32c6) (push) Canceled after 0s
CI / release-firmware (esp32s3) (push) Canceled after 0s
CI / release-firmware (nrf52840) (push) Canceled after 0s
CI / release-firmware (rp2040) (push) Canceled after 0s
CI / release-firmware (rp2350) (push) Canceled after 0s
CI / release-firmware (stm32) (push) Canceled after 0s
CI / publish-firmware (push) Canceled after 0s
CI / build-debian-src (push) Canceled after 0s
CI / MacOS (15) (push) Canceled after 0s
CI / MacOS (26) (push) Canceled after 0s
Build MacOS Binary / build-MacOS (push) Canceled after 0s
CI / package-pio-deps-native-tft (push) Canceled after 0s
CI / test-native (push) Canceled after 0s
CI / docker (alpine, native, linux/amd64) (push) Canceled after 0s
CI / docker (alpine, native, linux/arm64) (push) Canceled after 0s
CI / docker (alpine, native-tft, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/arm/v7) (push) Canceled after 0s
CI / docker (debian, native, linux/arm64) (push) Canceled after 0s
CI / docker (debian, native-tft, linux/amd64) (push) Canceled after 0s
Build Docker / docker-build (push) Canceled after 0s
2026-06-25 21:31:21 +08:00
kevin d7cbac0156 up
CI / setup (all) (push) Canceled after 0s
CI / setup (check) (push) Canceled after 0s
CI / version (push) Canceled after 0s
CI / check (${{ fromJson(needs.setup.outputs.check) }}) (push) Canceled after 0s
CI / build (${{ fromJson(needs.setup.outputs.all) }}) (push) Canceled after 0s
Build Debian Source Package / build-debian-src (push) Canceled after 0s
Package PlatformIO Library Dependencies / pkg-pio-libdeps (push) Canceled after 0s
Run Tests on Native platform / Generate Test Reports (push) Canceled after 0s
Run Tests on Native platform / Native PlatformIO Tests (push) Canceled after 0s
Run Tests on Native platform / Native Simulator Tests (push) Canceled after 0s
CI / gather-artifacts (esp32) (push) Canceled after 0s
CI / gather-artifacts (esp32c3) (push) Canceled after 0s
CI / gather-artifacts (esp32c6) (push) Canceled after 0s
CI / gather-artifacts (esp32s3) (push) Canceled after 0s
CI / gather-artifacts (nrf52840) (push) Canceled after 0s
CI / gather-artifacts (rp2040) (push) Canceled after 0s
CI / gather-artifacts (rp2350) (push) Canceled after 0s
CI / gather-artifacts (stm32) (push) Canceled after 0s
CI / firmware-size-report (push) Canceled after 0s
CI / release-artifacts (push) Canceled after 0s
CI / release-firmware (esp32) (push) Canceled after 0s
CI / release-firmware (esp32c3) (push) Canceled after 0s
CI / release-firmware (esp32c6) (push) Canceled after 0s
CI / release-firmware (esp32s3) (push) Canceled after 0s
CI / release-firmware (nrf52840) (push) Canceled after 0s
CI / release-firmware (rp2040) (push) Canceled after 0s
CI / release-firmware (rp2350) (push) Canceled after 0s
CI / release-firmware (stm32) (push) Canceled after 0s
CI / publish-firmware (push) Canceled after 0s
CI / build-debian-src (push) Canceled after 0s
CI / MacOS (15) (push) Canceled after 0s
CI / MacOS (26) (push) Canceled after 0s
Build MacOS Binary / build-MacOS (push) Canceled after 0s
CI / package-pio-deps-native-tft (push) Canceled after 0s
CI / test-native (push) Canceled after 0s
CI / docker (alpine, native, linux/amd64) (push) Canceled after 0s
CI / docker (alpine, native, linux/arm64) (push) Canceled after 0s
CI / docker (alpine, native-tft, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/arm/v7) (push) Canceled after 0s
CI / docker (debian, native, linux/arm64) (push) Canceled after 0s
CI / docker (debian, native-tft, linux/amd64) (push) Canceled after 0s
Build Docker / docker-build (push) Canceled after 0s
2026-06-25 21:24:54 +08:00
kevin bc241481b1 添加中文支持
CI / setup (all) (push) Canceled after 0s
CI / setup (check) (push) Canceled after 0s
CI / version (push) Canceled after 0s
CI / check (${{ fromJson(needs.setup.outputs.check) }}) (push) Canceled after 0s
CI / build (${{ fromJson(needs.setup.outputs.all) }}) (push) Canceled after 0s
Build Debian Source Package / build-debian-src (push) Canceled after 0s
Package PlatformIO Library Dependencies / pkg-pio-libdeps (push) Canceled after 0s
Run Tests on Native platform / Generate Test Reports (push) Canceled after 0s
Run Tests on Native platform / Native PlatformIO Tests (push) Canceled after 0s
Run Tests on Native platform / Native Simulator Tests (push) Canceled after 0s
CI / gather-artifacts (esp32) (push) Canceled after 0s
CI / gather-artifacts (esp32c3) (push) Canceled after 0s
CI / gather-artifacts (esp32c6) (push) Canceled after 0s
CI / gather-artifacts (esp32s3) (push) Canceled after 0s
CI / gather-artifacts (nrf52840) (push) Canceled after 0s
CI / gather-artifacts (rp2040) (push) Canceled after 0s
CI / gather-artifacts (rp2350) (push) Canceled after 0s
CI / gather-artifacts (stm32) (push) Canceled after 0s
CI / firmware-size-report (push) Canceled after 0s
CI / release-artifacts (push) Canceled after 0s
CI / release-firmware (esp32) (push) Canceled after 0s
CI / release-firmware (esp32c3) (push) Canceled after 0s
CI / release-firmware (esp32c6) (push) Canceled after 0s
CI / release-firmware (esp32s3) (push) Canceled after 0s
CI / release-firmware (nrf52840) (push) Canceled after 0s
CI / release-firmware (rp2040) (push) Canceled after 0s
CI / release-firmware (rp2350) (push) Canceled after 0s
CI / release-firmware (stm32) (push) Canceled after 0s
CI / publish-firmware (push) Canceled after 0s
CI / build-debian-src (push) Canceled after 0s
CI / MacOS (15) (push) Canceled after 0s
CI / MacOS (26) (push) Canceled after 0s
Build MacOS Binary / build-MacOS (push) Canceled after 0s
CI / package-pio-deps-native-tft (push) Canceled after 0s
CI / test-native (push) Canceled after 0s
CI / docker (alpine, native, linux/amd64) (push) Canceled after 0s
CI / docker (alpine, native, linux/arm64) (push) Canceled after 0s
CI / docker (alpine, native-tft, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/arm/v7) (push) Canceled after 0s
CI / docker (debian, native, linux/arm64) (push) Canceled after 0s
CI / docker (debian, native-tft, linux/amd64) (push) Canceled after 0s
Build Docker / docker-build (push) Canceled after 0s
2026-06-25 21:00:37 +08:00
kevin ae62affeec 修复可能的卡死问题
CI / setup (all) (push) Canceled after 0s
CI / setup (check) (push) Canceled after 0s
CI / version (push) Canceled after 0s
CI / check (${{ fromJson(needs.setup.outputs.check) }}) (push) Canceled after 0s
CI / build (${{ fromJson(needs.setup.outputs.all) }}) (push) Canceled after 0s
Run Tests on Native platform / Generate Test Reports (push) Canceled after 0s
CI / gather-artifacts (esp32) (push) Canceled after 0s
CI / gather-artifacts (esp32c3) (push) Canceled after 0s
CI / gather-artifacts (esp32c6) (push) Canceled after 0s
CI / gather-artifacts (esp32s3) (push) Canceled after 0s
CI / gather-artifacts (nrf52840) (push) Canceled after 0s
CI / gather-artifacts (rp2040) (push) Canceled after 0s
CI / gather-artifacts (rp2350) (push) Canceled after 0s
CI / gather-artifacts (stm32) (push) Canceled after 0s
CI / firmware-size-report (push) Canceled after 0s
CI / release-artifacts (push) Canceled after 0s
CI / release-firmware (esp32) (push) Canceled after 0s
CI / release-firmware (esp32c3) (push) Canceled after 0s
CI / release-firmware (esp32c6) (push) Canceled after 0s
CI / release-firmware (esp32s3) (push) Canceled after 0s
CI / release-firmware (nrf52840) (push) Canceled after 0s
CI / release-firmware (rp2040) (push) Canceled after 0s
CI / release-firmware (rp2350) (push) Canceled after 0s
CI / release-firmware (stm32) (push) Canceled after 0s
CI / publish-firmware (push) Canceled after 0s
Build Debian Source Package / build-debian-src (push) Canceled after 0s
Package PlatformIO Library Dependencies / pkg-pio-libdeps (push) Canceled after 0s
Run Tests on Native platform / Native PlatformIO Tests (push) Canceled after 0s
Run Tests on Native platform / Native Simulator Tests (push) Canceled after 0s
CI / build-debian-src (push) Canceled after 0s
CI / MacOS (15) (push) Canceled after 0s
CI / MacOS (26) (push) Canceled after 0s
Build MacOS Binary / build-MacOS (push) Canceled after 0s
CI / package-pio-deps-native-tft (push) Canceled after 0s
CI / test-native (push) Canceled after 0s
CI / docker (alpine, native, linux/amd64) (push) Canceled after 0s
CI / docker (alpine, native, linux/arm64) (push) Canceled after 0s
CI / docker (alpine, native-tft, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/arm/v7) (push) Canceled after 0s
CI / docker (debian, native, linux/arm64) (push) Canceled after 0s
CI / docker (debian, native-tft, linux/amd64) (push) Canceled after 0s
Build Docker / docker-build (push) Canceled after 0s
2026-06-25 15:13:36 +08:00
kevin fa89db17bb 同时启动wifi和蓝牙
CI / setup (all) (push) Canceled after 0s
CI / setup (check) (push) Canceled after 0s
CI / version (push) Canceled after 0s
CI / check (${{ fromJson(needs.setup.outputs.check) }}) (push) Canceled after 0s
CI / build (${{ fromJson(needs.setup.outputs.all) }}) (push) Canceled after 0s
Run Tests on Native platform / Generate Test Reports (push) Canceled after 0s
Build Debian Source Package / build-debian-src (push) Canceled after 0s
Package PlatformIO Library Dependencies / pkg-pio-libdeps (push) Canceled after 0s
Run Tests on Native platform / Native PlatformIO Tests (push) Canceled after 0s
Run Tests on Native platform / Native Simulator Tests (push) Canceled after 0s
CI / gather-artifacts (esp32) (push) Canceled after 0s
CI / gather-artifacts (esp32c3) (push) Canceled after 0s
CI / gather-artifacts (esp32c6) (push) Canceled after 0s
CI / gather-artifacts (esp32s3) (push) Canceled after 0s
CI / gather-artifacts (nrf52840) (push) Canceled after 0s
CI / gather-artifacts (rp2040) (push) Canceled after 0s
CI / gather-artifacts (rp2350) (push) Canceled after 0s
CI / gather-artifacts (stm32) (push) Canceled after 0s
CI / firmware-size-report (push) Canceled after 0s
CI / release-artifacts (push) Canceled after 0s
CI / release-firmware (esp32) (push) Canceled after 0s
CI / release-firmware (esp32c3) (push) Canceled after 0s
CI / release-firmware (esp32c6) (push) Canceled after 0s
CI / release-firmware (esp32s3) (push) Canceled after 0s
CI / release-firmware (nrf52840) (push) Canceled after 0s
CI / release-firmware (rp2040) (push) Canceled after 0s
CI / release-firmware (rp2350) (push) Canceled after 0s
CI / release-firmware (stm32) (push) Canceled after 0s
CI / publish-firmware (push) Canceled after 0s
CI / build-debian-src (push) Canceled after 0s
CI / MacOS (15) (push) Canceled after 0s
CI / MacOS (26) (push) Canceled after 0s
Build MacOS Binary / build-MacOS (push) Canceled after 0s
CI / package-pio-deps-native-tft (push) Canceled after 0s
CI / test-native (push) Canceled after 0s
CI / docker (alpine, native, linux/amd64) (push) Canceled after 0s
CI / docker (alpine, native, linux/arm64) (push) Canceled after 0s
CI / docker (alpine, native-tft, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/amd64) (push) Canceled after 0s
CI / docker (debian, native, linux/arm/v7) (push) Canceled after 0s
CI / docker (debian, native, linux/arm64) (push) Canceled after 0s
CI / docker (debian, native-tft, linux/amd64) (push) Canceled after 0s
Build Docker / docker-build (push) Canceled after 0s
2026-06-25 13:41:58 +08:00
kevin 8e6525c4aa 修改编译方式
Build Docker Multi-Arch Manifest / docker-manifest (push) Canceled after 0s
Package for OpenSUSE Build Service / package-obs (push) Canceled after 0s
Trigger COPR build / build-copr-hook (push) Canceled after 0s
Daily Packaging / docker-multiarch (push) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-debian-amd64 (push) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-debian-arm64 (push) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-debian-armv7 (push) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-debian-riscv64 (push) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-alpine-amd64 (push) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-alpine-arm64 (push) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-alpine-armv7 (push) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-alpine-riscv64 (push) Canceled after 0s
Daily Packaging / package-ppa (jammy) (push) Canceled after 0s
Daily Packaging / package-ppa (noble) (push) Canceled after 0s
Daily Packaging / package-ppa (questing) (push) Canceled after 0s
Daily Packaging / package-ppa (resolute) (push) Canceled after 0s
Package for Launchpad PPA / build-debian-src (push) Canceled after 0s
Package for Launchpad PPA / package-ppa (push) Canceled after 0s
Daily Packaging / package-obs (push) Canceled after 0s
Package for OpenSUSE Build Service / build-debian-src (push) Canceled after 0s
Daily Packaging / hook-copr (push) Canceled after 0s
Build Docker / docker-build (push) Canceled after 0s
Build Debian Source Package / build-debian-src (push) Canceled after 0s
2026-06-25 12:57:12 +08:00
Ben MeadorsandGitHub 7aa004c1eb Merge pull request #10780 from meshtastic/forward-port-t-impulse-plus
Forward-port: Add Lilygo T-Impulse-Plus (#10497) to develop
2026-06-24 20:11:33 -05:00
Ben MeadorsandGitHub 8e72b16693 Merge pull request #10779 from meshtastic/forward-port-master-fixes
Forward-port master-only fixes to develop
2026-06-24 20:10:58 -05:00
AustinandBen Meadors 3c68c2eed9 Debian: Correctly build without signing (for forks) (#10605)
(cherry picked from commit 266c143359)
2026-06-24 18:39:44 -05:00
AustinandBen Meadors a43cd75bd3 Add Lilygo T-Impulse-Plus (#10497)
* Add Lilygo T-Impulse-Plus

* Enable small screen layout 64x32

* trunk'd

* Haptic Feedback (short and long press)

* enable Charging Indicator

* enable nrfutil uploads

* trunk fmt

* Add Lilygo T-Impulse-Plus

* Enable small screen layout 64x32

* trunk'd

* Haptic Feedback (short and long press)

* enable Charging Indicator

* enable nrfutil uploads

* trunk fmt

* enable proper device model

* Dim the haptic duration a bit.

* Fix GPS pins and speed. Module enable is active low, speed is 38400

* Potential fix for pull request finding

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

* set correct geometry.

* Add custom_meshtastic_* metadata to t-impulse-plus platformio.ini

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
(cherry picked from commit cb867cc6c8)
2026-06-24 16:54:03 -05:00
Ben Meadors 41cbb45cdc Fix build on picomputer
(cherry picked from commit 22d08b4303)
2026-06-24 16:50:44 -05:00
ManuelandBen Meadors 434225eb01 tdeck touch driver fix (#10740)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
(cherry picked from commit 4044f4af6c)
2026-06-24 16:50:44 -05:00
LittleatonandBen Meadors b603ed0bbf Additional *RAK* missing values for 6421 / 13300 13302 modules + Zebra/Nebra Hat Configs (#10644)
* rebased to master

* Update bin/config.d/lora-ZebraHat_2W.yaml

Co-authored-by: Austin <vidplace7@gmail.com>

* Update bin/config.d/lora-ZebraHat_1W.yaml

Co-authored-by: Austin <vidplace7@gmail.com>

* Update bin/config.d/lora-NebraHat_2W.yaml

Co-authored-by: Austin <vidplace7@gmail.com>

* Update bin/config.d/lora-NebraHat_1W.yaml

Co-authored-by: Austin <vidplace7@gmail.com>

* Remove TX_GAIN_LORA configuration line

* Remove TX_GAIN_LORA configuration line

* Comment out TX_GAIN_LORA configuration

* Comment out TX_GAIN_LORA configuration

* Update lora-ok3506-RAK6421-13300-slot1.yaml

* Update lora-ok3506-RAK6421-13300-slot2.yaml

* Update lora-RAK6421-13300-slot1.yaml

* Update lora-RAK6421-13300-slot2.yaml

---------

Co-authored-by: Austin <vidplace7@gmail.com>
(cherry picked from commit a49729eb89)
2026-06-24 16:49:29 -05:00
AustinandBen Meadors 245b45fff3 meshtasticd: Add configs for B&Q Station G3 (#10673)
configs for raspberry pi and luckfox lyra zero

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
(cherry picked from commit bfc206a47b)
2026-06-24 16:49:29 -05:00
Ben Meadors 9e3a9c370d Enhance RTC handling with unit test support for system time fallback (#10642)
* Enhance RTC handling with unit test support for system time fallback

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
(cherry picked from commit c7f17a80b2)
2026-06-24 16:49:29 -05:00
Ben MeadorsandGitHub b93239bb0a fix(phone-api): skip manifest scan for node-info-only requests; bound getFiles (#10778)
Forward-port of #10754 and #10757 from master (2.7) into develop, so the
develop->master 2.8 promotion (#10777) doesn't drop them.

#10754: PhoneAPI no longer walks the filesystem to build the file manifest on
node-info-only config requests (SPECIAL_NONCE_ONLY_NODES), which never consume
it. getFiles() is now bounded (default 64 entries, depth 3) via collectFiles(),
takes an optional wasLimited out-param, and reserves capacity with a bad_alloc/
length_error fallback. The manifest vector is freed via swap (releaseFilesManifest).

#10757: getFiles()/collectFiles() now guard against empty file names returned by
the Adafruit LittleFS nRF52 glue (issue 4395).

Ported by hand rather than cherry-picked: master had reflowed FSCommon.cpp to a
different brace style (every line conflicted), #10754 already subsumes #10757,
and develop carries a MESHTASTIC_EXCLUDE_FILES_MANIFEST path (nRF54L15) that
master lacks. The exclude path is preserved and now also short-circuits + frees
the manifest. Verified: native Docker suite 448/448, clang-format clean.
2026-06-24 12:38:46 -05:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>thebenternBen MeadorsClaude Opus 4.8
fcfe329091 Guard TCP API writes after Wi-Fi reconnects (#10505)
* Guard TCP API writes against dead sockets

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/ebeb38d5-7339-4eac-b310-4b6dd9d40758

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* fix: apply review fixes for server write checks and stream locking

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/215c659d-bc17-4b30-89f4-78e3f9cdb3c3

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* style: format TCP API write check files

* fix(api): keep TCP API open on write backpressure; drop broken writability gate

canWriteFrame() treated a full transmit buffer (availableForWrite() == 0) as a
dead socket and closed the connection, so every healthy client was dropped and
the native simulator integration test timed out waiting for config. A zero
availableForWrite() is normal backpressure, not a disconnect; only a dropped
link should refuse a write. Reduce canWriteFrame() to the reliable
!client.connected() check -- genuine write failures are still caught after the
fact by onFrameWriteFailed().

Remove the now-unused ClientWriteChecks.h writability helper and its unit test
(test_client_write_checks, which also failed to link), and restore
ServerAPI.cpp to the project clang-format style (fixes the Trunk check).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 05:31:17 -05:00
Jonathan BennettandGitHub 04545c7ba2 Delete dead code src/platform/esp32/iram-quirk.c (#10762) 2026-06-22 16:31:49 -05:00
63 changed files with 3262 additions and 568 deletions
+2 -1
View File
@@ -10,5 +10,6 @@
},
"[powershell]": {
"editor.defaultFormatter": "ms-vscode.powershell"
}
},
"idf.currentSetup": "C:/Espressif/frameworks/esp-idf-v5.3.1/"
}
+19
View File
@@ -0,0 +1,19 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/NebraHat
# Use for 1 watt hat
Meta:
name: NebraHat 1W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Nebra SX1262 Pi Hat - 1W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
# CS: 8 # Newer version of MeshtasticD do not need this? If issues uncomment this line
IRQ: 22
Busy: 4
Reset: 18
RXen: 25
I2C:
I2CDevice: /dev/i2c-1
+20
View File
@@ -0,0 +1,20 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/NebraHat
# Use for 2 watt hat
Meta:
name: NebraHat 2W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Nebra SX1262 Pi Hat - 2W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 8
# CS: 8 # Newer version of MeshtasticD do not need this? If issues uncomment this line
IRQ: 22
Busy: 4
Reset: 18
RXen: 25
I2C:
I2CDevice: /dev/i2c-1
+1 -1
View File
@@ -18,4 +18,4 @@ Lora:
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: 8
# CS: 8
+1 -1
View File
@@ -18,4 +18,4 @@ Lora:
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: 7
# CS: 7
+19
View File
@@ -0,0 +1,19 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/ZebraHAT
# Use for 1 watt hat
Meta:
name: ZebraHat 1W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Zebra SX1262 Pi Hat - 1W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 18
CS: 24
IRQ: 22
Busy: 27
Reset: 17
I2C:
I2CDevice: /dev/i2c-1
+20
View File
@@ -0,0 +1,20 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/ZebraHAT
# Use for 2 watt hat
Meta:
name: ZebraHat 2W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Zebra SX1262 Pi Hat - 2W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 8
CS: 24
IRQ: 22
Busy: 27
Reset: 17
RXen: 25
I2C:
I2CDevice: /dev/i2c-1
@@ -29,6 +29,8 @@ Lora:
- pin: 50 # GPIO1_C2 (physical 16)
gpiochip: 1
line: 18
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_A7 (SPI1_CSN1, physical 26)
# pin: 7
@@ -29,6 +29,8 @@ Lora:
- pin: 50 # GPIO1_C2 (physical 16)
gpiochip: 1
line: 18
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_A7 (SPI1_CSN1, physical 26)
# pin: 7
@@ -0,0 +1,29 @@
# Station G3 + BQ35LORA900V1M Primary Slot
# Board Doc: https://wiki.bqvoy.com/en/devkits/station-g3
Meta:
name: B&Q Station G3 + BQ35LORA900V1M Primary Slot
support: official
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_A5 (physical 15)
pin: 5
gpiochip: 0
line: 5
Reset: # GPIO1_D1 (physical 36)
pin: 57
gpiochip: 1
line: 25
Busy: # GPIO0_B4 (physical 18)
pin: 12
gpiochip: 0
line: 12
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: # GPIO0_B2 (physical 24)
# pin: 10
# gpiochip: 0
# line: 10
@@ -29,6 +29,8 @@ Lora:
- pin: 13 # GPIO0_B5
gpiochip: 0
line: 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B1
# pin: 9
@@ -29,6 +29,8 @@ Lora:
- pin: 13 # GPIO0_B5
gpiochip: 0
line: 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B1
# pin: 9
@@ -31,6 +31,8 @@ Lora:
- pin: 103 # GPIO3_A7 (physical 16)
gpiochip: 3
line: 7
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B7 (SPI0_CSN1, physical 26)
# pin: 15
@@ -31,6 +31,8 @@ Lora:
- pin: 103 # GPIO3_A7 (physical 16)
gpiochip: 3
line: 7
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B7 (SPI0_CSN1, physical 26)
# pin: 15
@@ -0,0 +1,17 @@
# Station G3 + BQ35LORA900V1M Primary Slot
# Board Doc: https://wiki.bqvoy.com/en/devkits/station-g3
Meta:
name: B&Q Station G3 + BQ35LORA900V1M Primary Slot
support: official
compatible:
- raspberry-pi
Lora:
Module: sx1262
IRQ: 22
Reset: 16
Busy: 24
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
#CS: 8
+2
View File
@@ -1,3 +1,5 @@
# This config works with all revisions of the Meshtoad USB radio.
Meta:
name: meshtoad-e22
support: official
+25 -29
View File
@@ -219,41 +219,37 @@ with open(jsonLoc) as f:
jsonStr = re.sub("//.*","", f.read(), flags=re.MULTILINE)
userPrefs = json.loads(jsonStr)
pref_flags = []
# Pre-process the userPrefs
for pref in userPrefs:
if userPrefs[pref].startswith("{"):
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
elif userPrefs[pref].lstrip("-").replace(".", "").isdigit():
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
elif userPrefs[pref] == "true" or userPrefs[pref] == "false":
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
elif userPrefs[pref].startswith("meshtastic_"):
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
# If the value is a string, we need to wrap it in quotes
else:
pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "")
# General options that are passed to the C and C++ compilers
# Calculate unix epoch for current day (midnight)
current_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
build_epoch = int(current_date.timestamp())
flags = [
"-DAPP_VERSION=" + verObj["long"],
"-DAPP_VERSION_SHORT=" + verObj["short"],
"-DAPP_ENV=" + env.get("PIOENV"),
"-DAPP_REPO=" + repo_owner,
"-DBUILD_EPOCH=" + str(build_epoch),
] + pref_flags
cpp_defines = [
("APP_VERSION", verObj["long"]),
("APP_VERSION_SHORT", verObj["short"]),
("APP_ENV", env.get("PIOENV")),
("APP_REPO", repo_owner),
("BUILD_EPOCH", str(build_epoch)),
]
print("Using flags:")
for flag in flags:
print(flag)
# Process userPrefs into CPPDEFINES
for pref in userPrefs:
val = userPrefs[pref]
if val.startswith("{"):
cpp_defines.append((pref, val))
elif val.lstrip("-").replace(".", "").isdigit():
cpp_defines.append((pref, val))
elif val == "true" or val == "false":
cpp_defines.append((pref, val))
elif val.startswith("meshtastic_"):
cpp_defines.append((pref, val))
else:
cpp_defines.append((pref, env.StringifyMacro(val)))
projenv.Append(
CCFLAGS=flags,
)
print("Using CPPDEFINES:")
for d in cpp_defines:
print(f" -D{d[0]}={d[1]}")
projenv.Append(CPPDEFINES=cpp_defines)
for lb in env.GetLibBuilders():
if lb.name == "meshtastic-device-ui":
+53
View File
@@ -0,0 +1,53 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_T_IMPULSE_PLUS -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [["0x239A", "0x8029"]],
"usb_product": "T-Impulse-Plus-nRF52840",
"mcu": "nrf52840",
"variant": "t-impulse-plus",
"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"
},
"frameworks": ["arduino"],
"name": "Lilygo T-Impulse-Plus-nRF52840",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"require_upload_port": true,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
]
},
"url": "https://www.lilygo.cc/",
"vendor": "Lilygo"
}
+1 -1
View File
@@ -33,5 +33,5 @@ if [[ -n $GPG_KEY_ID ]]; then
debuild -S -nc -k"$GPG_KEY_ID"
else
# Build the source deb without signing (forks)
debuild -S -nc
debuild -S -nc -us -uc
fi
+2 -1
View File
@@ -13,7 +13,7 @@ extra_configs =
description = Meshtastic
[env]
test_build_src = true
; test_build_src = true ; disabled for faster incremental builds
extra_scripts =
pre:bin/platformio-pre.py
bin/platformio-custom.py
@@ -62,6 +62,7 @@ build_flags = -Wno-missing-field-initializers
-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
#-D OLED_ZH=1 ; uncomment for compile-time Chinese font (runtime switching works without this on TFT)
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
#-D DEBUG_LOOP_TIMING=1 ; uncomment to add main loop timing logs
+118 -31
View File
@@ -88,6 +88,9 @@ bool renameFile(const char *pathFrom, const char *pathTo)
#endif
}
#include <cstring>
#include <new>
#include <stdexcept>
#include <vector>
/**
@@ -119,6 +122,93 @@ bool fsFormat()
#endif
}
#ifdef FSCom
namespace
{
bool pathEndsWithDot(const char *path)
{
if (!path)
return false;
size_t length = strlen(path);
return length > 0 && path[length - 1] == '.';
}
bool copyFilePath(char *dest, size_t destSize, const char *path, bool *wasLimited)
{
if (!path || destSize == 0) {
if (wasLimited)
*wasLimited = true;
return false;
}
if (strlcpy(dest, path, destSize) >= destSize) {
if (wasLimited)
*wasLimited = true;
return false;
}
return true;
}
void collectFiles(const char *dirname, uint8_t levels, size_t maxCount, std::vector<meshtastic_FileInfo> &filenames,
bool *wasLimited)
{
if (!dirname)
return;
File root = FSCom.open(dirname, FILE_O_READ);
if (!root)
return;
if (!root.isDirectory()) {
root.close();
return;
}
File file = root.openNextFile();
// file.name()[0] check is a workaround for a bug in the Adafruit LittleFS nrf52 glue (see issue 4395)
while (file && file.name()[0]) {
if (filenames.size() >= maxCount) {
if (wasLimited)
*wasLimited = true;
file.close();
break;
}
const char *fileName = file.name();
if (file.isDirectory() && !pathEndsWithDot(fileName)) {
char pathBuffer[sizeof(((meshtastic_FileInfo *)nullptr)->file_name)] = {};
#ifdef ARCH_ESP32
const char *subDirPath = file.path();
#else
const char *subDirPath = fileName;
#endif
bool hasSubDirPath = copyFilePath(pathBuffer, sizeof(pathBuffer), subDirPath, wasLimited);
file.close();
if (levels && hasSubDirPath) {
collectFiles(pathBuffer, levels - 1, maxCount, filenames, wasLimited);
} else if (wasLimited) {
*wasLimited = true;
}
} else {
meshtastic_FileInfo fileInfo = {"", static_cast<uint32_t>(file.size())};
#ifdef ARCH_ESP32
bool hasFilePath = copyFilePath(fileInfo.file_name, sizeof(fileInfo.file_name), file.path(), wasLimited);
#else
bool hasFilePath = copyFilePath(fileInfo.file_name, sizeof(fileInfo.file_name), file.name(), wasLimited);
#endif
if (hasFilePath && !pathEndsWithDot(fileInfo.file_name)) {
filenames.push_back(fileInfo);
}
file.close();
}
file = root.openNextFile();
}
root.close();
}
} // namespace
#endif
/**
* @brief Get the list of files in a directory.
*
@@ -127,43 +217,40 @@ bool fsFormat()
*
* @param dirname The name of the directory.
* @param levels The number of levels of subdirectories to list.
* @return A vector of strings containing the full path of each file in the directory.
* @param maxCount The maximum number of files to collect before truncating the walk.
* @param wasLimited Optional out-param, set to true if the listing was truncated (by maxCount or low memory).
* @return A vector of meshtastic_FileInfo for each file in the directory.
*/
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels)
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels, size_t maxCount, bool *wasLimited)
{
std::vector<meshtastic_FileInfo> filenames = {};
if (wasLimited)
*wasLimited = false;
#ifdef FSCom
File root = FSCom.open(dirname, FILE_O_READ);
if (!root)
return filenames;
if (!root.isDirectory())
return filenames;
File file = root.openNextFile();
while (file) {
#ifdef ARCH_ESP32
const char *filepath = file.path();
#else
const char *filepath = file.name();
#endif
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
if (levels) {
std::vector<meshtastic_FileInfo> subDirFilenames = getFiles(filepath, levels - 1);
filenames.insert(filenames.end(), subDirFilenames.begin(), subDirFilenames.end());
file.close();
}
} else {
meshtastic_FileInfo fileInfo = {"", static_cast<uint32_t>(file.size())};
strncpy(fileInfo.file_name, filepath, sizeof(fileInfo.file_name) - 1);
fileInfo.file_name[sizeof(fileInfo.file_name) - 1] = '\0';
if (!String(fileInfo.file_name).endsWith(".")) {
filenames.push_back(fileInfo);
}
file.close();
#if defined(__cpp_exceptions) || defined(__EXCEPTIONS)
size_t reservedCount = maxCount;
while (reservedCount > 0) {
try {
filenames.reserve(reservedCount);
break;
} catch (const std::bad_alloc &) {
reservedCount /= 2;
} catch (const std::length_error &) {
reservedCount /= 2;
}
file = root.openNextFile();
}
root.close();
if (reservedCount == 0) {
if (wasLimited)
*wasLimited = true;
return filenames;
}
if (reservedCount < maxCount) {
if (wasLimited)
*wasLimited = true;
maxCount = reservedCount;
}
#endif
collectFiles(dirname, levels, maxCount, filenames, wasLimited);
#endif
return filenames;
}
+1 -1
View File
@@ -61,7 +61,7 @@ void fsListFiles();
bool copyFile(const char *from, const char *to);
bool renameFile(const char *pathFrom, const char *pathTo);
bool fsFormat();
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels, size_t maxCount = 64, bool *wasLimited = nullptr);
void listDir(const char *dirname, uint8_t levels, bool del = false);
void rmDir(const char *dirname);
void setupSDCard();
+15
View File
@@ -24,6 +24,7 @@
#include "main.h"
#include "meshUtils.h"
#include "power/PowerHAL.h"
#include "power/SGM41562.h"
#include "sleep.h"
#ifdef ARCH_ESP32
// #include <driver/adc.h>
@@ -545,6 +546,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
// lastly provide a fallback to indicate external power when fully charged.
virtual bool isVbusIn() override
{
#ifdef HAS_SGM41562
if (sgm41562 && sgm41562->refresh())
return sgm41562->isInputPowerGood();
#endif
#ifdef EXT_PWR_DETECT
return digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE;
@@ -561,6 +566,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
/// we can't be smart enough to say 'full'?
virtual bool isCharging() override
{
#ifdef HAS_SGM41562
if (sgm41562 && sgm41562->refresh())
return sgm41562->isCharging();
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT) && !defined(HAS_PMU)
if (hasRAK()) {
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
@@ -767,6 +776,12 @@ bool Power::analogInit()
*/
bool Power::setup()
{
#ifdef HAS_SGM41562
// Initialize the charger early so AnalogBatteryLevel can read charging
// state from it. The charger does not provide battery voltage / percent —
// those still come from the platform ADC via analogInit() below.
initSGM41562(SGM41562_WIRE);
#endif
bool found = false;
if (axpChipInit()) {
found = true;
+87 -12
View File
@@ -31,13 +31,63 @@ static uint32_t
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
#ifdef PIO_UNIT_TESTING
// Test seam: unit tests can inject a fake system clock (e.g. the uptime seconds that
// gettimeofday() returns on boards without a real RTC, like RP2040) and force readFromRTC()
// down the no-hardware-RTC fallback even when a hardware-RTC branch is compiled in.
static bool hasMockSystemTime = false;
static bool forceSystemTimeFallback = false;
static struct timeval mockSystemTime = {};
#endif
// Reads the platform system clock (or the injected mock during unit tests). Used only by the
// no-hardware-RTC fallback below, so it may be unused on builds with a hardware RTC.
[[maybe_unused]] static bool readSystemTime(struct timeval *tv)
{
#ifdef PIO_UNIT_TESTING
if (hasMockSystemTime) {
*tv = mockSystemTime;
return true;
}
#endif
return gettimeofday(tv, NULL) == 0;
}
// Seeds the clock from the system time on boards without a hardware RTC. gettimeofday() can
// return uptime rather than wall-clock time there (e.g. RP2040), so only adopt it when we have
// nothing better yet -- never clobber a higher-quality GPS/NTP/phone source (issue #9828).
[[maybe_unused]] static RTCSetResult readFromSystemTimeFallback()
{
struct timeval tv;
if (readSystemTime(&tv)) {
uint32_t now = millis();
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
if (currentQuality == RTCQualityNone) {
LOG_DEBUG("Seed time from system clock: %lu", (unsigned long)printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
} else {
LOG_DEBUG("Ignore system clock fallback (%lu); current RTC quality is %s", (unsigned long)printableEpoch,
RtcName(currentQuality));
}
return RTCSetResultSuccess;
}
return RTCSetResultNotSet;
}
/**
* Reads the current date and time from the RTC module and updates the system time.
* @return True if the RTC was successfully read and the system time was updated, false otherwise.
* Reads date/time from the RTC module (or system-time fallback) and seeds internal timekeeping.
* @return RTCSetResultSuccess if a time source was read successfully (even if an existing higher-quality time is retained).
*/
RTCSetResult readFromRTC()
{
struct timeval tv; /* btw settimeofday() is helpful here too*/
#ifdef PIO_UNIT_TESTING
if (forceSystemTimeFallback) {
return readFromSystemTimeFallback();
}
#endif
[[maybe_unused]] struct timeval tv; /* btw settimeofday() is helpful here too*/
#ifdef RV3028_RTC
if (rtc_found.address == RV3028_RTC) {
uint32_t now = millis();
@@ -162,14 +212,7 @@ RTCSetResult readFromRTC()
}
}
#else
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
LOG_DEBUG("Read RTC time as %ld", printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
return RTCSetResultSuccess;
}
return readFromSystemTimeFallback();
#endif
return RTCSetResultNotSet;
}
@@ -292,7 +335,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
LOG_WARN("Failed to set time for RX8130CE");
}
}
#elif defined(ARCH_ESP32)
#elif defined(ARCH_ESP32) || defined(ARCH_RP2040)
settimeofday(tv, NULL);
#endif
@@ -423,6 +466,38 @@ void setBootRelativeTimeForUnitTest(uint32_t secondsSinceBoot)
lastSetFromPhoneNtpOrGps = 0;
lastTimeValidationWarning = 0;
}
void clearRTCSystemTimeForTests()
{
hasMockSystemTime = false;
mockSystemTime = {};
}
void setRTCSystemTimeForTests(const struct timeval *tv)
{
if (tv == NULL) {
clearRTCSystemTimeForTests();
return;
}
mockSystemTime = *tv;
hasMockSystemTime = true;
}
void setReadFromRTCUseSystemTimeForTests(bool enabled)
{
forceSystemTimeFallback = enabled;
}
void resetRTCStateForTests()
{
currentQuality = RTCQualityNone;
timeStartMsec = 0;
zeroOffsetSecs = 0;
lastSetFromPhoneNtpOrGps = 0;
lastTimeValidationWarning = 0;
setReadFromRTCUseSystemTimeForTests(false);
clearRTCSystemTimeForTests();
}
#endif
time_t gm_mktime(const struct tm *tm)
+4
View File
@@ -56,6 +56,10 @@ RTCSetResult readFromRTC();
#ifdef PIO_UNIT_TESTING
void setBootRelativeTimeForUnitTest(uint32_t secondsSinceBoot);
void resetRTCStateForTests();
void setRTCSystemTimeForTests(const struct timeval *tv);
void clearRTCSystemTimeForTests();
void setReadFromRTCUseSystemTimeForTests(bool enabled);
#endif
time_t gm_mktime(const struct tm *tm);
+286 -5
View File
@@ -39,6 +39,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "draw/NodeListRenderer.h"
#include "draw/NotificationRenderer.h"
#include "draw/UIRenderer.h"
#include "graphics/fonts/OLEDDisplayFontsZH.h"
#include "graphics/TFTColorRegions.h"
#include "modules/CannedMessageModule.h"
#include "security/LockdownDisplay.h"
@@ -60,6 +61,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "graphics/TFTPalette.h"
#include "graphics/emotes.h"
#include "graphics/images.h"
#include "graphics/l10n/Strings.h"
#include "input/TouchScreenImpl1.h"
#include "main.h"
#include "mesh-pb-constants.h"
@@ -140,10 +142,11 @@ static void drawLockdownLockScreenIntoBuffer(OLEDDisplay *display)
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_LARGE);
display->drawString(w / 2, h / 2 - FONT_HEIGHT_LARGE, "LOCKED");
display->drawString(w / 2, h / 2 - FONT_HEIGHT_LARGE, meshtastic::l10n::getString(meshtastic::l10n::StringKey::LOCKED));
display->setFont(FONT_SMALL);
char status[32] = "Connect to unlock";
char status[32];
strncpy(status, meshtastic::l10n::getString(meshtastic::l10n::StringKey::ConnectToUnlock), 31);
if (powerStatus && powerStatus->getHasBattery()) {
int pct = powerStatus->getBatteryChargePercent();
snprintf(status, sizeof(status), "Battery %d%%", pct);
@@ -731,7 +734,10 @@ void Screen::setup()
// Enable display rendering
useDisplay = true;
// Load saved brightness from UI config
// Set the runtime display language from persisted config
meshtastic::l10n::currentDisplayLanguage = uiconfig.language;
// Load brightness from UI config
// For OLED displays (SSD1306), default brightness is 255 if not set
if (uiconfig.screen_brightness == 0) {
#if defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
@@ -819,6 +825,9 @@ void Screen::setup()
// Enable UTF-8 to display mapping
dispdev->setFontTableLookupFunction(customFontTableLookup);
// Load CJK (Chinese) font if language requires it
loadCJKFontIfNeeded();
#ifdef USERPREFS_OEM_TEXT
logo_timeout *= 2; // Give more time for branded boot logos
#endif
@@ -828,7 +837,7 @@ void Screen::setup()
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
#ifdef ARCH_ESP32
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER || wakeCause == ESP_SLEEP_WAKEUP_EXT1)
graphics::UIRenderer::drawFrameText(display, state, x, y, "Resuming...");
graphics::UIRenderer::drawFrameText(display, state, x, y, meshtastic::l10n::getString(meshtastic::l10n::StringKey::Resuming));
else
#endif
{
@@ -985,6 +994,13 @@ int32_t Screen::runOnce()
return RUN_SAME;
}
// Detect language changes from remote config (phone app)
if (uiconfig.language != meshtastic::l10n::currentDisplayLanguage) {
meshtastic::l10n::currentDisplayLanguage = uiconfig.language;
loadCJKFontIfNeeded();
setFrames(FOCUS_PRESERVE);
}
if (displayHeight == 0) {
displayHeight = dispdev->getHeight();
}
@@ -1052,7 +1068,7 @@ int32_t Screen::runOnce()
}
#endif
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0 && !suppressRebootBanner) {
showSimpleBanner("Rebooting...", 0);
showSimpleBanner(meshtastic::l10n::getString(meshtastic::l10n::StringKey::Rebooting), 0);
}
// Process incoming commands.
@@ -2210,6 +2226,271 @@ bool Screen::isOverlayBannerShowing()
return NotificationRenderer::isOverlayBannerShowing();
}
char Screen::customFontTableLookup(const uint8_t ch)
{
static uint8_t LASTCHAR;
static bool SKIPREST;
static int8_t cjkRemaining = 0;
if (cjkRemaining > 0) {
cjkRemaining--;
LASTCHAR = ch;
return (uint8_t)0;
}
if (ch < 128) {
LASTCHAR = 0;
SKIPREST = false;
cjkRemaining = 0;
return ch;
}
uint8_t last = LASTCHAR;
LASTCHAR = ch;
switch (last) {
case 0xC2: {
SKIPREST = false;
return (uint8_t)ch;
}
case 0xC3: {
SKIPREST = false;
return (uint8_t)(ch | 0xC0);
}
}
if (ch == 0xC2 || ch == 0xC3)
return (uint8_t)0;
#if defined(OLED_PL)
switch (last) {
case 0xC3: {
if (ch == 147)
return (uint8_t)(ch);
else if (ch == 179)
return (uint8_t)(148);
else
return (uint8_t)(ch | 0xC0);
break;
}
case 0xC4: {
SKIPREST = false;
return (uint8_t)(ch);
}
case 0xC5: {
SKIPREST = false;
if (ch == 132)
return (uint8_t)(136);
else if (ch == 186)
return (uint8_t)(137);
else
return (uint8_t)(ch);
break;
}
}
if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5)
return (uint8_t)0;
#endif
#if defined(OLED_UA) || defined(OLED_RU)
switch (last) {
case 0xC3: {
SKIPREST = false;
return (uint8_t)(ch | 0xC0);
}
case 0xD0: {
SKIPREST = false;
if (ch == 132)
return (uint8_t)(170);
if (ch == 134)
return (uint8_t)(178);
if (ch == 135)
return (uint8_t)(175);
if (ch == 129)
return (uint8_t)(168);
if (ch > 143 && ch < 192)
return (uint8_t)(ch + 48);
break;
}
case 0xD1: {
SKIPREST = false;
if (ch == 148)
return (uint8_t)(186);
if (ch == 150)
return (uint8_t)(179);
if (ch == 151)
return (uint8_t)(191);
if (ch == 145)
return (uint8_t)(184);
if (ch > 127 && ch < 144)
return (uint8_t)(ch + 112);
break;
}
case 0xD2: {
SKIPREST = false;
if (ch == 144)
return (uint8_t)(165);
if (ch == 145)
return (uint8_t)(180);
break;
}
}
if (ch == 0xC2 || ch == 0xC3 || ch == 0x82 || ch == 0xD0 || ch == 0xD1)
return (uint8_t)0;
#endif
#if defined(OLED_CS)
switch (last) {
case 0xC2: {
SKIPREST = false;
return (uint8_t)ch;
}
case 0xC3: {
SKIPREST = false;
return (uint8_t)(ch | 0xC0);
}
case 0xC4: {
SKIPREST = false;
if (ch == 140)
return (uint8_t)(129);
if (ch == 141)
return (uint8_t)(138);
if (ch == 142)
return (uint8_t)(130);
if (ch == 143)
return (uint8_t)(139);
if (ch == 154)
return (uint8_t)(131);
if (ch == 155)
return (uint8_t)(140);
if (ch == 185)
return (uint8_t)(147);
if (ch == 186)
return (uint8_t)(148);
if (ch == 189)
return (uint8_t)(149);
if (ch == 190)
return (uint8_t)(150);
break;
}
case 0xC5: {
SKIPREST = false;
if (ch == 135)
return (uint8_t)(132);
if (ch == 136)
return (uint8_t)(141);
if (ch == 152)
return (uint8_t)(133);
if (ch == 153)
return (uint8_t)(142);
if (ch == 160)
return (uint8_t)(134);
if (ch == 161)
return (uint8_t)(143);
if (ch == 164)
return (uint8_t)(135);
if (ch == 165)
return (uint8_t)(144);
if (ch == 174)
return (uint8_t)(136);
if (ch == 175)
return (uint8_t)(145);
if (ch == 189)
return (uint8_t)(137);
if (ch == 190)
return (uint8_t)(146);
if (ch == 148)
return (uint8_t)(151);
if (ch == 149)
return (uint8_t)(152);
break;
}
}
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);
}
case 0xCE: {
SKIPREST = false;
if (ch >= 145 && ch <= 161)
return (uint8_t)(ch + 48);
else if (ch >= 163 && ch <= 169)
return (uint8_t)(ch + 48);
else if (ch >= 177 && ch <= 193)
return (uint8_t)(ch + 48);
break;
}
case 0xCF: {
SKIPREST = false;
if (ch >= 130 && ch <= 137)
return (uint8_t)(ch + 112);
break;
}
}
if (ch == 0xC2 || ch == 0xC3 || ch == 0xCE || ch == 0xCF)
return (uint8_t)0;
#endif
if (ch >= 0xE2 && ch <= 0xEF) {
cjkRemaining = 2;
return (uint8_t)0;
}
if (SKIPREST)
return (uint8_t)0;
SKIPREST = true;
return (uint8_t)191;
}
void Screen::drawTextL10n(int16_t x, int16_t y, const char *text, OLEDDISPLAY_TEXT_ALIGNMENT alignment)
{
if (!dispdev || !text)
return;
#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014)
if (meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_SIMPLIFIED_CHINESE ||
meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_TRADITIONAL_CHINESE) {
TFTDisplay *tftDisp = static_cast<TFTDisplay *>(dispdev);
if (tftDisp->hasCJKFont()) {
tftDisp->drawStringCJK(x, y, text);
return;
}
}
#endif
dispdev->setTextAlignment(alignment);
dispdev->drawString(x, y, text);
}
void Screen::setFontL10n(const uint8_t *fontData)
{
if (!dispdev)
return;
dispdev->setFont(fontData);
}
void Screen::loadCJKFontIfNeeded()
{
#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014)
if (meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_SIMPLIFIED_CHINESE ||
meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_TRADITIONAL_CHINESE) {
TFTDisplay *tftDisp = static_cast<TFTDisplay *>(dispdev);
if (!tftDisp->hasCJKFont()) {
tftDisp->loadCJKFont(FontZH_CN_16, FontZH_CN_16_size);
}
}
#endif
}
} // namespace graphics
#else
+5 -247
View File
@@ -366,255 +366,13 @@ class Screen : public concurrency::OSThread
enqueueCmd(ScreenCmd{.cmd = Cmd::NOOP});
}
/// Overrides the default utf8 character conversion, to replace empty space with question marks
static char customFontTableLookup(const uint8_t ch)
{
// UTF-8 to font table index converter
// Code from http://playground.arduino.cc/Main/Utf8ascii
static uint8_t LASTCHAR;
static bool SKIPREST; // Only display a single unconvertable-character symbol per sequence of unconvertable characters
static char customFontTableLookup(const uint8_t ch);
if (ch < 128) { // Standard ASCII-set 0..0x7F handling
LASTCHAR = 0;
SKIPREST = false;
return ch;
}
void drawTextL10n(int16_t x, int16_t y, const char *text, OLEDDISPLAY_TEXT_ALIGNMENT alignment = TEXT_ALIGN_LEFT);
void setFontL10n(const uint8_t *fontData);
uint8_t last = LASTCHAR; // get last char
LASTCHAR = ch;
switch (last) {
case 0xC2: {
SKIPREST = false;
return (uint8_t)ch;
}
case 0xC3: {
SKIPREST = false;
return (uint8_t)(ch | 0xC0);
}
}
// We want to strip out prefix chars for two-byte char formats
if (ch == 0xC2 || ch == 0xC3)
return (uint8_t)0;
#if defined(OLED_PL)
switch (last) {
case 0xC3: {
if (ch == 147)
return (uint8_t)(ch); // Ó
else if (ch == 179)
return (uint8_t)(148); // ó
else
return (uint8_t)(ch | 0xC0);
break;
}
case 0xC4: {
SKIPREST = false;
return (uint8_t)(ch);
}
case 0xC5: {
SKIPREST = false;
if (ch == 132)
return (uint8_t)(136); // ń
else if (ch == 186)
return (uint8_t)(137); // ź
else
return (uint8_t)(ch);
break;
}
}
// We want to strip out prefix chars for two-byte char formats
if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5)
return (uint8_t)0;
#endif
#if defined(OLED_UA) || defined(OLED_RU)
switch (last) {
case 0xC3: {
SKIPREST = false;
return (uint8_t)(ch | 0xC0);
}
// map UTF-8 cyrillic chars to it Windows-1251 (CP-1251) ASCII codes
// note: in this case we must use compatible font - provided ArialMT_Plain_10/16/24 by 'ThingPulse/esp8266-oled-ssd1306'
// library have empty chars for non-latin ASCII symbols
case 0xD0: {
SKIPREST = false;
if (ch == 132)
return (uint8_t)(170); // Є
if (ch == 134)
return (uint8_t)(178); // І
if (ch == 135)
return (uint8_t)(175); // Ї
if (ch == 129)
return (uint8_t)(168); // Ё
if (ch > 143 && ch < 192)
return (uint8_t)(ch + 48);
break;
}
case 0xD1: {
SKIPREST = false;
if (ch == 148)
return (uint8_t)(186); // є
if (ch == 150)
return (uint8_t)(179); // і
if (ch == 151)
return (uint8_t)(191); // ї
if (ch == 145)
return (uint8_t)(184); // ё
if (ch > 127 && ch < 144)
return (uint8_t)(ch + 112);
break;
}
case 0xD2: {
SKIPREST = false;
if (ch == 144)
return (uint8_t)(165); // Ґ
if (ch == 145)
return (uint8_t)(180); // ґ
break;
}
}
// We want to strip out prefix chars for two-byte char formats
if (ch == 0xC2 || ch == 0xC3 || ch == 0x82 || ch == 0xD0 || ch == 0xD1)
return (uint8_t)0;
#endif
#if defined(OLED_CS)
switch (last) {
case 0xC2: {
SKIPREST = false;
return (uint8_t)ch;
}
case 0xC3: {
SKIPREST = false;
return (uint8_t)(ch | 0xC0);
}
case 0xC4: {
SKIPREST = false;
if (ch == 140)
return (uint8_t)(129); // Č
if (ch == 141)
return (uint8_t)(138); // č
if (ch == 142)
return (uint8_t)(130); // Ď
if (ch == 143)
return (uint8_t)(139); // ď
if (ch == 154)
return (uint8_t)(131); // Ě
if (ch == 155)
return (uint8_t)(140); // ě
// Slovak specific glyphs
if (ch == 185)
return (uint8_t)(147); // Ĺ
if (ch == 186)
return (uint8_t)(148); // ĺ
if (ch == 189)
return (uint8_t)(149); // Ľ
if (ch == 190)
return (uint8_t)(150); // ľ
break;
}
case 0xC5: {
SKIPREST = false;
if (ch == 135)
return (uint8_t)(132); // Ň
if (ch == 136)
return (uint8_t)(141); // ň
if (ch == 152)
return (uint8_t)(133); // Ř
if (ch == 153)
return (uint8_t)(142); // ř
if (ch == 160)
return (uint8_t)(134); // Š
if (ch == 161)
return (uint8_t)(143); // š
if (ch == 164)
return (uint8_t)(135); // Ť
if (ch == 165)
return (uint8_t)(144); // ť
if (ch == 174)
return (uint8_t)(136); // Ů
if (ch == 175)
return (uint8_t)(145); // ů
if (ch == 189)
return (uint8_t)(137); // Ž
if (ch == 190)
return (uint8_t)(146); // ž
// Slovak specific glyphs
if (ch == 148)
return (uint8_t)(151); // Ŕ
if (ch == 149)
return (uint8_t)(152); // ŕ
break;
}
}
// We want to strip out prefix chars for two-byte char formats
if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5)
return (uint8_t)0;
#endif
#if 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
// rest of it
if (SKIPREST)
return (uint8_t)0;
SKIPREST = true;
return (uint8_t)191; // otherwise: return ¿ if character can't be converted (note that the font map we're using doesn't
// stick to standard EASCII codes)
}
// Load CJK font if language requires it (called from setup & on language change)
void loadCJKFontIfNeeded();
/// Returns a handle to the DebugInfo screen.
//
+4
View File
@@ -20,6 +20,10 @@
#include "graphics/fonts/OLEDDisplayFontsGR.h"
#endif
#ifdef OLED_ZH
#include "graphics/fonts/OLEDDisplayFontsZH.h"
#endif
#if (defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(T5_S3_EPAPER_PRO)) && defined(USE_EINK)
#include "graphics/fonts/EinkDisplayFonts.h"
#endif
+31
View File
@@ -1636,4 +1636,35 @@ bool TFTDisplay::connect()
return true;
}
bool TFTDisplay::loadCJKFont(const uint8_t *vlwData, uint32_t vlwDataLen)
{
if (!tft || !vlwData || vlwDataLen == 0)
return false;
cjkFontLoaded = tft->loadFont(vlwData);
if (cjkFontLoaded) {
LOG_INFO("CJK font loaded (%u bytes)", vlwDataLen);
} else {
LOG_WARN("Failed to load CJK font");
}
return cjkFontLoaded;
}
void TFTDisplay::drawStringCJK(int16_t x, int16_t y, const char *text)
{
if (!tft || !text || !cjkFontLoaded)
return;
concurrency::LockGuard g(spiLock);
uint16_t fgColor = getThemeDefaultOnColor();
tft->setTextColor(fgColor, getThemeDefaultOffColor());
tft->setCursor(x, y);
tft->print(text);
}
int16_t TFTDisplay::getStringWidthCJK(const char *text)
{
if (!tft || !text || !cjkFontLoaded)
return 0;
return tft->textWidth(text);
}
#endif // USE_TFTDISPLAY
+7
View File
@@ -38,6 +38,12 @@ class TFTDisplay : public OLEDDisplay
// Functions for changing display brightness
void setDisplayBrightness(uint8_t);
// CJK (Chinese/Japanese/Korean) text rendering via LovyanGFX
bool loadCJKFont(const uint8_t *vlwData, uint32_t vlwDataLen);
void drawStringCJK(int16_t x, int16_t y, const char *text);
int16_t getStringWidthCJK(const char *text);
bool hasCJKFont() const { return cjkFontLoaded; }
/**
* shim to make the abstraction happy
*
@@ -64,4 +70,5 @@ class TFTDisplay : public OLEDDisplay
uint16_t *linePixelBuffer = nullptr;
uint16_t *repaintChunkBuffer = nullptr;
bool cjkFontLoaded = false;
};
+288 -132
View File
@@ -13,6 +13,7 @@
#include "graphics/Screen.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/TFTColorRegions.h"
#include "graphics/l10n/Strings.h"
#include "graphics/draw/MessageRenderer.h"
#include "graphics/draw/UIRenderer.h"
#include "input/RotaryEncoderInterruptImpl1.h"
@@ -35,6 +36,9 @@
namespace graphics
{
using namespace meshtastic::l10n;
using meshtastic::l10n::StringKey;
namespace
{
@@ -132,10 +136,15 @@ uint8_t test_count = 0;
void menuHandler::loraMenu()
{
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"};
static const char *optionsArray[5];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = getString(StringKey::DeviceRole);
optionsArray[2] = getString(StringKey::RadioPreset);
optionsArray[3] = getString(StringKey::FrequencySlot);
optionsArray[4] = getString(StringKey::LoRaRegion);
enum optionsNumbers { Back = 0, DeviceRolePicker = 1, RadioPresetPicker = 2, FrequencySlot = 3, LoraPicker = 4 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "LoRa Actions";
bannerOptions.message = getString(StringKey::LoRaActions);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -156,7 +165,9 @@ void menuHandler::loraMenu()
void menuHandler::OnboardMessage()
{
static const char *optionsArray[] = {"OK", "Got it!"};
static const char *optionsArray[2];
optionsArray[0] = getString(StringKey::OK);
optionsArray[1] = getString(StringKey::GotIt);
enum optionsNumbers { OK, got };
BannerOverlayOptions bannerOptions;
#if HAS_TFT
@@ -220,7 +231,7 @@ static void applyLoraRegion(meshtastic_Config_LoRaConfig_RegionCode region, bool
void menuHandler::LoraRegionPicker(uint32_t duration)
{
static const LoraRegionOption regionOptions[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"US", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_US},
{"EU_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_433},
{"EU_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_868},
@@ -261,7 +272,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
const char *bannerMessage = "Set the LoRa region";
if (currentResolution == ScreenResolution::UltraLow) {
bannerMessage = "LoRa Region";
bannerMessage = getString(StringKey::LoRaRegion);
}
auto bannerOptions =
@@ -353,7 +364,12 @@ void menuHandler::licensedToNormalConfirmMenu()
void menuHandler::deviceRolePicker()
{
static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"};
static const char *optionsArray[5];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = getString(StringKey::Client);
optionsArray[2] = getString(StringKey::ClientMute);
optionsArray[3] = getString(StringKey::LostAndFound);
optionsArray[4] = getString(StringKey::Tracker);
enum optionsNumbers {
Back = 0,
devicerole_client = 1,
@@ -362,7 +378,7 @@ void menuHandler::deviceRolePicker()
devicerole_tracker = 4
};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Device Role";
bannerOptions.message = getString(StringKey::DeviceRole);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -394,9 +410,9 @@ void menuHandler::FrequencySlotPicker()
static int optionsEnumArray[MAX_CHANNEL_OPTIONS];
static char channelText[MAX_CHANNEL_OPTIONS - 1][12];
int options = 0;
optionsArray[options] = "Back";
optionsArray[options] = getString(StringKey::Back);
optionsEnumArray[options++] = Back;
optionsArray[options] = "Slot 0 (Auto)";
optionsArray[options] = getString(StringKey::Slot0);
optionsEnumArray[options++] = 0;
// Calculate number of channels (copied from RadioInterface::applyModemConfig())
@@ -433,7 +449,7 @@ void menuHandler::FrequencySlotPicker()
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Frequency Slot";
bannerOptions.message = getString(StringKey::FrequencySlot);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -469,7 +485,7 @@ static BannerOverlayOptions buildRegionPresetBanner()
static char presetLabelBuf[MAX_PRESET_OPTIONS][12]; // scratch space for name copies
int count = 0;
optionsArray[count] = "Back";
optionsArray[count] = getString(StringKey::Back);
optionsEnumArray[count++] = -1;
if (myRegion && myRegion->profile) {
@@ -493,7 +509,7 @@ static BannerOverlayOptions buildRegionPresetBanner()
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Radio Preset";
bannerOptions.message = getString(StringKey::RadioPreset);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = static_cast<uint8_t>(count);
@@ -520,10 +536,13 @@ void menuHandler::radioPresetPicker()
void menuHandler::twelveHourPicker()
{
static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
static const char *optionsArray[3];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = getString(StringKey::Hour12);
optionsArray[2] = getString(StringKey::Hour24);
enum optionsNumbers { Back = 0, twelve = 1, twentyfour = 2 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Time Format";
bannerOptions.message = getString(StringKey::TimeFormat);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -559,7 +578,7 @@ void menuHandler::showConfirmationBanner(const char *message, std::function<void
void menuHandler::clockFacePicker()
{
static const ClockFaceOption clockFaceOptions[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"Digital", OptionsAction::Select, false},
{"Analog", OptionsAction::Select, true},
};
@@ -595,7 +614,7 @@ void menuHandler::clockFacePicker()
void menuHandler::TZPicker()
{
static const TimezoneOption timezoneOptions[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"US/Hawaii", OptionsAction::Select, "HST10"},
{"US/Alaska", OptionsAction::Select, "AKST9AKDT,M3.2.0,M11.1.0"},
{"US/Pacific", OptionsAction::Select, "PST8PDT,M3.2.0,M11.1.0"},
@@ -658,13 +677,20 @@ void menuHandler::TZPicker()
void menuHandler::clockMenu()
{
#if defined(OLED_TINY)
static const char *optionsArray[] = {"Back", "Time Format", "Timezone"};
static const char *optionsArray[3];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = getString(StringKey::TimeFormat);
optionsArray[2] = getString(StringKey::Timezone);
#else
static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
static const char *optionsArray[4];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = getString(StringKey::ClockFace);
optionsArray[2] = getString(StringKey::TimeFormat);
optionsArray[3] = getString(StringKey::Timezone);
#endif
enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Clock Action";
bannerOptions.message = getString(StringKey::ClockAction);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -692,14 +718,14 @@ void menuHandler::messageResponseMenu()
auto mode = graphics::MessageRenderer::getThreadMode();
int threadChannel = graphics::MessageRenderer::getThreadChannel();
optionsArray[options] = "Back";
optionsArray[options] = getString(StringKey::Back);
optionsEnumArray[options++] = Back;
// New Reply submenu (replaces Preset and Freetext directly in this menu)
optionsArray[options] = "Reply";
optionsArray[options] = getString(StringKey::Reply);
optionsEnumArray[options++] = ReplyMenu;
optionsArray[options] = "View Chats";
optionsArray[options] = getString(StringKey::ViewChats);
optionsEnumArray[options++] = ViewMode;
// If viewing ALL chats, hide “Mute Chat”
@@ -707,21 +733,21 @@ void menuHandler::messageResponseMenu()
const uint8_t chIndex = (threadChannel != 0) ? (uint8_t)threadChannel : channels.getPrimaryIndex();
const auto &chan = channels.getByIndex(chIndex);
optionsArray[options] = chan.settings.module_settings.is_muted ? "Unmute Channel" : "Mute Channel";
optionsArray[options] = chan.settings.module_settings.is_muted ? getString(StringKey::UnmuteChannel) : getString(StringKey::MuteChannel);
optionsEnumArray[options++] = MuteChannel;
}
// Delete submenu
optionsArray[options] = "Delete";
optionsArray[options] = getString(StringKey::Delete);
optionsEnumArray[options++] = DeleteMenu;
#ifdef HAS_I2S
optionsArray[options] = "Read Aloud";
optionsArray[options] = getString(StringKey::ReadAloud);
optionsEnumArray[options++] = Aloud;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action";
bannerOptions.message = getString(StringKey::MessageAction);
if (currentResolution == ScreenResolution::UltraLow) {
bannerOptions.message = "Message";
}
@@ -781,16 +807,16 @@ void menuHandler::replyMenu()
int options = 0;
// Back
optionsArray[options] = "Back";
optionsArray[options] = getString(StringKey::Back);
optionsEnumArray[options++] = Back;
// Preset reply
optionsArray[options] = "With Preset";
optionsArray[options] = getString(StringKey::WithPreset);
optionsEnumArray[options++] = ReplyPreset;
// Freetext reply (only when keyboard exists)
if (kb_found) {
optionsArray[options] = "With Freetext";
optionsArray[options] = getString(StringKey::WithFreetext);
optionsEnumArray[options++] = ReplyFreetext;
}
@@ -861,21 +887,21 @@ void menuHandler::deleteMessagesMenu()
auto mode = graphics::MessageRenderer::getThreadMode();
optionsArray[options] = "Back";
optionsArray[options] = getString(StringKey::Back);
optionsEnumArray[options++] = Back;
optionsArray[options] = "Delete Oldest";
optionsArray[options] = getString(StringKey::DeleteOldest);
optionsEnumArray[options++] = DeleteOldest;
// If viewing ALL chats → hide “Delete This Chat”
if (mode != graphics::MessageRenderer::ThreadMode::ALL) {
optionsArray[options] = "Delete This Chat";
optionsArray[options] = getString(StringKey::DeleteThisChat);
optionsEnumArray[options++] = DeleteThis;
}
if (currentResolution == ScreenResolution::UltraLow) {
optionsArray[options] = "Delete All";
optionsArray[options] = getString(StringKey::DeleteAll);
} else {
optionsArray[options] = "Delete All Chats";
optionsArray[options] = getString(StringKey::DeleteAllChats);
}
optionsEnumArray[options++] = DeleteAll;
@@ -945,7 +971,7 @@ void menuHandler::messageViewModeMenu()
ids.clear();
idToPeer.clear();
labels.push_back("Back");
labels.push_back(getString(StringKey::Back));
ids.push_back(-1);
labels.push_back("View All Chats");
ids.push_back(-2);
@@ -1076,30 +1102,31 @@ void menuHandler::homeBaseMenu()
{
enum optionsNumbers { Back, Mute, Backlight, Position, Preset, Freetext, Sleep, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static const char *optionsArray[enumEnd];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
if (moduleConfig.external_notification.enabled && externalNotificationModule &&
config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED) {
if (!externalNotificationModule->getMute()) {
optionsArray[options] = "Temporarily Mute";
optionsArray[options] = getString(StringKey::TemporarilyMute);
} else {
optionsArray[options] = "Unmute";
optionsArray[options] = getString(StringKey::Unmute);
}
optionsEnumArray[options++] = Mute;
}
#if defined(PIN_EINK_EN) || defined(PCA_PIN_EINK_EN)
optionsArray[options] = "Toggle Backlight";
optionsArray[options] = getString(StringKey::ToggleBacklight);
optionsEnumArray[options++] = Backlight;
#else
optionsArray[options] = "Sleep Screen";
optionsArray[options] = getString(StringKey::SleepScreen);
optionsEnumArray[options++] = Sleep;
#endif
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
optionsArray[options] = "Send Position";
optionsArray[options] = getString(StringKey::SendPosition);
} else {
optionsArray[options] = "Send Node Info";
optionsArray[options] = getString(StringKey::SendNodeInfo);
}
optionsEnumArray[options++] = Position;
@@ -1165,18 +1192,19 @@ void menuHandler::textMessageBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static const char *optionsArray[enumEnd];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "New Preset Msg";
optionsArray[options] = getString(StringKey::NewPresetMsg);
optionsEnumArray[options++] = Preset;
if (kb_found) {
optionsArray[options] = "New Freetext Msg";
optionsArray[options] = getString(StringKey::NewFreetextMsg);
optionsEnumArray[options++] = Freetext;
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action";
bannerOptions.message = getString(StringKey::MessageAction);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -1193,36 +1221,37 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu()
{
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, WiFiToggle, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static const char *optionsArray[enumEnd];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "Notifications";
optionsArray[options] = getString(StringKey::Notifications);
optionsEnumArray[options++] = Notifications;
optionsArray[options] = "Display Options";
optionsArray[options] = getString(StringKey::DisplayOptions);
optionsEnumArray[options++] = ScreenOptions;
if (currentResolution == ScreenResolution::UltraLow) {
optionsArray[options] = "Bluetooth";
optionsArray[options] = getString(StringKey::Bluetooth);
} else {
optionsArray[options] = "Bluetooth Toggle";
optionsArray[options] = getString(StringKey::BluetoothToggle);
}
optionsEnumArray[options++] = Bluetooth;
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
optionsArray[options] = "WiFi Toggle";
optionsArray[options] = getString(StringKey::WifiToggle);
optionsEnumArray[options++] = WiFiToggle;
#endif
if (currentResolution == ScreenResolution::UltraLow) {
optionsArray[options] = "Power";
optionsArray[options] = getString(StringKey::Power);
} else {
optionsArray[options] = "Reboot/Shutdown";
optionsArray[options] = getString(StringKey::RebootShutdown);
}
optionsEnumArray[options++] = PowerMenu;
if (test_enabled) {
optionsArray[options] = "Test Menu";
optionsArray[options] = getString(StringKey::TestMenu);
optionsEnumArray[options++] = Test;
}
@@ -1269,7 +1298,8 @@ void menuHandler::favoriteBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, GoToChat, Remove, TraceRoute, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static const char *optionsArray[enumEnd];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
@@ -1283,26 +1313,26 @@ void menuHandler::favoriteBaseMenu()
}
}
if (hasConversation) {
optionsArray[options] = "Go To Chat";
optionsArray[options] = getString(StringKey::GoToChat);
optionsEnumArray[options++] = GoToChat;
}
if (currentResolution == ScreenResolution::UltraLow) {
optionsArray[options] = "New Preset";
optionsArray[options] = getString(StringKey::NewPreset);
} else {
optionsArray[options] = "New Preset Msg";
optionsArray[options] = getString(StringKey::NewPresetMsg);
}
optionsEnumArray[options++] = Preset;
if (kb_found) {
optionsArray[options] = "New Freetext Msg";
optionsArray[options] = getString(StringKey::NewFreetextMsg);
optionsEnumArray[options++] = Freetext;
}
if (currentResolution != ScreenResolution::UltraLow) {
optionsArray[options] = "Trace Route";
optionsArray[options] = getString(StringKey::TraceRoute);
optionsEnumArray[options++] = TraceRoute;
}
optionsArray[options] = "Remove Favorite";
optionsArray[options] = getString(StringKey::RemoveFavorite);
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions;
@@ -1354,7 +1384,7 @@ void menuHandler::positionBaseMenu()
};
static const PositionMenuOption baseOptions[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"On/Off Toggle", OptionsAction::Select, static_cast<int>(PositionAction::GpsToggle)},
{"Format", OptionsAction::Select, static_cast<int>(PositionAction::GpsFormat)},
{"Smart Position", OptionsAction::Select, static_cast<int>(PositionAction::GPSSmartPosition)},
@@ -1364,7 +1394,7 @@ void menuHandler::positionBaseMenu()
};
static const PositionMenuOption calibrateOptions[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"On/Off Toggle", OptionsAction::Select, static_cast<int>(PositionAction::GpsToggle)},
{"Format", OptionsAction::Select, static_cast<int>(PositionAction::GpsFormat)},
{"Smart Position", OptionsAction::Select, static_cast<int>(PositionAction::GPSSmartPosition)},
@@ -1448,18 +1478,19 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, NodePicker, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static const char *optionsArray[enumEnd];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "Node Actions / Settings";
optionsArray[options] = getString(StringKey::NodeActionsSettings);
optionsEnumArray[options++] = NodePicker;
if (currentResolution != ScreenResolution::UltraLow) {
optionsArray[options] = "Show Long/Short Name";
optionsArray[options] = getString(StringKey::ShowNameLength);
optionsEnumArray[options++] = NodeNameLength;
}
optionsArray[options] = "Reset NodeDB";
optionsArray[options] = getString(StringKey::NodeDbReset);
optionsEnumArray[options++] = Reset;
BannerOverlayOptions bannerOptions;
@@ -1509,35 +1540,36 @@ void menuHandler::manageNodeMenu()
return;
}
enum optionsNumbers { Back, Favorite, Mute, TraceRoute, KeyVerification, Ignore, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static const char *optionsArray[enumEnd];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
if (nodeInfoLiteIsFavorite(node)) {
optionsArray[options] = "Unfavorite";
optionsArray[options] = getString(StringKey::RemoveFavorite);
} else {
optionsArray[options] = "Favorite";
optionsArray[options] = getString(StringKey::Favorite);
}
optionsEnumArray[options++] = Favorite;
bool isMuted = nodeInfoLiteIsMuted(node);
if (isMuted) {
optionsArray[options] = "Unmute Notifications";
optionsArray[options] = getString(StringKey::UnmuteNotifications);
} else {
optionsArray[options] = "Mute Notifications";
optionsArray[options] = getString(StringKey::MuteNotifications);
}
optionsEnumArray[options++] = Mute;
optionsArray[options] = "Trace Route";
optionsArray[options] = getString(StringKey::TraceRoute);
optionsEnumArray[options++] = TraceRoute;
optionsArray[options] = "Key Verification";
optionsArray[options] = getString(StringKey::KeyVerification);
optionsEnumArray[options++] = KeyVerification;
if (nodeInfoLiteIsIgnored(node)) {
optionsArray[options] = "Unignore Node";
optionsArray[options] = getString(StringKey::UnignoreNode);
} else {
optionsArray[options] = "Ignore Node";
optionsArray[options] = getString(StringKey::IgnoreNode);
}
optionsEnumArray[options++] = Ignore;
@@ -1643,7 +1675,7 @@ void menuHandler::manageNodeMenu()
void menuHandler::nodeNameLengthMenu()
{
static const NodeNameOption nodeNameOptions[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"Long", OptionsAction::Select, true},
{"Short", OptionsAction::Select, false},
};
@@ -1681,7 +1713,10 @@ void menuHandler::nodeNameLengthMenu()
void menuHandler::resetNodeDBMenu()
{
static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"};
static const char *optionsArray[3];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "Reset All";
optionsArray[2] = "Preserve Favorites";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Confirm Reset NodeDB";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -1710,7 +1745,7 @@ void menuHandler::resetNodeDBMenu()
void menuHandler::compassNorthMenu()
{
static const CompassOption compassOptions[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"Dynamic", OptionsAction::Select, meshtastic_CompassMode_DYNAMIC},
{"Fixed Ring", OptionsAction::Select, meshtastic_CompassMode_FIXED_RING},
{"Freeze Heading", OptionsAction::Select, meshtastic_CompassMode_FREEZE_HEADING},
@@ -1756,7 +1791,7 @@ void menuHandler::compassNorthMenu()
void menuHandler::GPSToggleMenu()
{
static const GPSToggleOption gpsToggleOptions[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"Enabled", OptionsAction::Select, meshtastic_Config_PositionConfig_GpsMode_ENABLED},
{"Disabled", OptionsAction::Select, meshtastic_Config_PositionConfig_GpsMode_DISABLED},
};
@@ -1805,7 +1840,7 @@ void menuHandler::GPSToggleMenu()
void menuHandler::GPSFormatMenu()
{
static const GPSFormatOption formatOptionsHigh[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"Decimal Degrees", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC},
{"Degrees Minutes Seconds", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS},
{"Universal Transverse Mercator", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM},
@@ -1816,7 +1851,7 @@ void menuHandler::GPSFormatMenu()
};
static const GPSFormatOption formatOptionsLow[] = {
{"Back", OptionsAction::Back},
{getString(StringKey::Back), OptionsAction::Back},
{"DEC", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC},
{"DMS", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS},
{"UTM", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM},
@@ -1854,7 +1889,7 @@ void menuHandler::GPSFormatMenu()
int initialSelection = 0;
if (currentResolution == ScreenResolution::High) {
bannerOptions = createStaticBannerOptions("GPS Format", formatOptionsHigh, formatLabelsHigh, onSelection);
bannerOptions = createStaticBannerOptions(getString(StringKey::GPSFormat), formatOptionsHigh, formatLabelsHigh, onSelection);
for (size_t i = 0; i < formatCount; ++i) {
if (formatOptionsHigh[i].hasValue && uiconfig.gps_format == formatOptionsHigh[i].value) {
initialSelection = static_cast<int>(i);
@@ -1862,7 +1897,7 @@ void menuHandler::GPSFormatMenu()
}
}
} else {
bannerOptions = createStaticBannerOptions("GPS Format", formatOptionsLow, formatLabelsLow, onSelection);
bannerOptions = createStaticBannerOptions(getString(StringKey::GPSFormat), formatOptionsLow, formatLabelsLow, onSelection);
for (size_t i = 0; i < formatCount; ++i) {
if (formatOptionsLow[i].hasValue && uiconfig.gps_format == formatOptionsLow[i].value) {
initialSelection = static_cast<int>(i);
@@ -1877,7 +1912,10 @@ void menuHandler::GPSFormatMenu()
void menuHandler::GPSSmartPositionMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
static const char *optionsArray[3];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "Enabled";
optionsArray[2] = "Disabled";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle Smart Position";
if (currentResolution == ScreenResolution::UltraLow) {
@@ -1907,9 +1945,23 @@ void menuHandler::GPSSmartPositionMenu()
void menuHandler::GPSUpdateIntervalMenu()
{
static const char *optionsArray[] = {"Back", "8 seconds", "20 seconds", "40 seconds", "1 minute", "80 seconds",
"2 minutes", "5 minutes", "10 minutes", "15 minutes", "30 minutes", "1 hour",
"6 hours", "12 hours", "24 hours", "At Boot Only"};
static const char *optionsArray[16];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "8 seconds";
optionsArray[2] = "20 seconds";
optionsArray[3] = "40 seconds";
optionsArray[4] = "1 minute";
optionsArray[5] = "80 seconds";
optionsArray[6] = "2 minutes";
optionsArray[7] = "5 minutes";
optionsArray[8] = "10 minutes";
optionsArray[9] = "15 minutes";
optionsArray[10] = "30 minutes";
optionsArray[11] = "1 hour";
optionsArray[12] = "6 hours";
optionsArray[13] = "12 hours";
optionsArray[14] = "24 hours";
optionsArray[15] = "At Boot Only";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Update Interval";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -1995,9 +2047,24 @@ void menuHandler::GPSUpdateIntervalMenu()
void menuHandler::GPSPositionBroadcastMenu()
{
static const char *optionsArray[] = {"Back", "1 minute", "90 seconds", "5 minutes", "15 minutes", "1 hour",
"2 hours", "3 hours", "4 hours", "5 hours", "6 hours", "12 hours",
"18 hours", "24 hours", "36 hours", "48 hours", "72 hours"};
static const char *optionsArray[17];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "1 minute";
optionsArray[2] = "90 seconds";
optionsArray[3] = "5 minutes";
optionsArray[4] = "15 minutes";
optionsArray[5] = "1 hour";
optionsArray[6] = "2 hours";
optionsArray[7] = "3 hours";
optionsArray[8] = "4 hours";
optionsArray[9] = "5 hours";
optionsArray[10] = "6 hours";
optionsArray[11] = "12 hours";
optionsArray[12] = "18 hours";
optionsArray[13] = "24 hours";
optionsArray[14] = "36 hours";
optionsArray[15] = "48 hours";
optionsArray[16] = "72 hours";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Broadcast Interval";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -2089,11 +2156,14 @@ void menuHandler::GPSPositionBroadcastMenu()
void menuHandler::bluetoothToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
static const char *optionsArray[3];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "Enabled";
optionsArray[2] = "Disabled";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle Bluetooth";
if (currentResolution == ScreenResolution::UltraLow) {
bannerOptions.message = "Bluetooth";
bannerOptions.message = getString(StringKey::Bluetooth);
}
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
@@ -2111,7 +2181,12 @@ void menuHandler::bluetoothToggleMenu()
void menuHandler::BuzzerModeMenu()
{
static const char *optionsArray[] = {"All Enabled", "All Disabled", "Notifications", "System Only", "DMs Only"};
static const char *optionsArray[5];
optionsArray[0] = "All Enabled";
optionsArray[1] = "All Disabled";
optionsArray[2] = getString(StringKey::Notifications);
optionsArray[3] = "System Only";
optionsArray[4] = "DMs Only";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Notification Sounds";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -2126,7 +2201,11 @@ void menuHandler::BuzzerModeMenu()
void menuHandler::BrightnessPickerMenu()
{
static const char *optionsArray[] = {"Back", "Low", "Medium", "High"};
static const char *optionsArray[4];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "Low";
optionsArray[2] = "Medium";
optionsArray[3] = "High";
// Get current brightness level to set initial selection
int currentSelection = 1; // Default to Medium
@@ -2139,7 +2218,7 @@ void menuHandler::BrightnessPickerMenu()
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Brightness";
bannerOptions.message = getString(StringKey::Brightness);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -2151,7 +2230,7 @@ void menuHandler::BrightnessPickerMenu()
uiconfig.screen_brightness = 255;
}
if (selected != 0) { // Not "Back"
if (selected != 0) { // Not getString(StringKey::Back)
// Apply brightness immediately
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
// For HELTEC devices, use analogWrite to control backlight
@@ -2174,7 +2253,9 @@ void menuHandler::BrightnessPickerMenu()
void menuHandler::switchToMUIMenu()
{
static const char *optionsArray[] = {"No", "Yes"};
static const char *optionsArray[2];
optionsArray[0] = "No";
optionsArray[1] = "Yes";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Switch to MUI?";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -2192,11 +2273,13 @@ void menuHandler::switchToMUIMenu()
void menuHandler::rebootMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
static const char *optionsArray[2];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "Confirm";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Reboot Device?";
if (currentResolution == ScreenResolution::UltraLow) {
bannerOptions.message = "Reboot";
bannerOptions.message = getString(StringKey::Reboot);
}
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
@@ -2216,11 +2299,13 @@ void menuHandler::rebootMenu()
void menuHandler::shutdownMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
static const char *optionsArray[2];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "Confirm";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Shutdown Device?";
if (currentResolution == ScreenResolution::UltraLow) {
bannerOptions.message = "Shutdown";
bannerOptions.message = getString(StringKey::Shutdown);
}
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
@@ -2239,7 +2324,9 @@ void menuHandler::shutdownMenu()
void menuHandler::removeFavoriteMenu()
{
static const char *optionsArray[] = {"Back", "Yes"};
static const char *optionsArray[2];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "Yes";
BannerOverlayOptions bannerOptions;
std::string message = "Unfavorite This Node?\n";
const auto *node = nodeDB->getMeshNode(graphics::UIRenderer::currentFavoriteNodeNum);
@@ -2273,17 +2360,18 @@ void menuHandler::testMenu()
{
enum optionsNumbers { Back, NumberPicker, ShowChirpy, TestAnnounce };
static const char *optionsArray[5] = {"Back"};
static const char *optionsArray[5];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[5] = {Back};
int options = 1;
optionsArray[options] = "Number Picker";
optionsArray[options] = getString(StringKey::NumberPicker);
optionsEnumArray[options++] = NumberPicker;
optionsArray[options] = screen->isFrameHidden("chirpy") ? "Show Chirpy" : "Hide Chirpy";
optionsEnumArray[options++] = ShowChirpy;
#ifdef HAS_I2S
optionsArray[options] = "Test Announce";
optionsArray[options] = getString(StringKey::TestAnnounce);
optionsEnumArray[options++] = TestAnnounce;
#endif
@@ -2322,7 +2410,9 @@ void menuHandler::wifiBaseMenu()
{
enum optionsNumbers { Back, Wifi_toggle };
static const char *optionsArray[] = {"Back", "WiFi Toggle"};
static const char *optionsArray[2];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = getString(StringKey::WifiToggle);
BannerOverlayOptions bannerOptions;
bannerOptions.message = "WiFi Menu";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -2340,7 +2430,10 @@ void menuHandler::wifiToggleMenu()
{
enum optionsNumbers { Back, Wifi_disable, Wifi_enable };
static const char *optionsArray[] = {"Back", "WiFi Disabled", "WiFi Enabled"};
static const char *optionsArray[3];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "WiFi Disabled";
optionsArray[2] = "WiFi Enabled";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "WiFi Actions";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -2377,33 +2470,40 @@ void menuHandler::screenOptionsMenu()
bool hasSupportBrightness = false;
#endif
enum optionsNumbers { Back, Brightness, FrameToggles, DisplayUnits, MessageBubbles, Theme };
static const char *optionsArray[7] = {"Back"};
static int optionsEnumArray[7] = {Back};
enum optionsNumbers { Back, Brightness, FrameToggles, DisplayUnits, MessageBubbles, Theme, LangPicker };
static const char *optionsArray[8];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[8] = {Back};
int options = 1;
// Only show brightness for B&W displays
if (hasSupportBrightness) {
optionsArray[options] = "Brightness";
optionsArray[options] = getString(StringKey::Brightness);
optionsEnumArray[options++] = Brightness;
}
optionsArray[options] = "Frame Visibility";
optionsArray[options] = getString(StringKey::FrameVisibility);
optionsEnumArray[options++] = FrameToggles;
optionsArray[options] = "Display Units";
optionsArray[options] = getString(StringKey::DisplayUnits);
optionsEnumArray[options++] = DisplayUnits;
optionsArray[options] = "Message Bubbles";
optionsArray[options] = getString(StringKey::MessageBubbles);
optionsEnumArray[options++] = MessageBubbles;
#if GRAPHICS_TFT_COLORING_ENABLED
optionsArray[options] = "Theme";
optionsArray[options] = getString(StringKey::Theme);
optionsEnumArray[options++] = Theme;
#endif
#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014)
optionsArray[options] = getString(StringKey::Language);
optionsEnumArray[options++] = LangPicker;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Display Options";
bannerOptions.message = getString(StringKey::DisplayOptions);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -2423,6 +2523,9 @@ void menuHandler::screenOptionsMenu()
} else if (selected == Theme) {
menuHandler::menuQueue = menuHandler::ThemeMenu;
screen->runNow();
} else if (selected == LangPicker) {
menuHandler::menuQueue = menuHandler::LanguagePicker;
screen->runNow();
} else {
menuQueue = SystemBaseMenu;
screen->runNow();
@@ -2435,25 +2538,26 @@ void menuHandler::powerMenu()
{
enum optionsNumbers { Back, Reboot, Shutdown, MUI };
static const char *optionsArray[4] = {"Back"};
static const char *optionsArray[4];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[4] = {Back};
int options = 1;
optionsArray[options] = "Reboot";
optionsArray[options] = getString(StringKey::Reboot);
optionsEnumArray[options++] = Reboot;
optionsArray[options] = "Shutdown";
optionsArray[options] = getString(StringKey::Shutdown);
optionsEnumArray[options++] = Shutdown;
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsArray[options] = getString(StringKey::SwitchToMUI);
optionsEnumArray[options++] = MUI;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Reboot / Shutdown";
if (currentResolution == ScreenResolution::UltraLow) {
bannerOptions.message = "Power";
bannerOptions.message = getString(StringKey::Power);
}
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
@@ -2490,7 +2594,9 @@ void menuHandler::keyVerificationFinalPrompt()
keyVerificationModule->generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
static const char *optionsArray[] = {"Reject", "Accept"};
static const char *optionsArray[2];
optionsArray[0] = "Reject";
optionsArray[1] = "Accept";
graphics::BannerOverlayOptions options;
options.message = message;
options.durationMs = 30000;
@@ -2526,7 +2632,8 @@ void menuHandler::frameTogglesMenu()
show_power,
enumEnd
};
static const char *optionsArray[enumEnd] = {"Finish"};
static const char *optionsArray[enumEnd];
optionsArray[0] = "Finish";
static int optionsEnumArray[enumEnd] = {Finish};
int options = 1;
@@ -2655,7 +2762,10 @@ void menuHandler::displayUnitsMenu()
{
enum optionsNumbers { Back, MetricUnits, ImperialUnits };
static const char *optionsArray[] = {"Back", "Metric", "Imperial"};
static const char *optionsArray[3];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "Metric";
optionsArray[2] = "Imperial";
BannerOverlayOptions bannerOptions;
bannerOptions.message = " Select display units";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -2683,9 +2793,12 @@ void menuHandler::messageBubblesMenu()
{
enum optionsNumbers { Back, ShowBubbles, HideBubbles };
static const char *optionsArray[] = {"Back", "Show Bubbles", "Hide Bubbles"};
static const char *optionsArray[3];
optionsArray[0] = getString(StringKey::Back);
optionsArray[1] = "Show Bubbles";
optionsArray[2] = "Hide Bubbles";
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Bubbles";
bannerOptions.message = getString(StringKey::MessageBubbles);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.InitialSelected = config.display.enable_message_bubbles ? 1 : 2;
@@ -2710,11 +2823,12 @@ void menuHandler::themeMenu()
{
// Build menu dynamically from the theme table.
// Only visible themes appear!
// Slot budget: 1 for "Back" + up to kMaxThemesInMenu visible themes.
// Slot budget: 1 for getString(StringKey::Back) + up to kMaxThemesInMenu visible themes.
// Bump kMaxThemesInMenu if you add more themes than will fit here.
constexpr size_t kMaxThemesInMenu = 15;
const size_t visibleCount = getVisibleThemeCount();
static const char *optionsArray[kMaxThemesInMenu + 1] = {"Back"};
static const char *optionsArray[kMaxThemesInMenu + 1];
optionsArray[0] = getString(StringKey::Back);
const size_t shownCount = (visibleCount < kMaxThemesInMenu) ? visibleCount : kMaxThemesInMenu;
const int options = static_cast<int>(shownCount) + 1; // +1 for Back
@@ -2723,12 +2837,12 @@ void menuHandler::themeMenu()
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Theme";
bannerOptions.message = getString(StringKey::Theme);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
// Highlight the currently active theme (visible index + 1 for the Back
// offset). If the active theme is hidden, leave selection on "Back".
// offset). If the active theme is hidden, leave selection on getString(StringKey::Back).
const size_t activeVisible = getActiveVisibleThemeIndex();
bannerOptions.InitialSelected = (activeVisible == SIZE_MAX) ? 0 : static_cast<int>(activeVisible) + 1;
@@ -2903,6 +3017,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case ThemeMenu:
themeMenu();
break;
case LanguagePicker:
languagePickerMenu();
break;
case HamModeConfirm:
hamModeConfirmMenu();
break;
@@ -2913,6 +3030,45 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
menuQueue = MenuNone;
}
void menuHandler::languagePickerMenu()
{
enum optionsNumbers { Back, English, Chinese };
static const char *optionsArray[3];
optionsArray[0] = getString(StringKey::Back);
static int optionsEnumArray[3] = {Back};
int options = 1;
optionsArray[options] = getString(StringKey::English);
optionsEnumArray[options++] = English;
optionsArray[options] = getString(StringKey::Chinese);
optionsEnumArray[options++] = Chinese;
BannerOverlayOptions bannerOptions;
bannerOptions.message = getString(StringKey::Language);
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == English) {
uiconfig.language = meshtastic_Language_ENGLISH;
meshtastic::l10n::currentDisplayLanguage = meshtastic_Language_ENGLISH;
saveUIConfig();
} else if (selected == Chinese) {
uiconfig.language = meshtastic_Language_SIMPLIFIED_CHINESE;
meshtastic::l10n::currentDisplayLanguage = meshtastic_Language_SIMPLIFIED_CHINESE;
saveUIConfig();
#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014)
screen->loadCJKFontIfNeeded();
#endif
}
menuQueue = ScreenOptionsMenu;
screen->runNow();
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::saveUIConfig()
{
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
+2
View File
@@ -56,6 +56,7 @@ class menuHandler
DisplayUnits,
MessageBubblesMenu,
ThemeMenu,
LanguagePicker,
HamModeConfirm,
LicensedToNormalConfirm
};
@@ -107,6 +108,7 @@ class menuHandler
static void wifiBaseMenu();
static void wifiToggleMenu();
static void screenOptionsMenu();
static void languagePickerMenu();
static void powerMenu();
static void nodeNameLengthMenu();
static void frameTogglesMenu();
+11 -1
View File
@@ -13,6 +13,7 @@
#include "graphics/SharedUIDisplay.h"
#include "graphics/TFTColorRegions.h"
#include "graphics/TFTPalette.h"
#include "graphics/l10n/Strings.h"
#include "graphics/TimeFormatters.h"
#include "graphics/emotes.h"
#include "main.h"
@@ -75,6 +76,15 @@ void scrollDown()
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
{
#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014) || defined(USE_ST7789) || \
defined(USE_ST7796)
if (meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_SIMPLIFIED_CHINESE ||
meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_TRADITIONAL_CHINESE) {
screen->drawTextL10n(x, y, line.c_str());
return;
}
#endif
graphics::EmoteRenderer::drawStringWithEmotes(display, x, y, line, FONT_HEIGHT_SMALL, emotes, emoteCount);
}
@@ -482,7 +492,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Still in ALL mode and no messages at all → show placeholder
graphics::drawCommonHeader(display, x, y, titleStr);
didReset = false;
const char *messageString = "No messages";
const char *messageString = meshtastic::l10n::getString(meshtastic::l10n::StringKey::NoMessages);
int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2);
display->drawString(center_text, getTextPositions(display)[2], messageString);
graphics::drawCommonFooter(display, x, y);
+46 -14
View File
@@ -10,6 +10,8 @@
#include "graphics/TFTColorRegions.h"
#include "graphics/TFTPalette.h"
#include "graphics/images.h"
#include "graphics/l10n/Strings.h"
#include "graphics/Screen.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#if HAS_BUTTON
@@ -39,6 +41,27 @@ extern std::vector<std::string> functionSymbol;
extern std::string functionSymbolString;
extern bool hasUnreadMessage;
namespace
{
inline bool isCJKLanguage()
{
return meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_SIMPLIFIED_CHINESE ||
meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_TRADITIONAL_CHINESE;
}
inline void drawBannerText(OLEDDisplay *display, int16_t x, int16_t y, const char *text)
{
#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014)
if (isCJKLanguage() && ::screen) {
::screen->drawTextL10n(x, y, text);
return;
}
#endif
display->drawString(x, y, text);
}
} // namespace
namespace graphics
{
int bannerSignalBars = -1;
@@ -184,7 +207,7 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat
{
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_SMALL);
display->drawString(64 + x, y, "Creating SSL certificate");
display->drawString(64 + x, y, meshtastic::l10n::getString(meshtastic::l10n::StringKey::CreatingSSLCert));
#ifdef ARCH_ESP32
yield();
@@ -193,9 +216,9 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat
display->setFont(FONT_SMALL);
if ((millis() / 1000) % 2) {
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . .");
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, meshtastic::l10n::getString(meshtastic::l10n::StringKey::PleaseWait));
} else {
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ");
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, meshtastic::l10n::getString(meshtastic::l10n::StringKey::PleaseWait));
}
}
@@ -919,9 +942,12 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->setColor(BLACK);
int yOffset = 3;
if (current_notification_type == notificationTypeEnum::node_picker) {
UIRenderer::drawStringWithEmotes(display, textX, lineY - yOffset, lineBuffer, FONT_HEIGHT_SMALL, 1, false);
if (isCJKLanguage())
drawBannerText(display, textX, lineY - yOffset, lineBuffer);
else
UIRenderer::drawStringWithEmotes(display, textX, lineY - yOffset, lineBuffer, FONT_HEIGHT_SMALL, 1, false);
} else {
display->drawString(textX, lineY - yOffset, lineBuffer);
drawBannerText(display, textX, lineY - yOffset, lineBuffer);
}
display->setColor(WHITE);
lineY += (thisLineHeight - 2 - background_yOffset);
@@ -943,9 +969,12 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
int groupStartX = boxLeft + (boxWidth - totalWidth) / 2;
if (current_notification_type == notificationTypeEnum::node_picker) {
UIRenderer::drawStringWithEmotes(display, groupStartX, lineY, lineBuffer, FONT_HEIGHT_SMALL, 1, false);
if (isCJKLanguage())
drawBannerText(display, groupStartX, lineY, lineBuffer);
else
UIRenderer::drawStringWithEmotes(display, groupStartX, lineY, lineBuffer, FONT_HEIGHT_SMALL, 1, false);
} else {
display->drawString(groupStartX, lineY, lineBuffer);
drawBannerText(display, groupStartX, lineY, lineBuffer);
}
int baseX = groupStartX + textWidth + gap;
@@ -977,9 +1006,12 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
}
} else {
if (current_notification_type == notificationTypeEnum::node_picker) {
UIRenderer::drawStringWithEmotes(display, textX, lineY, lineBuffer, FONT_HEIGHT_SMALL, 1, false);
if (isCJKLanguage())
drawBannerText(display, textX, lineY, lineBuffer);
else
UIRenderer::drawStringWithEmotes(display, textX, lineY, lineBuffer, FONT_HEIGHT_SMALL, 1, false);
} else {
display->drawString(textX, lineY, lineBuffer);
drawBannerText(display, textX, lineY, lineBuffer);
}
}
lineY += thisLineHeight;
@@ -1009,24 +1041,24 @@ void NotificationRenderer::drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisp
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_MEDIUM);
char tempBuf[24];
snprintf(tempBuf, sizeof(tempBuf), "Critical fault #%d", error_code);
char tempBuf[40];
snprintf(tempBuf, sizeof(tempBuf), "%s #%d", meshtastic::l10n::getString(meshtastic::l10n::StringKey::CriticalFault), error_code);
display->drawString(0 + x, 0 + y, tempBuf);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please visit \nmeshtastic.org");
display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, meshtastic::l10n::getString(meshtastic::l10n::StringKey::ForHelpVisit));
}
void NotificationRenderer::drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(64 + x, y, "Updating");
display->drawString(64 + x, y, meshtastic::l10n::getString(meshtastic::l10n::StringKey::Updating));
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(),
"Please be patient and do not power off.");
meshtastic::l10n::getString(meshtastic::l10n::StringKey::PleaseBePatient));
}
void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state)
+46
View File
@@ -0,0 +1,46 @@
// Chinese font data (VLW format) for OLEDDisplay-based TFT rendering via LovyanGFX.
//
// HOW TO GENERATE FONT DATA:
//
// Option 1: Adafruit GFX fontconvert (simpler, fewer glyphs)
// brew install adafruitnrf52-gfx-fontconvert # macOS
// fontconvert NotoSansSC-Regular.ttf 16 > zh_cn_16.h
// The output is a GFXfont struct. LovyanGFX can use this via ft_gfx type.
//
// Option 2: LovyanGFX VLW format (recommended, more features)
// Use the LovyanGFX font converter script to generate a .vlw file from TTF
// Then embed the .vlw binary as a byte array using xxd:
// xxd -i zh_font_16.vlw > zh_font_16_data.h
//
// Option 3: Use BDF fonts (free, text-based)
// WenQuanYi bitmap fonts: http://wenq.org/
// Convert BDF to VLW using LovyanGFX tools
//
// FONT SIZE GUIDELINES:
// - 16px: Minimum readable Chinese on 320x240 TFT (~20 chars per line)
// - 20px: Better readability, ~16 chars per line
// - 24px: Title/header text, ~13 chars per line
//
// CHARACTER SET RECOMMENDATIONS:
// - GB2312 Level 1: 3755 commonly used simplified Chinese characters
// - Plus ASCII 0x20-0x7E for punctuation and Latin characters
//
// PLACEHOLDER DATA - Replace with generated font binary:
#include "OLEDDisplayFontsZH.h"
// Placeholder font data.
// Replace with actual generated VLW/Gfx font binary from a Chinese TTF file.
// The array below is intentionally minimal - it won't render any characters
// until real font data is provided.
const uint8_t FontZH_CN_16[] = {
// VLW font file magic header would go here
// Format: 0x00 0x00 ...
// Replace with output from: xxd -i your_chinese_font.vlw
};
const uint32_t FontZH_CN_16_size = sizeof(FontZH_CN_16);
const uint8_t FontZH_TW_16[] = {
// Traditional Chinese VLW font data placeholder
};
const uint32_t FontZH_TW_16_size = sizeof(FontZH_TW_16);
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <cstdint>
// Chinese Simplified font - VLW format (to be generated from TTF)
//
// HOW TO GENERATE:
// 1. Download NotoSansSC-Regular.ttf (or any open-source Chinese font)
// 2. Use Adafruit fontconvert tool:
// ./fontconvert NotoSansSC-Regular.ttf 16 > zh_cn_16_pt.h
// 3. Copy the resulting binary data (GFXfont struct bytes) into the array below
//
// For LovyanGFX VLW font format, use:
// - The binary VLW file can be embedded as a const uint8_t[] array
// - Loaded with tft->loadFont(zh_font_data_array)
extern const uint8_t FontZH_CN_16[];
extern const uint32_t FontZH_CN_16_size;
// Chinese Traditional font (VLW format)
extern const uint8_t FontZH_TW_16[];
extern const uint32_t FontZH_TW_16_size;
+20
View File
@@ -0,0 +1,20 @@
#include "Strings.h"
#include "Strings_EN.h"
#include "Strings_ZH.h"
namespace meshtastic
{
namespace l10n
{
meshtastic_Language currentDisplayLanguage = meshtastic_Language_ENGLISH;
const char *getString(StringKey key, meshtastic_Language lang)
{
if (lang == meshtastic_Language_SIMPLIFIED_CHINESE || lang == meshtastic_Language_TRADITIONAL_CHINESE)
return getStringZH(key);
return getStringEN(key);
}
} // namespace l10n
} // namespace meshtastic
+170
View File
@@ -0,0 +1,170 @@
#pragma once
#include "mesh/generated/meshtastic/device_ui.pb.h"
namespace meshtastic
{
namespace l10n
{
enum class StringKey : uint8_t {
Back,
OK,
GotIt,
WelcomeToMeshtastic,
SwipeToNavigate,
ClickToNavigate,
UseSelectButton,
DeviceRole,
RadioPreset,
FrequencySlot,
LoRaRegion,
Client,
ClientMute,
LostAndFound,
Tracker,
Hour12,
Hour24,
ClockAction,
ClockFace,
TimeFormat,
Timezone,
MessageAction,
Reply,
ViewChats,
UnmuteChannel,
MuteChannel,
Delete,
ReadAloud,
Brightness,
FrameVisibility,
DisplayUnits,
MessageBubbles,
Theme,
Reboot,
Shutdown,
SwitchToMUI,
DisplayOptions,
Language,
Chinese,
English,
NoGPS,
GPSoff,
GPSnotPresent,
GPSisDisabled,
Node,
Hop,
USB,
BT_off,
WiFi,
WiFiNotConnected,
WiFiConnected,
SSID,
LoRaInfo,
LoRa,
Role,
Freq,
ChUtil,
System,
Heap,
PSRAM,
Flash,
SD,
Ver,
Up,
ClientApp,
NoConnected,
LOCKED,
ConnectToUnlock,
Battery,
Resuming,
LastHeard,
HopsSig,
HopsSignal,
Nodes,
Distance,
Bearings,
CreatingSSLCert,
PleaseWait,
CriticalFault,
ForHelpVisit,
Updating,
PleaseBePatient,
RebootInProgress,
PowerMenu,
SystemMenu,
ScreenOptions,
WifiToggle,
BluetoothToggle,
NodeDbReset,
ManageNode,
RemoveFavorite,
AddFavorite,
TestMenu,
TraceRoute,
ThrottleMessage,
MessageViewMode,
DeleteMessages,
NodeNameLength,
GPSMenu,
GPSToggle,
GPSFormat,
GPSSmartPosition,
GPSUpdateInterval,
GPSPositionBroadcast,
CompassNorth,
BuzzerMode,
HamModeConfirm,
LicensedToNormal,
KeyVerification,
FinalPrompt,
RegionUnset,
SelectRegionMessage,
Rebooting,
LoRaActions,
Slot0,
WithPreset,
WithFreetext,
DeleteOldest,
DeleteThisChat,
DeleteAll,
DeleteAllChats,
TemporarilyMute,
Unmute,
ToggleBacklight,
SleepScreen,
SendPosition,
SendNodeInfo,
NewPresetMsg,
NewFreetextMsg,
Notifications,
Power,
RebootShutdown,
GoToChat,
NewPreset,
Favorite,
UnmuteNotifications,
MuteNotifications,
UnignoreNode,
IgnoreNode,
NumberPicker,
TestAnnounce,
NodeActionsSettings,
ShowNameLength,
Bluetooth,
NoMessages,
COUNT
};
extern meshtastic_Language currentDisplayLanguage;
const char *getString(StringKey key, meshtastic_Language lang);
inline const char *getString(StringKey key)
{
extern meshtastic_Language currentDisplayLanguage;
return getString(key, currentDisplayLanguage);
}
} // namespace l10n
} // namespace meshtastic
+456
View File
@@ -0,0 +1,456 @@
#pragma once
#include "Strings.h"
namespace meshtastic
{
namespace l10n
{
// English string table
// The strings are stored as const arrays to save RAM on constrained devices.
// Each locale file defines the same set of strings in its own language.
static const char STR_BACK_EN[] = "Back";
static const char STR_OK_EN[] = "OK";
static const char STR_GOT_IT_EN[] = "Got it!";
static const char STR_WELCOME_TO_MESHTASTIC_EN[] = "Welcome to Meshtastic!";
static const char STR_SWIPE_TO_NAVIGATE_EN[] = "Swipe to navigate.";
static const char STR_CLICK_TO_NAVIGATE_EN[] = "Click to navigate.";
static const char STR_USE_SELECT_BUTTON_EN[] = "Use the Select button.";
static const char STR_DEVICE_ROLE_EN[] = "Device Role";
static const char STR_RADIO_PRESET_EN[] = "Radio Preset";
static const char STR_FREQUENCY_SLOT_EN[] = "Frequency Slot";
static const char STR_LORA_REGION_EN[] = "LoRa Region";
static const char STR_CLIENT_EN[] = "Client";
static const char STR_CLIENT_MUTE_EN[] = "Client Mute";
static const char STR_LOST_AND_FOUND_EN[] = "Lost and Found";
static const char STR_TRACKER_EN[] = "Tracker";
static const char STR_12HOUR_EN[] = "12-hour";
static const char STR_24HOUR_EN[] = "24-hour";
static const char STR_CLOCK_ACTION_EN[] = "Clock Action";
static const char STR_CLOCK_FACE_EN[] = "Clock Face";
static const char STR_TIME_FORMAT_EN[] = "Time Format";
static const char STR_TIMEZONE_EN[] = "Timezone";
static const char STR_MESSAGE_ACTION_EN[] = "Message Action";
static const char STR_REPLY_EN[] = "Reply";
static const char STR_VIEW_CHATS_EN[] = "View Chats";
static const char STR_UNMUTE_CHANNEL_EN[] = "Unmute Channel";
static const char STR_MUTE_CHANNEL_EN[] = "Mute Channel";
static const char STR_DELETE_EN[] = "Delete";
static const char STR_READ_ALOUD_EN[] = "Read Aloud";
static const char STR_BRIGHTNESS_EN[] = "Brightness";
static const char STR_FRAME_VISIBILITY_EN[] = "Frame Visibility";
static const char STR_DISPLAY_UNITS_EN[] = "Display Units";
static const char STR_MESSAGE_BUBBLES_EN[] = "Message Bubbles";
static const char STR_THEME_EN[] = "Theme";
static const char STR_REBOOT_EN[] = "Reboot";
static const char STR_SHUTDOWN_EN[] = "Shutdown";
static const char STR_SWITCH_TO_MUI_EN[] = "Switch to MUI";
static const char STR_DISPLAY_OPTIONS_EN[] = "Display Options";
static const char STR_LANGUAGE_EN[] = "Language";
static const char STR_CHINESE_EN[] = "Chinese";
static const char STR_ENGLISH_EN[] = "English";
static const char STR_NO_GPS_EN[] = "No GPS";
static const char STR_GPS_OFF_EN[] = "GPS off";
static const char STR_GPS_NOT_PRESENT_EN[] = "GPS not present";
static const char STR_GPS_IS_DISABLED_EN[] = "GPS is disabled";
static const char STR_NODE_EN[] = "Node";
static const char STR_HOP_EN[] = "Hop:";
static const char STR_USB_EN[] = "USB";
static const char STR_BT_OFF_EN[] = "BT off";
static const char STR_WIFI_EN[] = "WiFi";
static const char STR_WIFI_NOT_CONNECTED_EN[] = "WiFi: Not Connected";
static const char STR_WIFI_CONNECTED_EN[] = "WiFi: Connected";
static const char STR_SSID_EN[] = "SSID:";
static const char STR_LORA_INFO_EN[] = "LoRa Info";
static const char STR_LORA_EN[] = "LoRa";
static const char STR_ROLE_EN[] = "Role:";
static const char STR_FREQ_EN[] = "Freq:";
static const char STR_CHUTIL_EN[] = "ChUtil";
static const char STR_SYSTEM_EN[] = "System";
static const char STR_HEAP_EN[] = "Heap:";
static const char STR_PSRAM_EN[] = "PSRAM:";
static const char STR_FLASH_EN[] = "Flash:";
static const char STR_SD_EN[] = "SD:";
static const char STR_VER_EN[] = "Ver:";
static const char STR_UP_EN[] = "Up:";
static const char STR_CLIENT_APP_EN[] = "Client";
static const char STR_NO_CONNECTED_EN[] = "No Connected";
static const char STR_LOCKED_EN[] = "LOCKED";
static const char STR_CONNECT_TO_UNLOCK_EN[] = "Connect to unlock";
static const char STR_BATTERY_EN[] = "Battery";
static const char STR_RESUMING_EN[] = "Resuming...";
static const char STR_LAST_HEARD_EN[] = "Last Heard";
static const char STR_HOPS_SIG_EN[] = "Hops/Sig";
static const char STR_HOPS_SIGNAL_EN[] = "Hops/Signal";
static const char STR_NODES_EN[] = "Nodes";
static const char STR_DISTANCE_EN[] = "Distance";
static const char STR_BEARINGS_EN[] = "Bearings";
static const char STR_CREATING_SSL_EN[] = "Creating SSL certificate";
static const char STR_PLEASE_WAIT_EN[] = "Please wait . . .";
static const char STR_CRITICAL_FAULT_EN[] = "Critical fault";
static const char STR_FOR_HELP_VISIT_EN[] = "For help, please visit";
static const char STR_UPDATING_EN[] = "Updating";
static const char STR_PLEASE_BE_PATIENT_EN[] = "Please be patient and do not power off.";
static const char STR_REBOOT_IN_PROGRESS_EN[] = "Reboot in progress...";
static const char STR_POWER_MENU_EN[] = "Power";
static const char STR_SYSTEM_MENU_EN[] = "System";
static const char STR_SCREEN_OPTIONS_EN[] = "Screen";
static const char STR_WIFI_TOGGLE_EN[] = "WiFi";
static const char STR_BLUETOOTH_TOGGLE_EN[] = "Bluetooth";
static const char STR_NODE_DB_RESET_EN[] = "Reset NodeDB";
static const char STR_MANAGE_NODE_EN[] = "Manage Node";
static const char STR_REMOVE_FAVORITE_EN[] = "Remove Favorite";
static const char STR_ADD_FAVORITE_EN[] = "Add Favorite";
static const char STR_TEST_MENU_EN[] = "Test";
static const char STR_TRACE_ROUTE_EN[] = "Trace Route";
static const char STR_THROTTLE_MSG_EN[] = "Message Throttled";
static const char STR_MSG_VIEW_MODE_EN[] = "View Mode";
static const char STR_DELETE_MESSAGES_EN[] = "Delete Messages";
static const char STR_NODE_NAME_LENGTH_EN[] = "Name Length";
static const char STR_GPS_MENU_EN[] = "GPS";
static const char STR_GPS_TOGGLE_EN[] = "GPS Toggle";
static const char STR_GPS_FORMAT_EN[] = "GPS Format";
static const char STR_GPS_SMART_POS_EN[] = "Smart Position";
static const char STR_GPS_UPDATE_INTERVAL_EN[] = "Update Interval";
static const char STR_GPS_POS_BCAST_EN[] = "Position Broadcast";
static const char STR_COMPASS_NORTH_EN[] = "Compass North";
static const char STR_BUZZER_MODE_EN[] = "Buzzer";
static const char STR_HAM_MODE_EN[] = "Ham Mode";
static const char STR_LICENSED_NORMAL_EN[] = "Normal Mode";
static const char STR_KEY_VERIFY_EN[] = "Verify Key";
static const char STR_REGION_UNSET_EN[] = "Select a Region";
static const char STR_SELECT_REGION_EN[] = "Please select your LoRa region:";
static const char STR_REBOOTING_EN[] = "Rebooting...";
static const char STR_LORA_ACTIONS_EN[] = "LoRa Actions";
static const char STR_SLOT0_EN[] = "Slot 0 (Auto)";
static const char STR_WITH_PRESET_EN[] = "With Preset";
static const char STR_WITH_FREETEXT_EN[] = "With Freetext";
static const char STR_DELETE_OLDEST_EN[] = "Delete Oldest";
static const char STR_DELETE_THIS_CHAT_EN[] = "Delete This Chat";
static const char STR_DELETE_ALL_EN[] = "Delete All";
static const char STR_DELETE_ALL_CHATS_EN[] = "Delete All Chats";
static const char STR_TEMPORARILY_MUTE_EN[] = "Temporarily Mute";
static const char STR_UNMUTE_EN[] = "Unmute";
static const char STR_TOGGLE_BACKLIGHT_EN[] = "Toggle Backlight";
static const char STR_SLEEP_SCREEN_EN[] = "Sleep Screen";
static const char STR_SEND_POSITION_EN[] = "Send Position";
static const char STR_SEND_NODE_INFO_EN[] = "Send Node Info";
static const char STR_NEW_PRESET_MSG_EN[] = "New Preset Msg";
static const char STR_NEW_FREETEXT_MSG_EN[] = "New Freetext Msg";
static const char STR_NOTIFICATIONS_EN[] = "Notifications";
static const char STR_POWER_EN[] = "Power";
static const char STR_REBOOT_SHUTDOWN_EN[] = "Reboot/Shutdown";
static const char STR_GO_TO_CHAT_EN[] = "Go To Chat";
static const char STR_NEW_PRESET_EN[] = "New Preset";
static const char STR_FAVORITE_EN[] = "Favorite";
static const char STR_UNMUTE_NOTIFICATIONS_EN[] = "Unmute Notifications";
static const char STR_MUTE_NOTIFICATIONS_EN[] = "Mute Notifications";
static const char STR_UNIGNORE_NODE_EN[] = "Unignore Node";
static const char STR_IGNORE_NODE_EN[] = "Ignore Node";
static const char STR_NUMBER_PICKER_EN[] = "Number Picker";
static const char STR_TEST_ANNOUNCE_EN[] = "Test Announce";
static const char STR_NODE_ACTIONS_EN[] = "Node Actions / Settings";
static const char STR_SHOW_NAME_LENGTH_EN[] = "Show Long/Short Name";
static const char STR_BLUETOOTH_EN[] = "Bluetooth";
static const char STR_NO_MESSAGES_EN[] = "No messages";
inline const char *getStringEN(StringKey key)
{
switch (key) {
case StringKey::Back:
return STR_BACK_EN;
case StringKey::OK:
return STR_OK_EN;
case StringKey::GotIt:
return STR_GOT_IT_EN;
case StringKey::WelcomeToMeshtastic:
return STR_WELCOME_TO_MESHTASTIC_EN;
case StringKey::SwipeToNavigate:
return STR_SWIPE_TO_NAVIGATE_EN;
case StringKey::ClickToNavigate:
return STR_CLICK_TO_NAVIGATE_EN;
case StringKey::UseSelectButton:
return STR_USE_SELECT_BUTTON_EN;
case StringKey::DeviceRole:
return STR_DEVICE_ROLE_EN;
case StringKey::RadioPreset:
return STR_RADIO_PRESET_EN;
case StringKey::FrequencySlot:
return STR_FREQUENCY_SLOT_EN;
case StringKey::LoRaRegion:
return STR_LORA_REGION_EN;
case StringKey::Client:
return STR_CLIENT_EN;
case StringKey::ClientMute:
return STR_CLIENT_MUTE_EN;
case StringKey::LostAndFound:
return STR_LOST_AND_FOUND_EN;
case StringKey::Tracker:
return STR_TRACKER_EN;
case StringKey::Hour12:
return STR_12HOUR_EN;
case StringKey::Hour24:
return STR_24HOUR_EN;
case StringKey::ClockAction:
return STR_CLOCK_ACTION_EN;
case StringKey::ClockFace:
return STR_CLOCK_FACE_EN;
case StringKey::TimeFormat:
return STR_TIME_FORMAT_EN;
case StringKey::Timezone:
return STR_TIMEZONE_EN;
case StringKey::MessageAction:
return STR_MESSAGE_ACTION_EN;
case StringKey::Reply:
return STR_REPLY_EN;
case StringKey::ViewChats:
return STR_VIEW_CHATS_EN;
case StringKey::UnmuteChannel:
return STR_UNMUTE_CHANNEL_EN;
case StringKey::MuteChannel:
return STR_MUTE_CHANNEL_EN;
case StringKey::Delete:
return STR_DELETE_EN;
case StringKey::ReadAloud:
return STR_READ_ALOUD_EN;
case StringKey::Brightness:
return STR_BRIGHTNESS_EN;
case StringKey::FrameVisibility:
return STR_FRAME_VISIBILITY_EN;
case StringKey::DisplayUnits:
return STR_DISPLAY_UNITS_EN;
case StringKey::MessageBubbles:
return STR_MESSAGE_BUBBLES_EN;
case StringKey::Theme:
return STR_THEME_EN;
case StringKey::Reboot:
return STR_REBOOT_EN;
case StringKey::Shutdown:
return STR_SHUTDOWN_EN;
case StringKey::SwitchToMUI:
return STR_SWITCH_TO_MUI_EN;
case StringKey::DisplayOptions:
return STR_DISPLAY_OPTIONS_EN;
case StringKey::Language:
return STR_LANGUAGE_EN;
case StringKey::Chinese:
return STR_CHINESE_EN;
case StringKey::English:
return STR_ENGLISH_EN;
case StringKey::NoGPS:
return STR_NO_GPS_EN;
case StringKey::GPSoff:
return STR_GPS_OFF_EN;
case StringKey::GPSnotPresent:
return STR_GPS_NOT_PRESENT_EN;
case StringKey::GPSisDisabled:
return STR_GPS_IS_DISABLED_EN;
case StringKey::Node:
return STR_NODE_EN;
case StringKey::Hop:
return STR_HOP_EN;
case StringKey::USB:
return STR_USB_EN;
case StringKey::BT_off:
return STR_BT_OFF_EN;
case StringKey::WiFi:
return STR_WIFI_EN;
case StringKey::WiFiNotConnected:
return STR_WIFI_NOT_CONNECTED_EN;
case StringKey::WiFiConnected:
return STR_WIFI_CONNECTED_EN;
case StringKey::SSID:
return STR_SSID_EN;
case StringKey::LoRaInfo:
return STR_LORA_INFO_EN;
case StringKey::LoRa:
return STR_LORA_EN;
case StringKey::Role:
return STR_ROLE_EN;
case StringKey::Freq:
return STR_FREQ_EN;
case StringKey::ChUtil:
return STR_CHUTIL_EN;
case StringKey::System:
return STR_SYSTEM_EN;
case StringKey::Heap:
return STR_HEAP_EN;
case StringKey::PSRAM:
return STR_PSRAM_EN;
case StringKey::Flash:
return STR_FLASH_EN;
case StringKey::SD:
return STR_SD_EN;
case StringKey::Ver:
return STR_VER_EN;
case StringKey::Up:
return STR_UP_EN;
case StringKey::ClientApp:
return STR_CLIENT_APP_EN;
case StringKey::NoConnected:
return STR_NO_CONNECTED_EN;
case StringKey::LOCKED:
return STR_LOCKED_EN;
case StringKey::ConnectToUnlock:
return STR_CONNECT_TO_UNLOCK_EN;
case StringKey::Battery:
return STR_BATTERY_EN;
case StringKey::Resuming:
return STR_RESUMING_EN;
case StringKey::LastHeard:
return STR_LAST_HEARD_EN;
case StringKey::HopsSig:
return STR_HOPS_SIG_EN;
case StringKey::HopsSignal:
return STR_HOPS_SIGNAL_EN;
case StringKey::Nodes:
return STR_NODES_EN;
case StringKey::Distance:
return STR_DISTANCE_EN;
case StringKey::Bearings:
return STR_BEARINGS_EN;
case StringKey::CreatingSSLCert:
return STR_CREATING_SSL_EN;
case StringKey::PleaseWait:
return STR_PLEASE_WAIT_EN;
case StringKey::CriticalFault:
return STR_CRITICAL_FAULT_EN;
case StringKey::ForHelpVisit:
return STR_FOR_HELP_VISIT_EN;
case StringKey::Updating:
return STR_UPDATING_EN;
case StringKey::PleaseBePatient:
return STR_PLEASE_BE_PATIENT_EN;
case StringKey::RebootInProgress:
return STR_REBOOT_IN_PROGRESS_EN;
case StringKey::PowerMenu:
return STR_POWER_MENU_EN;
case StringKey::SystemMenu:
return STR_SYSTEM_MENU_EN;
case StringKey::ScreenOptions:
return STR_SCREEN_OPTIONS_EN;
case StringKey::WifiToggle:
return STR_WIFI_TOGGLE_EN;
case StringKey::BluetoothToggle:
return STR_BLUETOOTH_TOGGLE_EN;
case StringKey::NodeDbReset:
return STR_NODE_DB_RESET_EN;
case StringKey::ManageNode:
return STR_MANAGE_NODE_EN;
case StringKey::RemoveFavorite:
return STR_REMOVE_FAVORITE_EN;
case StringKey::AddFavorite:
return STR_ADD_FAVORITE_EN;
case StringKey::TestMenu:
return STR_TEST_MENU_EN;
case StringKey::TraceRoute:
return STR_TRACE_ROUTE_EN;
case StringKey::ThrottleMessage:
return STR_THROTTLE_MSG_EN;
case StringKey::MessageViewMode:
return STR_MSG_VIEW_MODE_EN;
case StringKey::DeleteMessages:
return STR_DELETE_MESSAGES_EN;
case StringKey::NodeNameLength:
return STR_NODE_NAME_LENGTH_EN;
case StringKey::GPSMenu:
return STR_GPS_MENU_EN;
case StringKey::GPSToggle:
return STR_GPS_TOGGLE_EN;
case StringKey::GPSFormat:
return STR_GPS_FORMAT_EN;
case StringKey::GPSSmartPosition:
return STR_GPS_SMART_POS_EN;
case StringKey::GPSUpdateInterval:
return STR_GPS_UPDATE_INTERVAL_EN;
case StringKey::GPSPositionBroadcast:
return STR_GPS_POS_BCAST_EN;
case StringKey::CompassNorth:
return STR_COMPASS_NORTH_EN;
case StringKey::BuzzerMode:
return STR_BUZZER_MODE_EN;
case StringKey::HamModeConfirm:
return STR_HAM_MODE_EN;
case StringKey::LicensedToNormal:
return STR_LICENSED_NORMAL_EN;
case StringKey::KeyVerification:
return STR_KEY_VERIFY_EN;
case StringKey::RegionUnset:
return STR_REGION_UNSET_EN;
case StringKey::SelectRegionMessage:
return STR_SELECT_REGION_EN;
case StringKey::Rebooting:
return STR_REBOOTING_EN;
case StringKey::LoRaActions:
return STR_LORA_ACTIONS_EN;
case StringKey::Slot0:
return STR_SLOT0_EN;
case StringKey::WithPreset:
return STR_WITH_PRESET_EN;
case StringKey::WithFreetext:
return STR_WITH_FREETEXT_EN;
case StringKey::DeleteOldest:
return STR_DELETE_OLDEST_EN;
case StringKey::DeleteThisChat:
return STR_DELETE_THIS_CHAT_EN;
case StringKey::DeleteAll:
return STR_DELETE_ALL_EN;
case StringKey::DeleteAllChats:
return STR_DELETE_ALL_CHATS_EN;
case StringKey::TemporarilyMute:
return STR_TEMPORARILY_MUTE_EN;
case StringKey::Unmute:
return STR_UNMUTE_EN;
case StringKey::ToggleBacklight:
return STR_TOGGLE_BACKLIGHT_EN;
case StringKey::SleepScreen:
return STR_SLEEP_SCREEN_EN;
case StringKey::SendPosition:
return STR_SEND_POSITION_EN;
case StringKey::SendNodeInfo:
return STR_SEND_NODE_INFO_EN;
case StringKey::NewPresetMsg:
return STR_NEW_PRESET_MSG_EN;
case StringKey::NewFreetextMsg:
return STR_NEW_FREETEXT_MSG_EN;
case StringKey::Notifications:
return STR_NOTIFICATIONS_EN;
case StringKey::Power:
return STR_POWER_EN;
case StringKey::RebootShutdown:
return STR_REBOOT_SHUTDOWN_EN;
case StringKey::GoToChat:
return STR_GO_TO_CHAT_EN;
case StringKey::NewPreset:
return STR_NEW_PRESET_EN;
case StringKey::Favorite:
return STR_FAVORITE_EN;
case StringKey::UnmuteNotifications:
return STR_UNMUTE_NOTIFICATIONS_EN;
case StringKey::MuteNotifications:
return STR_MUTE_NOTIFICATIONS_EN;
case StringKey::UnignoreNode:
return STR_UNIGNORE_NODE_EN;
case StringKey::IgnoreNode:
return STR_IGNORE_NODE_EN;
case StringKey::NumberPicker:
return STR_NUMBER_PICKER_EN;
case StringKey::TestAnnounce:
return STR_TEST_ANNOUNCE_EN;
case StringKey::NodeActionsSettings:
return STR_NODE_ACTIONS_EN;
case StringKey::ShowNameLength:
return STR_SHOW_NAME_LENGTH_EN;
case StringKey::Bluetooth:
return STR_BLUETOOTH_EN;
case StringKey::NoMessages:
return STR_NO_MESSAGES_EN;
default:
return "?";
}
}
} // namespace l10n
} // namespace meshtastic
+452
View File
@@ -0,0 +1,452 @@
#pragma once
#include "Strings.h"
namespace meshtastic
{
namespace l10n
{
static const char STR_BACK_ZH[] = "\xe8\xbf\x94\xe5\x9b\x9e";
static const char STR_OK_ZH[] = "\xe7\xa1\xae\xe5\xae\x9a";
static const char STR_GOT_IT_ZH[] = "\xe7\x9f\xa5\xe9\x81\x93\xe4\xba\x86";
static const char STR_WELCOME_TO_MESHTASTIC_ZH[] = "\xe6\xac\xa2\xe8\xbf\x8e\xe4\xbd\xbf\xe7\x94\xa8 Meshtastic\xef\xbc\x81";
static const char STR_SWIPE_TO_NAVIGATE_ZH[] = "\xe6\xbb\x91\xe5\x8a\xa8\xe4\xbb\xa5\xe5\xaf\xbc\xe8\x88\xaa\xe3\x80\x82";
static const char STR_CLICK_TO_NAVIGATE_ZH[] = "\xe7\x82\xb9\xe5\x87\xbb\xe4\xbb\xa5\xe5\xaf\xbc\xe8\x88\xaa\xe3\x80\x82";
static const char STR_USE_SELECT_BUTTON_ZH[] = "\xe4\xbd\xbf\xe7\x94\xa8\xe9\x80\x89\xe6\x8b\xa9\xe6\x8c\x89\xe9\x92\xae\xe3\x80\x82";
static const char STR_DEVICE_ROLE_ZH[] = "\xe8\xae\xbe\xe5\xa4\x87\xe8\xa7\x92\xe8\x89\xb2";
static const char STR_RADIO_PRESET_ZH[] = "\xe6\x97\xa0\xe7\xba\xbf\xe9\xa2\x84\xe8\xae\xbe";
static const char STR_FREQUENCY_SLOT_ZH[] = "\xe9\xa2\x91\xe7\x8e\x87\xe6\xa7\xbd\xe4\xbd\x8d";
static const char STR_LORA_REGION_ZH[] = "LoRa \xe5\x9c\xb0\xe5\x8c\xba";
static const char STR_CLIENT_ZH[] = "\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf";
static const char STR_CLIENT_MUTE_ZH[] = "\xe9\x9d\x99\xe9\x9f\xb3\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf";
static const char STR_LOST_AND_FOUND_ZH[] = "\xe5\xa4\xb1\xe7\x89\xa9\xe6\x8b\x9b\xe9\xa2\x86";
static const char STR_TRACKER_ZH[] = "\xe8\xbf\xbd\xe8\xb8\xaa\xe5\x99\xa8";
static const char STR_12HOUR_ZH[] = "12 \xe5\xb0\x8f\xe6\x97\xb6\xe5\x88\xb6";
static const char STR_24HOUR_ZH[] = "24 \xe5\xb0\x8f\xe6\x97\xb6\xe5\x88\xb6";
static const char STR_CLOCK_ACTION_ZH[] = "\xe6\x97\xb6\xe9\x92\x9f\xe6\x93\x8d\xe4\xbd\x9c";
static const char STR_CLOCK_FACE_ZH[] = "\xe6\x97\xb6\xe9\x92\x9f\xe6\xa0\xb7\xe5\xbc\x8f";
static const char STR_TIME_FORMAT_ZH[] = "\xe6\x97\xb6\xe9\x97\xb4\xe6\xa0\xbc\xe5\xbc\x8f";
static const char STR_TIMEZONE_ZH[] = "\xe6\x97\xb6\xe5\x8c\xba";
static const char STR_MESSAGE_ACTION_ZH[] = "\xe6\xb6\x88\xe6\x81\xaf\xe6\x93\x8d\xe4\xbd\x9c";
static const char STR_REPLY_ZH[] = "\xe5\x9b\x9e\xe5\xa4\x8d";
static const char STR_VIEW_CHATS_ZH[] = "\xe6\x9f\xa5\xe7\x9c\x8b\xe8\x81\x8a\xe5\xa4\xa9";
static const char STR_UNMUTE_CHANNEL_ZH[] = "\xe5\x8f\x96\xe6\xb6\x88\xe9\x9d\x99\xe9\x9f\xb3";
static const char STR_MUTE_CHANNEL_ZH[] = "\xe9\x9d\x99\xe9\x9f\xb3\xe9\xa2\x91\xe9\x81\x93";
static const char STR_DELETE_ZH[] = "\xe5\x88\xa0\xe9\x99\xa4";
static const char STR_READ_ALOUD_ZH[] = "\xe5\xa4\xa7\xe5\xa3\xb0\xe6\x9c\x97\xe8\xaf\xbb";
static const char STR_BRIGHTNESS_ZH[] = "\xe4\xba\xae\xe5\xba\xa6";
static const char STR_FRAME_VISIBILITY_ZH[] = "\xe9\xa1\xb5\xe9\x9d\xa2\xe5\x8f\xaf\xe8\xa7\x81\xe6\x80\xa7";
static const char STR_DISPLAY_UNITS_ZH[] = "\xe6\x98\xbe\xe7\xa4\xba\xe5\x8d\x95\xe4\xbd\x8d";
static const char STR_MESSAGE_BUBBLES_ZH[] = "\xe6\xb6\x88\xe6\x81\xaf\xe6\xb0\x94\xe6\xb3\xa1";
static const char STR_THEME_ZH[] = "\xe4\xb8\xbb\xe9\xa2\x98";
static const char STR_REBOOT_ZH[] = "\xe9\x87\x8d\xe5\x90\xaf";
static const char STR_SHUTDOWN_ZH[] = "\xe5\x85\xb3\xe6\x9c\xba";
static const char STR_SWITCH_TO_MUI_ZH[] = "\xe5\x88\x87\xe6\x8d\xa2\xe5\x88\xb0 MUI";
static const char STR_DISPLAY_OPTIONS_ZH[] = "\xe6\x98\xbe\xe7\xa4\xba\xe9\x80\x89\xe9\xa1\xb9";
static const char STR_LANGUAGE_ZH[] = "\xe8\xaf\xad\xe8\xa8\x80";
static const char STR_CHINESE_ZH[] = "\xe4\xb8\xad\xe6\x96\x87";
static const char STR_ENGLISH_ZH[] = "\xe8\x8b\xb1\xe6\x96\x87";
static const char STR_NO_GPS_ZH[] = "\xe6\x97\xa0 GPS";
static const char STR_GPS_OFF_ZH[] = "GPS \xe5\xb7\xb2\xe5\x85\xb3\xe9\x97\xad";
static const char STR_GPS_NOT_PRESENT_ZH[] = "GPS \xe4\xb8\x8d\xe5\xad\x98\xe5\x9c\xa8";
static const char STR_GPS_IS_DISABLED_ZH[] = "GPS \xe5\xb7\xb2\xe7\xa6\x81\xe7\x94\xa8";
static const char STR_NODE_ZH[] = "\xe8\x8a\x82\xe7\x82\xb9";
static const char STR_HOP_ZH[] = "\xe8\xb7\xb3\xe6\x95\xb0:";
static const char STR_USB_ZH[] = "USB";
static const char STR_BT_OFF_ZH[] = "\xe8\x93\x9d\xe7\x89\x99\xe5\xb7\xb2\xe5\x85\xb3";
static const char STR_WIFI_ZH[] = "WiFi";
static const char STR_WIFI_NOT_CONNECTED_ZH[] = "WiFi: \xe6\x9c\xaa\xe8\xbf\x9e\xe6\x8e\xa5";
static const char STR_WIFI_CONNECTED_ZH[] = "WiFi: \xe5\xb7\xb2\xe8\xbf\x9e\xe6\x8e\xa5";
static const char STR_SSID_ZH[] = "SSID:";
static const char STR_LORA_INFO_ZH[] = "LoRa \xe4\xbf\xa1\xe6\x81\xaf";
static const char STR_LORA_ZH[] = "LoRa";
static const char STR_ROLE_ZH[] = "\xe8\xa7\x92\xe8\x89\xb2:";
static const char STR_FREQ_ZH[] = "\xe9\xa2\x91\xe7\x8e\x87:";
static const char STR_CHUTIL_ZH[] = "\xe4\xbf\xa1\xe9\x81\x93\xe5\x88\xa9\xe7\x94\xa8";
static const char STR_SYSTEM_ZH[] = "\xe7\xb3\xbb\xe7\xbb\x9f";
static const char STR_HEAP_ZH[] = "\xe5\xa0\x86:";
static const char STR_PSRAM_ZH[] = "PSRAM:";
static const char STR_FLASH_ZH[] = "Flash:";
static const char STR_SD_ZH[] = "SD:";
static const char STR_VER_ZH[] = "\xe7\x89\x88\xe6\x9c\xac:";
static const char STR_UP_ZH[] = "\xe8\xbf\x90\xe8\xa1\x8c:";
static const char STR_CLIENT_APP_ZH[] = "\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf";
static const char STR_NO_CONNECTED_ZH[] = "\xe6\x9c\xaa\xe8\xbf\x9e\xe6\x8e\xa5";
static const char STR_LOCKED_ZH[] = "\xe5\xb7\xb2\xe9\x94\x81\xe5\xae\x9a";
static const char STR_CONNECT_TO_UNLOCK_ZH[] = "\xe8\xbf\x9e\xe6\x8e\xa5\xe4\xbb\xa5\xe8\xa7\xa3\xe9\x94\x81";
static const char STR_BATTERY_ZH[] = "\xe7\x94\xb5\xe6\xb1\xa0";
static const char STR_RESUMING_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe6\x81\xa2\xe5\xa4\x8d...";
static const char STR_LAST_HEARD_ZH[] = "\xe6\x9c\x80\xe5\x90\x8e\xe5\x90\xac\xe5\x88\xb0";
static const char STR_HOPS_SIG_ZH[] = "\xe8\xb7\xb3\xe6\x95\xb0/\xe4\xbf\xa1\xe5\x8f\xb7";
static const char STR_HOPS_SIGNAL_ZH[] = "\xe8\xb7\xb3\xe6\x95\xb0/\xe4\xbf\xa1\xe5\x8f\xb7";
static const char STR_NODES_ZH[] = "\xe8\x8a\x82\xe7\x82\xb9";
static const char STR_DISTANCE_ZH[] = "\xe8\xb7\x9d\xe7\xa6\xbb";
static const char STR_BEARINGS_ZH[] = "\xe6\x96\xb9\xe4\xbd\x8d";
static const char STR_CREATING_SSL_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe5\x88\x9b\xe5\xbb\xba SSL \xe8\xaf\x81\xe4\xb9\xa6";
static const char STR_PLEASE_WAIT_ZH[] = "\xe8\xaf\xb7\xe7\xa8\x8d\xe5\x80\x99 . . .";
static const char STR_CRITICAL_FAULT_ZH[] = "\xe4\xb8\xa5\xe9\x87\x8d\xe6\x95\x85\xe9\x9a\x9c";
static const char STR_FOR_HELP_VISIT_ZH[] = "\xe8\xaf\xb7\xe8\xae\xbf\xe9\x97\xae\x4e\xe5\xb8\xae\xe5\x8a\xa9";
static const char STR_UPDATING_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe6\x9b\xb4\xe6\x96\xb0";
static const char STR_PLEASE_BE_PATIENT_ZH[] = "\xe8\xaf\xb7\xe8\x80\x90\xe5\xbf\x83\xe7\xad\x89\xe5\xbe\x85\xef\xbc\x8c\xe4\xb8\x8d\xe8\xa6\x81\xe5\x85\xb3\xe9\x97\xad\xe7\x94\xb5\xe6\xba\x90\xe3\x80\x82";
static const char STR_REBOOT_IN_PROGRESS_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe9\x87\x8d\xe5\x90\xaf...";
static const char STR_POWER_MENU_ZH[] = "\xe7\x94\xb5\xe6\xba\x90";
static const char STR_SYSTEM_MENU_ZH[] = "\xe7\xb3\xbb\xe7\xbb\x9f";
static const char STR_SCREEN_OPTIONS_ZH[] = "\xe5\xb1\x8f\xe5\xb9\x95";
static const char STR_WIFI_TOGGLE_ZH[] = "WiFi";
static const char STR_BLUETOOTH_TOGGLE_ZH[] = "\xe8\x93\x9d\xe7\x89\x99";
static const char STR_NODE_DB_RESET_ZH[] = "\xe9\x87\x8d\xe7\xbd\xae\xe8\x8a\x82\xe7\x82\xb9\xe5\xba\x93";
static const char STR_MANAGE_NODE_ZH[] = "\xe7\xae\xa1\xe7\x90\x86\xe8\x8a\x82\xe7\x82\xb9";
static const char STR_REMOVE_FAVORITE_ZH[] = "\xe5\x8f\x96\xe6\xb6\x88\xe6\x94\xb6\xe8\x97\x8f";
static const char STR_ADD_FAVORITE_ZH[] = "\xe6\xb7\xbb\xe5\x8a\xa0\xe6\x94\xb6\xe8\x97\x8f";
static const char STR_TEST_MENU_ZH[] = "\xe6\xb5\x8b\xe8\xaf\x95";
static const char STR_TRACE_ROUTE_ZH[] = "\xe8\xb7\xaf\xe7\x94\xb1\xe8\xbf\xbd\xe8\xb8\xaa";
static const char STR_THROTTLE_MSG_ZH[] = "\xe6\xb6\x88\xe6\x81\xaf\xe5\xb7\xb2\xe9\x99\x90\xe6\xb5\x81";
static const char STR_MSG_VIEW_MODE_ZH[] = "\xe6\x9f\xa5\xe7\x9c\x8b\xe6\xa8\xa1\xe5\xbc\x8f";
static const char STR_DELETE_MESSAGES_ZH[] = "\xe5\x88\xa0\xe9\x99\xa4\xe6\xb6\x88\xe6\x81\xaf";
static const char STR_NODE_NAME_LENGTH_ZH[] = "\xe5\x90\x8d\xe7\xa7\xb0\xe9\x95\xbf\xe5\xba\xa6";
static const char STR_GPS_MENU_ZH[] = "GPS";
static const char STR_GPS_TOGGLE_ZH[] = "GPS \xe5\xbc\x80\xe5\x85\xb3";
static const char STR_GPS_FORMAT_ZH[] = "GPS \xe6\xa0\xbc\xe5\xbc\x8f";
static const char STR_GPS_SMART_POS_ZH[] = "\xe6\x99\xba\xe8\x83\xbd\xe5\xae\x9a\xe4\xbd\x8d";
static const char STR_GPS_UPDATE_INTERVAL_ZH[] = "\xe6\x9b\xb4\xe6\x96\xb0\xe9\x97\xb4\xe9\x9a\x94";
static const char STR_GPS_POS_BCAST_ZH[] = "\xe4\xbd\x8d\xe7\xbd\xae\xe5\xb9\xbf\xe6\x92\xad";
static const char STR_COMPASS_NORTH_ZH[] = "\xe6\x8c\x87\xe5\x8d\x97\xe9\x92\x88";
static const char STR_BUZZER_MODE_ZH[] = "\xe8\x9c\x82\xe9\xb8\xa3\xe5\x99\xa8";
static const char STR_HAM_MODE_ZH[] = "Ham \xe6\xa8\xa1\xe5\xbc\x8f";
static const char STR_LICENSED_NORMAL_ZH[] = "\xe6\x99\xae\xe9\x80\x9a\xe6\xa8\xa1\xe5\xbc\x8f";
static const char STR_KEY_VERIFY_ZH[] = "\xe9\xaa\x8c\xe8\xaf\x81\xe5\xaf\x86\xe9\x92\xa5";
static const char STR_REGION_UNSET_ZH[] = "\xe8\xaf\xb7\xe9\x80\x89\xe6\x8b\xa9\xe5\x9c\xb0\xe5\x8c\xba";
static const char STR_SELECT_REGION_ZH[] = "\xe8\xaf\xb7\xe9\x80\x89\xe6\x8b\xa9\xe6\x82\xa8\xe7\x9a\x84 LoRa \xe5\x8c\xba\xe5\x9f\x9f:";
static const char STR_REBOOTING_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe9\x87\x8d\xe5\x90\xaf...";
static const char STR_LORA_ACTIONS_ZH[] = "LoRa \xe6\x93\x8d\xe4\xbd\x9c";
static const char STR_SLOT0_ZH[] = "\xe6\xa7\xbd\xe4\xbd\x8d 0 (\xe8\x87\xaa\xe5\x8a\xa8)";
static const char STR_WITH_PRESET_ZH[] = "\xe9\xa2\x84\xe8\xae\xbe\xe6\xb6\x88\xe6\x81\xaf";
static const char STR_WITH_FREETEXT_ZH[] = "\xe8\x87\xaa\xe5\xae\x9a\xe4\xb9\x89";
static const char STR_DELETE_OLDEST_ZH[] = "\xe5\x88\xa0\xe9\x99\xa4\xe6\x9c\x80\xe6\x97\xa7";
static const char STR_DELETE_THIS_CHAT_ZH[] = "\xe5\x88\xa0\xe9\x99\xa4\xe6\xad\xa4\xe8\x81\x8a\xe5\xa4\xa9";
static const char STR_DELETE_ALL_ZH[] = "\xe5\x88\xa0\xe9\x99\xa4\xe5\x85\xa8\xe9\x83\xa8";
static const char STR_DELETE_ALL_CHATS_ZH[] = "\xe5\x88\xa0\xe9\x99\xa4\xe6\x89\x80\xe6\x9c\x89\xe8\x81\x8a\xe5\xa4\xa9";
static const char STR_TEMPORARILY_MUTE_ZH[] = "\xe4\xb8\xb4\xe6\x97\xb6\xe9\x9d\x99\xe9\x9f\xb3";
static const char STR_UNMUTE_ZH[] = "\xe5\x8f\x96\xe6\xb6\x88\xe9\x9d\x99\xe9\x9f\xb3";
static const char STR_TOGGLE_BACKLIGHT_ZH[] = "\xe8\x83\x8c\xe5\x85\x89\xe5\xbc\x80\xe5\x85\xb3";
static const char STR_SLEEP_SCREEN_ZH[] = "\xe4\xbc\x91\xe7\x9c\xa0\xe5\xb1\x8f\xe5\xb9\x95";
static const char STR_SEND_POSITION_ZH[] = "\xe5\x8f\x91\xe9\x80\x81\xe4\xbd\x8d\xe7\xbd\xae";
static const char STR_SEND_NODE_INFO_ZH[] = "\xe5\x8f\x91\xe9\x80\x81\xe8\x8a\x82\xe7\x82\xb9\xe4\xbf\xa1\xe6\x81\xaf";
static const char STR_NEW_PRESET_MSG_ZH[] = "\xe6\x96\xb0\xe9\xa2\x84\xe8\xae\xbe\xe6\xb6\x88\xe6\x81\xaf";
static const char STR_NEW_FREETEXT_MSG_ZH[] = "\xe6\x96\xb0\xe8\x87\xaa\xe5\xae\x9a\xe4\xb9\x89\xe6\xb6\x88\xe6\x81\xaf";
static const char STR_NOTIFICATIONS_ZH[] = "\xe9\x80\x9a\xe7\x9f\xa5";
static const char STR_POWER_ZH[] = "\xe7\x94\xb5\xe6\xba\x90";
static const char STR_REBOOT_SHUTDOWN_ZH[] = "\xe9\x87\x8d\xe5\x90\xaf/\xe5\x85\xb3\xe6\x9c\xba";
static const char STR_GO_TO_CHAT_ZH[] = "\xe8\xbf\x9b\xe5\x85\xa5\xe8\x81\x8a\xe5\xa4\xa9";
static const char STR_NEW_PRESET_ZH[] = "\xe6\x96\xb0\xe9\xa2\x84\xe8\xae\xbe";
static const char STR_FAVORITE_ZH[] = "\xe6\x94\xb6\xe8\x97\x8f";
static const char STR_UNMUTE_NOTIFICATIONS_ZH[] = "\xe5\x8f\x96\xe6\xb6\x88\xe9\x80\x9a\xe7\x9f\xa5\xe9\x9d\x99\xe9\x9f\xb3";
static const char STR_MUTE_NOTIFICATIONS_ZH[] = "\xe9\x80\x9a\xe7\x9f\xa5\xe9\x9d\x99\xe9\x9f\xb3";
static const char STR_UNIGNORE_NODE_ZH[] = "\xe5\x8f\x96\xe6\xb6\x88\xe5\xbf\xbd\xe7\x95\xa5";
static const char STR_IGNORE_NODE_ZH[] = "\xe5\xbf\xbd\xe7\x95\xa5\xe8\x8a\x82\xe7\x82\xb9";
static const char STR_NUMBER_PICKER_ZH[] = "\xe6\x95\xb0\xe5\xad\x97\xe9\x80\x89\xe6\x8b\xa9";
static const char STR_TEST_ANNOUNCE_ZH[] = "\xe6\xb5\x8b\xe8\xaf\x95\xe5\x85\xac\xe5\x91\x8a";
static const char STR_NODE_ACTIONS_ZH[] = "\xe8\x8a\x82\xe7\x82\xb9\xe6\x93\x8d\xe4\xbd\x9c/\xe8\xae\xbe\xe7\xbd\xae";
static const char STR_SHOW_NAME_LENGTH_ZH[] = "\xe6\x98\xbe\xe7\xa4\xba\xe9\x95\xbf/\xe7\x9f\xad\xe5\x90\x8d";
static const char STR_BLUETOOTH_ZH[] = "\xe8\x93\x9d\xe7\x89\x99";
static const char STR_NO_MESSAGES_ZH[] = "\xe6\x97\xa0\xe6\xb6\x88\xe6\x81\xaf";
inline const char *getStringZH(StringKey key)
{
switch (key) {
case StringKey::Back:
return STR_BACK_ZH;
case StringKey::OK:
return STR_OK_ZH;
case StringKey::GotIt:
return STR_GOT_IT_ZH;
case StringKey::WelcomeToMeshtastic:
return STR_WELCOME_TO_MESHTASTIC_ZH;
case StringKey::SwipeToNavigate:
return STR_SWIPE_TO_NAVIGATE_ZH;
case StringKey::ClickToNavigate:
return STR_CLICK_TO_NAVIGATE_ZH;
case StringKey::UseSelectButton:
return STR_USE_SELECT_BUTTON_ZH;
case StringKey::DeviceRole:
return STR_DEVICE_ROLE_ZH;
case StringKey::RadioPreset:
return STR_RADIO_PRESET_ZH;
case StringKey::FrequencySlot:
return STR_FREQUENCY_SLOT_ZH;
case StringKey::LoRaRegion:
return STR_LORA_REGION_ZH;
case StringKey::Client:
return STR_CLIENT_ZH;
case StringKey::ClientMute:
return STR_CLIENT_MUTE_ZH;
case StringKey::LostAndFound:
return STR_LOST_AND_FOUND_ZH;
case StringKey::Tracker:
return STR_TRACKER_ZH;
case StringKey::Hour12:
return STR_12HOUR_ZH;
case StringKey::Hour24:
return STR_24HOUR_ZH;
case StringKey::ClockAction:
return STR_CLOCK_ACTION_ZH;
case StringKey::ClockFace:
return STR_CLOCK_FACE_ZH;
case StringKey::TimeFormat:
return STR_TIME_FORMAT_ZH;
case StringKey::Timezone:
return STR_TIMEZONE_ZH;
case StringKey::MessageAction:
return STR_MESSAGE_ACTION_ZH;
case StringKey::Reply:
return STR_REPLY_ZH;
case StringKey::ViewChats:
return STR_VIEW_CHATS_ZH;
case StringKey::UnmuteChannel:
return STR_UNMUTE_CHANNEL_ZH;
case StringKey::MuteChannel:
return STR_MUTE_CHANNEL_ZH;
case StringKey::Delete:
return STR_DELETE_ZH;
case StringKey::ReadAloud:
return STR_READ_ALOUD_ZH;
case StringKey::Brightness:
return STR_BRIGHTNESS_ZH;
case StringKey::FrameVisibility:
return STR_FRAME_VISIBILITY_ZH;
case StringKey::DisplayUnits:
return STR_DISPLAY_UNITS_ZH;
case StringKey::MessageBubbles:
return STR_MESSAGE_BUBBLES_ZH;
case StringKey::Theme:
return STR_THEME_ZH;
case StringKey::Reboot:
return STR_REBOOT_ZH;
case StringKey::Shutdown:
return STR_SHUTDOWN_ZH;
case StringKey::SwitchToMUI:
return STR_SWITCH_TO_MUI_ZH;
case StringKey::DisplayOptions:
return STR_DISPLAY_OPTIONS_ZH;
case StringKey::Language:
return STR_LANGUAGE_ZH;
case StringKey::Chinese:
return STR_CHINESE_ZH;
case StringKey::English:
return STR_ENGLISH_ZH;
case StringKey::NoGPS:
return STR_NO_GPS_ZH;
case StringKey::GPSoff:
return STR_GPS_OFF_ZH;
case StringKey::GPSnotPresent:
return STR_GPS_NOT_PRESENT_ZH;
case StringKey::GPSisDisabled:
return STR_GPS_IS_DISABLED_ZH;
case StringKey::Node:
return STR_NODE_ZH;
case StringKey::Hop:
return STR_HOP_ZH;
case StringKey::USB:
return STR_USB_ZH;
case StringKey::BT_off:
return STR_BT_OFF_ZH;
case StringKey::WiFi:
return STR_WIFI_ZH;
case StringKey::WiFiNotConnected:
return STR_WIFI_NOT_CONNECTED_ZH;
case StringKey::WiFiConnected:
return STR_WIFI_CONNECTED_ZH;
case StringKey::SSID:
return STR_SSID_ZH;
case StringKey::LoRaInfo:
return STR_LORA_INFO_ZH;
case StringKey::LoRa:
return STR_LORA_ZH;
case StringKey::Role:
return STR_ROLE_ZH;
case StringKey::Freq:
return STR_FREQ_ZH;
case StringKey::ChUtil:
return STR_CHUTIL_ZH;
case StringKey::System:
return STR_SYSTEM_ZH;
case StringKey::Heap:
return STR_HEAP_ZH;
case StringKey::PSRAM:
return STR_PSRAM_ZH;
case StringKey::Flash:
return STR_FLASH_ZH;
case StringKey::SD:
return STR_SD_ZH;
case StringKey::Ver:
return STR_VER_ZH;
case StringKey::Up:
return STR_UP_ZH;
case StringKey::ClientApp:
return STR_CLIENT_APP_ZH;
case StringKey::NoConnected:
return STR_NO_CONNECTED_ZH;
case StringKey::LOCKED:
return STR_LOCKED_ZH;
case StringKey::ConnectToUnlock:
return STR_CONNECT_TO_UNLOCK_ZH;
case StringKey::Battery:
return STR_BATTERY_ZH;
case StringKey::Resuming:
return STR_RESUMING_ZH;
case StringKey::LastHeard:
return STR_LAST_HEARD_ZH;
case StringKey::HopsSig:
return STR_HOPS_SIG_ZH;
case StringKey::HopsSignal:
return STR_HOPS_SIGNAL_ZH;
case StringKey::Nodes:
return STR_NODES_ZH;
case StringKey::Distance:
return STR_DISTANCE_ZH;
case StringKey::Bearings:
return STR_BEARINGS_ZH;
case StringKey::CreatingSSLCert:
return STR_CREATING_SSL_ZH;
case StringKey::PleaseWait:
return STR_PLEASE_WAIT_ZH;
case StringKey::CriticalFault:
return STR_CRITICAL_FAULT_ZH;
case StringKey::ForHelpVisit:
return STR_FOR_HELP_VISIT_ZH;
case StringKey::Updating:
return STR_UPDATING_ZH;
case StringKey::PleaseBePatient:
return STR_PLEASE_BE_PATIENT_ZH;
case StringKey::RebootInProgress:
return STR_REBOOT_IN_PROGRESS_ZH;
case StringKey::PowerMenu:
return STR_POWER_MENU_ZH;
case StringKey::SystemMenu:
return STR_SYSTEM_MENU_ZH;
case StringKey::ScreenOptions:
return STR_SCREEN_OPTIONS_ZH;
case StringKey::WifiToggle:
return STR_WIFI_TOGGLE_ZH;
case StringKey::BluetoothToggle:
return STR_BLUETOOTH_TOGGLE_ZH;
case StringKey::NodeDbReset:
return STR_NODE_DB_RESET_ZH;
case StringKey::ManageNode:
return STR_MANAGE_NODE_ZH;
case StringKey::RemoveFavorite:
return STR_REMOVE_FAVORITE_ZH;
case StringKey::AddFavorite:
return STR_ADD_FAVORITE_ZH;
case StringKey::TestMenu:
return STR_TEST_MENU_ZH;
case StringKey::TraceRoute:
return STR_TRACE_ROUTE_ZH;
case StringKey::ThrottleMessage:
return STR_THROTTLE_MSG_ZH;
case StringKey::MessageViewMode:
return STR_MSG_VIEW_MODE_ZH;
case StringKey::DeleteMessages:
return STR_DELETE_MESSAGES_ZH;
case StringKey::NodeNameLength:
return STR_NODE_NAME_LENGTH_ZH;
case StringKey::GPSMenu:
return STR_GPS_MENU_ZH;
case StringKey::GPSToggle:
return STR_GPS_TOGGLE_ZH;
case StringKey::GPSFormat:
return STR_GPS_FORMAT_ZH;
case StringKey::GPSSmartPosition:
return STR_GPS_SMART_POS_ZH;
case StringKey::GPSUpdateInterval:
return STR_GPS_UPDATE_INTERVAL_ZH;
case StringKey::GPSPositionBroadcast:
return STR_GPS_POS_BCAST_ZH;
case StringKey::CompassNorth:
return STR_COMPASS_NORTH_ZH;
case StringKey::BuzzerMode:
return STR_BUZZER_MODE_ZH;
case StringKey::HamModeConfirm:
return STR_HAM_MODE_ZH;
case StringKey::LicensedToNormal:
return STR_LICENSED_NORMAL_ZH;
case StringKey::KeyVerification:
return STR_KEY_VERIFY_ZH;
case StringKey::RegionUnset:
return STR_REGION_UNSET_ZH;
case StringKey::SelectRegionMessage:
return STR_SELECT_REGION_ZH;
case StringKey::Rebooting:
return STR_REBOOTING_ZH;
case StringKey::LoRaActions:
return STR_LORA_ACTIONS_ZH;
case StringKey::Slot0:
return STR_SLOT0_ZH;
case StringKey::WithPreset:
return STR_WITH_PRESET_ZH;
case StringKey::WithFreetext:
return STR_WITH_FREETEXT_ZH;
case StringKey::DeleteOldest:
return STR_DELETE_OLDEST_ZH;
case StringKey::DeleteThisChat:
return STR_DELETE_THIS_CHAT_ZH;
case StringKey::DeleteAll:
return STR_DELETE_ALL_ZH;
case StringKey::DeleteAllChats:
return STR_DELETE_ALL_CHATS_ZH;
case StringKey::TemporarilyMute:
return STR_TEMPORARILY_MUTE_ZH;
case StringKey::Unmute:
return STR_UNMUTE_ZH;
case StringKey::ToggleBacklight:
return STR_TOGGLE_BACKLIGHT_ZH;
case StringKey::SleepScreen:
return STR_SLEEP_SCREEN_ZH;
case StringKey::SendPosition:
return STR_SEND_POSITION_ZH;
case StringKey::SendNodeInfo:
return STR_SEND_NODE_INFO_ZH;
case StringKey::NewPresetMsg:
return STR_NEW_PRESET_MSG_ZH;
case StringKey::NewFreetextMsg:
return STR_NEW_FREETEXT_MSG_ZH;
case StringKey::Notifications:
return STR_NOTIFICATIONS_ZH;
case StringKey::Power:
return STR_POWER_ZH;
case StringKey::RebootShutdown:
return STR_REBOOT_SHUTDOWN_ZH;
case StringKey::GoToChat:
return STR_GO_TO_CHAT_ZH;
case StringKey::NewPreset:
return STR_NEW_PRESET_ZH;
case StringKey::Favorite:
return STR_FAVORITE_ZH;
case StringKey::UnmuteNotifications:
return STR_UNMUTE_NOTIFICATIONS_ZH;
case StringKey::MuteNotifications:
return STR_MUTE_NOTIFICATIONS_ZH;
case StringKey::UnignoreNode:
return STR_UNIGNORE_NODE_ZH;
case StringKey::IgnoreNode:
return STR_IGNORE_NODE_ZH;
case StringKey::NumberPicker:
return STR_NUMBER_PICKER_ZH;
case StringKey::TestAnnounce:
return STR_TEST_ANNOUNCE_ZH;
case StringKey::NodeActionsSettings:
return STR_NODE_ACTIONS_ZH;
case StringKey::ShowNameLength:
return STR_SHOW_NAME_LENGTH_ZH;
case StringKey::Bluetooth:
return STR_BLUETOOTH_ZH;
case StringKey::NoMessages:
return STR_NO_MESSAGES_ZH;
default:
return "?";
}
}
} // namespace l10n
} // namespace meshtastic
+97
View File
@@ -0,0 +1,97 @@
#include "HapticFeedback.h"
#ifdef HAPTIC_FEEDBACK_PIN
#include <Arduino.h>
#ifdef HAPTIC_FEEDBACK_ACTIVE_LOW
#define HAPTIC_FEEDBACK_ON_STATE LOW
#define HAPTIC_FEEDBACK_OFF_STATE HIGH
#else
#define HAPTIC_FEEDBACK_ON_STATE HIGH
#define HAPTIC_FEEDBACK_OFF_STATE LOW
#endif
HapticFeedback *hapticFeedback = nullptr;
void initHapticFeedback()
{
if (!hapticFeedback)
hapticFeedback = new HapticFeedback();
}
HapticFeedback::HapticFeedback() : concurrency::OSThread("Haptic")
{
pinMode(HAPTIC_FEEDBACK_PIN, OUTPUT);
digitalWrite(HAPTIC_FEEDBACK_PIN, HAPTIC_FEEDBACK_OFF_STATE);
}
void HapticFeedback::motorWrite(bool on)
{
digitalWrite(HAPTIC_FEEDBACK_PIN, on ? HAPTIC_FEEDBACK_ON_STATE : HAPTIC_FEEDBACK_OFF_STATE);
}
void HapticFeedback::pulse(uint16_t durationMs)
{
motorWrite(true);
pulseOffAt = millis() + durationMs;
if (pulseOffAt == 0) // 0 is the "no pulse" sentinel
pulseOffAt = 1;
scheduleNext();
}
void HapticFeedback::armDelayedPulse(uint16_t delayMs, uint16_t durationMs)
{
delayedPulseAt = millis() + delayMs;
if (delayedPulseAt == 0)
delayedPulseAt = 1;
delayedPulseDuration = durationMs;
scheduleNext();
}
void HapticFeedback::cancelDelayedPulse()
{
delayedPulseAt = 0;
}
void HapticFeedback::scheduleNext()
{
uint32_t now = millis();
uint32_t next = 0;
if (pulseOffAt != 0)
next = pulseOffAt;
if (delayedPulseAt != 0 && (next == 0 || (int32_t)(delayedPulseAt - next) < 0))
next = delayedPulseAt;
if (next == 0)
return;
int32_t delay = (int32_t)(next - now);
setIntervalFromNow(delay > 0 ? (unsigned long)delay : 0);
}
int32_t HapticFeedback::runOnce()
{
uint32_t now = millis();
if (pulseOffAt != 0 && (int32_t)(now - pulseOffAt) >= 0) {
motorWrite(false);
pulseOffAt = 0;
}
if (delayedPulseAt != 0 && (int32_t)(now - delayedPulseAt) >= 0) {
uint16_t dur = delayedPulseDuration;
delayedPulseAt = 0;
pulse(dur);
}
uint32_t next = 0;
if (pulseOffAt != 0)
next = pulseOffAt;
if (delayedPulseAt != 0 && (next == 0 || (int32_t)(delayedPulseAt - next) < 0))
next = delayedPulseAt;
if (next == 0)
return 60 * 1000;
int32_t delay = (int32_t)(next - now);
return delay > 0 ? delay : 0;
}
#endif // HAPTIC_FEEDBACK_PIN
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "configuration.h"
#ifdef HAPTIC_FEEDBACK_PIN
#include "concurrency/OSThread.h"
#include <stdint.h>
// Non-blocking pulses on a GPIO vibration motor. HAPTIC_FEEDBACK_ACTIVE_LOW inverts polarity.
class HapticFeedback : public concurrency::OSThread
{
public:
HapticFeedback();
void pulse(uint16_t durationMs = 30);
void armDelayedPulse(uint16_t delayMs, uint16_t durationMs = 30);
void cancelDelayedPulse();
protected:
int32_t runOnce() override;
private:
uint32_t pulseOffAt = 0;
uint32_t delayedPulseAt = 0;
uint16_t delayedPulseDuration = 0;
void motorWrite(bool on);
// Reschedule to the soonest pending event so later arms don't clobber earlier wakes.
void scheduleNext();
};
extern HapticFeedback *hapticFeedback;
void initHapticFeedback();
#endif // HAPTIC_FEEDBACK_PIN
+11
View File
@@ -2,6 +2,7 @@
#include "PowerFSM.h" // needed for event trigger
#include "configuration.h"
#include "graphics/Screen.h"
#include "input/HapticFeedback.h"
#include "modules/ExternalNotificationModule.h"
#ifdef MESHTASTIC_LOCKDOWN
#include "security/LockdownDisplay.h"
@@ -256,6 +257,16 @@ void InputBroker::Init()
}
touchBacklightActive = false;
};
#endif
#if defined(HAPTIC_FEEDBACK_PIN)
// Blip on touch, second blip when long-press fires (500 ms = touchConfig.longPressTime default).
touchConfig.suppressLeadUpSound = true;
initHapticFeedback();
touchConfig.onPress = []() {
hapticFeedback->pulse(80);
hapticFeedback->armDelayedPulse(500, 80);
};
touchConfig.onRelease = []() { hapticFeedback->cancelDelayedPulse(); };
#endif
TouchButtonThread->initButton(touchConfig);
#endif
+16 -13
View File
@@ -1071,19 +1071,6 @@ void setup()
digitalWrite(RF95_FAN_EN, LOW ^ 0);
#endif
#ifndef ARCH_PORTDUINO
// Initialize Wifi
#if HAS_WIFI
initWifi();
#endif
#if HAS_ETHERNET
// Initialize Ethernet
initEthernet();
#endif
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
// Start web server thread.
webServerThread = new WebServerThread();
@@ -1121,6 +1108,22 @@ void setup()
setCPUFast(false); // 80MHz is fine for our slow peripherals
#endif
#ifndef ARCH_PORTDUINO
// Force BT init before WiFi so NimBLE gets clean heap
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
setBluetoothEnable(true);
#endif
// Initialize Wifi after PowerFSM so BT gets clean heap during init
#if HAS_WIFI
initWifi();
#endif
#if HAS_ETHERNET
// Initialize Ethernet
initEthernet();
#endif
#endif
#ifdef ARDUINO_ARCH_ESP32
LOG_DEBUG("Free heap : %7d bytes", ESP.getFreeHeap());
LOG_DEBUG("Free PSRAM : %7d bytes", ESP.getFreePsram());
+31 -8
View File
@@ -40,6 +40,17 @@
#include "Throttle.h"
#include <RTC.h>
namespace
{
constexpr uint8_t FILES_MANIFEST_LEVELS = 3;
constexpr size_t FILES_MANIFEST_MAX_COUNT = 64;
void releaseFilesManifest(std::vector<meshtastic_FileInfo> &filesManifest)
{
std::vector<meshtastic_FileInfo>().swap(filesManifest);
}
} // namespace
// Flag to indicate a heartbeat was received and we should send queue status
bool heartbeatReceived = false;
@@ -298,18 +309,31 @@ void PhoneAPI::handleStartConfig()
state = STATE_SEND_MY_INFO;
}
pauseBluetoothLogging = true;
spiLock->lock();
#if defined(MESHTASTIC_EXCLUDE_FILES_MANIFEST)
// Skip the recursive FS walk. Used by platforms whose Zephyr LittleFS
// backend can't safely traverse a deep tree (e.g. nRF54L15) and platforms
// that don't support OTA browsing — the manifest is only consumed by
// companion apps for those flows.
filesManifest.clear();
releaseFilesManifest(filesManifest);
#else
filesManifest = getFiles("/", 10);
// Manifest is never read on the node-info-only path (STATE_SEND_FILEMANIFEST
// short-circuits to sendConfigComplete), so skip the SPI lock + FS walk.
if (config_nonce != SPECIAL_NONCE_ONLY_NODES) {
bool filesManifestLimited = false;
{
concurrency::LockGuard guard(spiLock);
filesManifest = getFiles("/", FILES_MANIFEST_LEVELS, FILES_MANIFEST_MAX_COUNT, &filesManifestLimited);
}
if (filesManifestLimited) {
LOG_WARN("Got %zu files in manifest (limited to %zu entries/depth %u)", filesManifest.size(),
FILES_MANIFEST_MAX_COUNT, static_cast<unsigned>(FILES_MANIFEST_LEVELS));
} else {
LOG_DEBUG("Got %zu files in manifest", filesManifest.size());
}
} else {
releaseFilesManifest(filesManifest);
}
#endif
spiLock->unlock();
LOG_DEBUG("Got %d files in manifest", filesManifest.size());
LOG_INFO("Start API client config millis=%u", millis());
// Protect against concurrent BLE callbacks: they run in NimBLE's FreeRTOS task and also touch nodeInfoQueue.
@@ -377,8 +401,7 @@ void PhoneAPI::close()
replayPhase = REPLAY_PHASE_IDLE;
}
packetForPhone = NULL;
filesManifest.clear();
filesManifest.shrink_to_fit();
releaseFilesManifest(filesManifest);
lastPortNumToRadio.clear();
fromRadioNum = 0;
config_nonce = 0;
@@ -943,7 +966,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// ONLY_NODES variants skip the manifest.
if (config_state == filesManifest.size() || config_nonce == SPECIAL_NONCE_ONLY_NODES) {
config_state = 0;
filesManifest.clear();
releaseFilesManifest(filesManifest);
// Skip to complete packet
sendConfigComplete();
} else {
+30 -27
View File
@@ -52,7 +52,8 @@ void StreamAPI::writeStream()
do {
// Send every packet we can
len = getFromRadio(txBuf + HEADER_LEN);
emitTxBuffer(len);
if (len != 0 && !emitTxBuffer(len))
break;
} while (len);
}
}
@@ -169,21 +170,36 @@ int32_t StreamAPI::readStream()
/**
* Send the current txBuffer over our stream
*/
void StreamAPI::emitTxBuffer(size_t len)
bool StreamAPI::writeFrame(uint8_t *buf, size_t len)
{
if (len != 0) {
txBuf[0] = START1;
txBuf[1] = START2;
txBuf[2] = (len >> 8) & 0xff;
txBuf[3] = len & 0xff;
if (len == 0 || !canWrite)
return false;
auto totalLen = len + HEADER_LEN;
// Serialize stream writes against `emitLogRecord` so a LOG_ firing
// mid-packet-emission can't interleave bytes on the wire.
concurrency::LockGuard guard(&streamLock);
stream->write(txBuf, totalLen);
buf[0] = START1;
buf[1] = START2;
buf[2] = (len >> 8) & 0xff;
buf[3] = len & 0xff;
auto totalLen = len + HEADER_LEN;
// Serialize write-readiness checks, writes and write-failure handling
// against concurrent stream writes/close.
concurrency::LockGuard guard(&streamLock);
if (!canWriteFrame(totalLen))
return false;
size_t written = stream->write(buf, totalLen);
if (written == totalLen) {
stream->flush();
return true;
}
onFrameWriteFailed(totalLen, written);
return false;
}
bool StreamAPI::emitTxBuffer(size_t len)
{
return writeFrame(txBuf, len);
}
void StreamAPI::emitRebooted()
@@ -221,20 +237,7 @@ void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src,
size_t len =
pb_encode_to_bytes(txBufLog + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratchLog);
if (len != 0) {
txBufLog[0] = START1;
txBufLog[1] = START2;
txBufLog[2] = (len >> 8) & 0xff;
txBufLog[3] = len & 0xff;
auto totalLen = len + HEADER_LEN;
// Serialize stream writes against `emitTxBuffer` so a packet
// emission in flight on another task doesn't interleave bytes
// with this log record.
concurrency::LockGuard guard(&streamLock);
stream->write(txBufLog, totalLen);
stream->flush();
}
writeFrame(txBufLog, len);
}
/// Hookable to find out when connection changes
@@ -249,4 +252,4 @@ void StreamAPI::onConnectionChanged(bool connected)
// received a packet in a while
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
}
}
}
+7 -2
View File
@@ -80,7 +80,7 @@ class StreamAPI : public PhoneAPI
/**
* Send the current txBuffer over our stream
*/
void emitTxBuffer(size_t len);
bool emitTxBuffer(size_t len);
/// Are we allowed to write packets to our output stream (subclasses can turn this off - i.e. SerialConsole)
bool canWrite = true;
@@ -91,7 +91,12 @@ class StreamAPI : public PhoneAPI
/// Low level function to emit a protobuf encapsulated log record
void emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg);
virtual bool canWriteFrame(size_t frameLen) { return true; }
virtual void onFrameWriteFailed(size_t frameLen, size_t writtenLen) {}
private:
bool writeFrame(uint8_t *buf, size_t len);
/// Dedicated scratch + tx buffer for LogRecord emission.
///
/// The main packet emission path (`writeStream` -> `getFromRadio` ->
@@ -113,4 +118,4 @@ class StreamAPI : public PhoneAPI
meshtastic_FromRadio fromRadioScratchLog = {};
uint8_t txBufLog[MAX_STREAM_BUF_SIZE] = {0};
concurrency::Lock streamLock;
};
};
+28 -2
View File
@@ -22,12 +22,38 @@ template <typename T> void ServerAPI<T>::close()
StreamAPI::close();
}
/// Check the current underlying physical link to see if the client is currently connected
/// Check the current underlying physical link to see if the client is currently
/// connected
template <typename T> bool ServerAPI<T>::checkIsConnected()
{
return client.connected();
}
template <typename T> bool ServerAPI<T>::canWriteFrame(size_t)
{
// Only a dropped link is a reason to refuse a write up front. A full transmit
// buffer (availableForWrite() == 0) is normal backpressure, not a dead socket,
// so we must not close the connection on it. A genuinely failed write is
// detected after the fact in onFrameWriteFailed().
if (!client.connected()) {
canWrite = false;
enabled = false;
LOG_WARN("TCP client disconnected before write, closing API service");
close();
return false;
}
return true;
}
template <typename T> void ServerAPI<T>::onFrameWriteFailed(size_t frameLen, size_t writtenLen)
{
canWrite = false;
enabled = false;
LOG_WARN("TCP client write short (%lu/%lu bytes), closing API service", (unsigned long)writtenLen, (unsigned long)frameLen);
close();
}
template <class T> int32_t ServerAPI<T>::runOnce()
{
if (client.connected()) {
@@ -95,4 +121,4 @@ template <class T, class U> int32_t APIServerPort<T, U>::runOnce()
waitTime = 100;
#endif
return 100; // only check occasionally for incoming connections
}
}
+2
View File
@@ -29,6 +29,8 @@ template <class T> class ServerAPI : public StreamAPI, private concurrency::OSTh
/// We override this method to prevent publishing EVENT_SERIAL_CONNECTED/DISCONNECTED for wifi links (we want the board to
/// stay in the POWERED state to prevent disabling wifi)
virtual void onConnectionChanged(bool connected) override {}
virtual bool canWriteFrame(size_t frameLen) override;
virtual void onFrameWriteFailed(size_t frameLen, size_t writtenLen) override;
virtual int32_t runOnce() override; // Check for dropped client connections
};
-23
View File
@@ -1,23 +0,0 @@
// Free up some precious space in the iram0_0_seg memory segment
#include <stdint.h>
#include <esp_attr.h>
#include <esp_flash.h>
#include <spi_flash_chip_driver.h>
#define IRAM_SECTION section(".iram1.stub")
IRAM_ATTR esp_err_t stub_probe(esp_flash_t *chip, uint32_t flash_id)
{
return ESP_ERR_NOT_FOUND;
}
const spi_flash_chip_t stub_flash_chip __attribute__((IRAM_SECTION)) = {
.name = "stub",
.probe = stub_probe,
};
extern const spi_flash_chip_t __wrap_esp_flash_chip_gd __attribute__((IRAM_SECTION, alias("stub_flash_chip")));
extern const spi_flash_chip_t __wrap_esp_flash_chip_issi __attribute__((IRAM_SECTION, alias("stub_flash_chip")));
extern const spi_flash_chip_t __wrap_esp_flash_chip_winbond __attribute__((IRAM_SECTION, alias("stub_flash_chip")));
-9
View File
@@ -33,13 +33,7 @@ void variant_shutdown() {}
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH
void setBluetoothEnable(bool enable)
{
#if defined(USE_WS5500) || defined(USE_CH390D)
if ((config.bluetooth.enabled == true) && (config.network.wifi_enabled == false))
#elif HAS_WIFI
if (!isWifiAvailable() && config.bluetooth.enabled == true)
#else
if (config.bluetooth.enabled == true)
#endif
{
if (!nimbleBluetooth) {
nimbleBluetooth = new NimbleBluetooth();
@@ -48,9 +42,6 @@ void setBluetoothEnable(bool enable)
powerMon->setState(meshtastic_PowerMon_State_BT_On);
nimbleBluetooth->setup();
}
// For ESP32, no way to recover from bluetooth shutdown without reboot
// BLE advertising automatically stops when MCU enters light-sleep(?)
// For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse
}
}
#else
+2
View File
@@ -89,6 +89,8 @@
#define HW_VENDOR meshtastic_HardwareModel_T_ECHO_CARD
#elif defined(TTGO_T_ECHO_PLUS)
#define HW_VENDOR meshtastic_HardwareModel_T_ECHO_PLUS
#elif defined(T_IMPULSE_PLUS)
#define HW_VENDOR meshtastic_HardwareModel_T_IMPULSE_PLUS
#elif defined(ELECROW_ThinkNode_M1)
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1
#elif defined(ELECROW_ThinkNode_M3)
+109
View File
@@ -0,0 +1,109 @@
#include "SGM41562.h"
#ifdef HAS_SGM41562
#include <Arduino.h>
SGM41562 *sgm41562 = nullptr;
bool initSGM41562(TwoWire &wire)
{
if (sgm41562)
return true;
sgm41562 = new SGM41562();
if (!sgm41562->begin(wire)) {
delete sgm41562;
sgm41562 = nullptr;
return false;
}
return true;
}
bool SGM41562::readReg(uint8_t reg, uint8_t &value)
{
wire_->beginTransmission(address_);
wire_->write(reg);
if (wire_->endTransmission(false) != 0)
return false;
if (wire_->requestFrom((int)address_, 1) != 1)
return false;
value = wire_->read();
return true;
}
bool SGM41562::writeReg(uint8_t reg, uint8_t value)
{
wire_->beginTransmission(address_);
wire_->write(reg);
wire_->write(value);
return wire_->endTransmission() == 0;
}
bool SGM41562::updateReg(uint8_t reg, uint8_t mask, uint8_t value)
{
uint8_t cur;
if (!readReg(reg, cur))
return false;
cur = (cur & ~mask) | (value & mask);
return writeReg(reg, cur);
}
bool SGM41562::begin(TwoWire &wire, uint8_t address)
{
wire_ = &wire;
address_ = address;
uint8_t id;
if (!readReg(REG_DEVICE_ID, id)) {
LOG_WARN("SGM41562: I2C read failed at 0x%02X", address_);
return false;
}
if (id != DEVICE_ID_EXPECTED) {
LOG_WARN("SGM41562: unexpected device ID 0x%02X (expected 0x%02X)", id, DEVICE_ID_EXPECTED);
return false;
}
LOG_INFO("SGM41562: detected at 0x%02X (id 0x%02X)", address_, id);
// Mirror the vendor reference init sequence: PCB OTP off, NTC off,
// watchdog off, charger enabled. These match LilyGo's stock firmware
// for the T-Impulse Plus.
delay(120);
writeReg(REG_SYS_VOLTAGE_REG, 0xB7);
writeReg(REG_MISC_OP_CONTROL, 0x40);
writeReg(REG_CHARGE_TERM_TIMER, 0x1A);
writeReg(REG_POWER_ON_CFG, 0xA4);
return refresh();
}
bool SGM41562::refresh()
{
uint32_t now = millis();
if (lastRefreshMs_ != 0 && (now - lastRefreshMs_) < 250)
return true; // cached
lastRefreshMs_ = now == 0 ? 1 : now;
uint8_t status, fault;
if (!readReg(REG_SYSTEM_STATUS, status))
return false;
if (!readReg(REG_FAULT, fault))
return false;
chargeStatus_ = static_cast<ChargeStatus>((status >> SYS_STATUS_CHRG_SHIFT) & SYS_STATUS_CHRG_MASK);
inputPowerGood_ = (status & SYS_STATUS_PG) != 0;
thermalReg_ = (status & SYS_STATUS_THERM_REG) != 0;
faultMask_ = fault & 0x3F; // bits [7:6] are enter_ship_time config, not faults
return true;
}
bool SGM41562::setChargeEnable(bool enable)
{
return updateReg(REG_POWER_ON_CFG, POWER_ON_CFG_CHG_DISABLE, enable ? 0x00 : POWER_ON_CFG_CHG_DISABLE);
}
bool SGM41562::setShippingModeEnable(bool enable)
{
return updateReg(REG_MISC_OP_CONTROL, MISC_OP_SHIPPING_MODE, enable ? MISC_OP_SHIPPING_MODE : 0x00);
}
#endif // HAS_SGM41562
+102
View File
@@ -0,0 +1,102 @@
#pragma once
#include "configuration.h"
#ifdef HAS_SGM41562
#include <Wire.h>
#include <stdint.h>
// SG Micro SGM41562 — single-cell Li-ion buck charger, I²C-controlled, no
// fuel gauge. This driver exposes status (charging / input good / fault),
// charge enable, and shipping-mode control. Battery voltage/percent still
// come from the platform ADC path; the charger is plumbed in as a
// side-channel for isCharging()/isVbusIn() in AnalogBatteryLevel.
//
// Reference: SGM41562 datasheet (Cmd map + bit fields cross-verified against
// LilyGo's `Cpp_Bus_Driver::Sgm41562xx` driver, which is what their vendor
// example for this board uses).
#ifndef SGM41562_ADDR
#define SGM41562_ADDR 0x03 // Per datasheet — unusual but correct
#endif
#ifndef SGM41562_WIRE
#define SGM41562_WIRE Wire1 // Most boards put the PMU on the secondary bus
#endif
class SGM41562
{
public:
enum class ChargeStatus : uint8_t {
NotCharging = 0b00,
Precharge = 0b01,
FastCharge = 0b10,
ChargeDone = 0b11,
};
bool begin(TwoWire &wire, uint8_t address = SGM41562_ADDR);
// Re-read the system status + fault registers. Throttled internally to
// at most one I²C transaction per 250 ms — call as often as you like.
bool refresh();
// Status — cached from the most recent refresh().
ChargeStatus chargeStatus() const { return chargeStatus_; }
bool isCharging() const { return chargeStatus_ == ChargeStatus::Precharge || chargeStatus_ == ChargeStatus::FastCharge; }
bool isChargeDone() const { return chargeStatus_ == ChargeStatus::ChargeDone; }
bool isInputPowerGood() const { return inputPowerGood_; }
bool isThermalRegulation() const { return thermalReg_; }
uint8_t faultMask() const { return faultMask_; }
// Control.
bool setChargeEnable(bool enable);
bool setShippingModeEnable(bool enable);
private:
TwoWire *wire_ = nullptr;
uint8_t address_ = SGM41562_ADDR;
uint32_t lastRefreshMs_ = 0;
ChargeStatus chargeStatus_ = ChargeStatus::NotCharging;
bool inputPowerGood_ = false;
bool thermalReg_ = false;
uint8_t faultMask_ = 0;
bool readReg(uint8_t reg, uint8_t &value);
bool writeReg(uint8_t reg, uint8_t value);
bool updateReg(uint8_t reg, uint8_t mask, uint8_t value);
// SGM41562 register addresses
static constexpr uint8_t REG_INPUT_SOURCE = 0x00;
static constexpr uint8_t REG_POWER_ON_CFG = 0x01;
static constexpr uint8_t REG_CHARGE_CURRENT = 0x02;
static constexpr uint8_t REG_DISCHARGE_TERM_CURRENT = 0x03;
static constexpr uint8_t REG_CHARGE_VOLTAGE = 0x04;
static constexpr uint8_t REG_CHARGE_TERM_TIMER = 0x05;
static constexpr uint8_t REG_MISC_OP_CONTROL = 0x06;
static constexpr uint8_t REG_SYS_VOLTAGE_REG = 0x07;
static constexpr uint8_t REG_SYSTEM_STATUS = 0x08;
static constexpr uint8_t REG_FAULT = 0x09;
static constexpr uint8_t REG_I2C_ADDR_MISC = 0x0A;
static constexpr uint8_t REG_DEVICE_ID = 0x0B;
// Bit positions in REG_POWER_ON_CFG.
static constexpr uint8_t POWER_ON_CFG_CHG_DISABLE = 0x08; // bit 3: 1 = charging disabled
// Bit positions in REG_MISC_OP_CONTROL.
static constexpr uint8_t MISC_OP_SHIPPING_MODE = 0x20; // bit 5: 1 = enter shipping mode
// Bit positions in REG_SYSTEM_STATUS.
static constexpr uint8_t SYS_STATUS_CHRG_SHIFT = 3;
static constexpr uint8_t SYS_STATUS_CHRG_MASK = 0x03;
static constexpr uint8_t SYS_STATUS_PG = 0x02; // bit 1: input power good
static constexpr uint8_t SYS_STATUS_THERM_REG = 0x01; // bit 0: thermal regulation
static constexpr uint8_t DEVICE_ID_EXPECTED = 0x04;
};
extern SGM41562 *sgm41562;
// Lazy-instantiate the global on the supplied wire. Returns true on success.
bool initSGM41562(TwoWire &wire);
#endif // HAS_SGM41562
+82
View File
@@ -0,0 +1,82 @@
#include "TestUtil.h"
#include "gps/RTC.h"
#include <sys/time.h>
#include <time.h>
#include <unity.h>
// Regression coverage for issue #9828: on boards without a hardware RTC (e.g. RP2040),
// gettimeofday() can return uptime seconds rather than wall-clock time. A later readFromRTC()
// must not overwrite a higher-quality network/GPS time with that value, but it should still seed
// the clock when nothing better exists yet.
//
// The native test build compiles the RV3028 hardware-RTC branch (variants/native/portduino
// defines RV3028_RTC), so these tests use setReadFromRTCUseSystemTimeForTests() to force the
// no-hardware-RTC fallback path and setRTCSystemTimeForTests() to inject a deterministic clock.
static const uint32_t kAllowedDriftSeconds = 2;
static const time_t kUptimeSeconds = 21; // what gettimeofday() returns on RP2040 without a real clock
// A clearly-valid wall-clock epoch, safely inside any BUILD_EPOCH validity window.
static time_t makeValidEpoch()
{
return time(NULL) + SEC_PER_DAY;
}
void setUp(void)
{
resetRTCStateForTests();
}
void tearDown(void)
{
resetRTCStateForTests();
}
// A higher-quality network time must survive a later system-time read that only knows uptime.
static void test_readFromRTC_preserves_better_network_time(void)
{
const time_t networkEpoch = makeValidEpoch();
struct timeval networkTime;
networkTime.tv_sec = networkEpoch;
networkTime.tv_usec = 0;
TEST_ASSERT_EQUAL_INT(RTCSetResultSuccess, perhapsSetRTC(RTCQualityFromNet, &networkTime));
// Simulate a later readFromRTC() falling back to a system clock that only knows uptime.
struct timeval uptime;
uptime.tv_sec = kUptimeSeconds;
uptime.tv_usec = 0;
setRTCSystemTimeForTests(&uptime);
setReadFromRTCUseSystemTimeForTests(true);
TEST_ASSERT_EQUAL_INT(RTCSetResultSuccess, readFromRTC());
TEST_ASSERT_EQUAL_INT(RTCQualityFromNet, getRTCQuality());
TEST_ASSERT_UINT32_WITHIN(kAllowedDriftSeconds, (uint32_t)networkEpoch, getValidTime(RTCQualityFromNet));
}
// Before any higher-quality source exists, the fallback should still seed the clock.
static void test_readFromRTC_initializes_time_when_no_better_source(void)
{
const time_t systemEpoch = makeValidEpoch();
struct timeval systemTime;
systemTime.tv_sec = systemEpoch;
systemTime.tv_usec = 0;
setRTCSystemTimeForTests(&systemTime);
setReadFromRTCUseSystemTimeForTests(true);
TEST_ASSERT_EQUAL_INT(RTCSetResultSuccess, readFromRTC());
TEST_ASSERT_EQUAL_INT(RTCQualityNone, getRTCQuality());
TEST_ASSERT_UINT32_WITHIN(kAllowedDriftSeconds, (uint32_t)systemEpoch, getTime());
}
void setup()
{
delay(10);
initializeTestEnvironment();
UNITY_BEGIN();
RUN_TEST(test_readFromRTC_preserves_better_network_time);
RUN_TEST(test_readFromRTC_initializes_time_when_no_better_source);
exit(UNITY_END());
}
void loop() {}
@@ -0,0 +1,24 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
static const uint8_t TX = 21;
static const uint8_t RX = 20;
static const uint8_t SDA = 12;
static const uint8_t SCL = 13;
static const uint8_t SS = 8;
static const uint8_t MOSI = 7;
static const uint8_t MISO = 6;
static const uint8_t SCK = 10;
static const uint8_t A0 = 0;
static const uint8_t A1 = 1;
static const uint8_t A2 = 2;
static const uint8_t A3 = 3;
static const uint8_t A4 = 4;
static const uint8_t A5 = 5;
#endif /* Pins_Arduino_h */
@@ -0,0 +1,29 @@
; ESP32 C3 Super Mini Development Board
; https://www.espboards.dev/esp32/esp32-c3-super-mini/
[env:esp32c3_moonshine]
extends = esp32c3_base
board = esp32-c3-devkitm-1
board_level = pr
build_flags =
${esp32c3_base.build_flags}
-D PRIVATE_HW
-I variants/esp32c3/diy/esp32c3_moonshine
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
monitor_speed = 115200
upload_protocol = esptool
;upload_port = /dev/ttyUSB0
upload_speed = 921600
; ------------------- Flash 核心配置 -------------------
; 1. Flash 容量(根据硬件实际值改,常见 4MB/8MB)
board_build.flash_size = 4MB
; 2. Flash 模式(ESP32-C3 推荐 dio
board_build.flash_mode = dio
; 3. Flash 时钟频率(ESP32-C3 推荐 80MHz
board_build.flash_freq = 80MHz
; (可选)上传时的 Flash 配置(和上面保持一致即可)
board_upload.flash_size = 4MB
@@ -0,0 +1,63 @@
#define BUTTON_PIN 9
#define HAS_SCREEN 0
#define HAS_I2C 0
#define WIRE_INTERFACES_COUNT (0)
#define I2C_SDA 12
#define I2C_SCL 13
#define BATTERY_PIN 1
#define ADC_CHANNEL ADC_CHANNEL_1
#define ADC_MULTIPLIER 2.0f
#define LED_POWER 0 // LED
#define LED_STATE_ON 1 // State when LED is lit
#define HAS_GPS 0
#undef GPS_RX_PIN
#undef GPS_TX_PIN
//#define E220_400M30S
//#define E220_400M33S
#define E22_400M33S
#ifdef E220_400M30S
#define SETTING_MAX_POWER 30
#define TX_GAIN_LORA 8
#define SX126X_MAX_POWER 22
#endif
#ifdef E22_400M33S
#define SETTING_MAX_POWER 22
#define NUM_PA_POINTS 1
#define TX_GAIN_LORA 0
#define SX126X_MAX_POWER 22
//#define HAS_LORA_FEM 1
#endif
#define USE_LLCC68
#define USE_SX1262
#define USE_SX1268
#define LORA_SCK 10
#define LORA_MISO 6
#define LORA_MOSI 7
#define LORA_CS 8
#define LORA_DIO0 RADIOLIB_NC
#define LORA_RESET 5
#define LORA_DIO1 3
#define LORA_DIO2 RADIOLIB_NC
#define LORA_BUSY 4
#define SX126X_RXEN 2
#define SX126X_CS LORA_CS
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_BUSY
#define SX126X_RESET LORA_RESET
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define TCXO_OPTIONAL
@@ -37,6 +37,9 @@ extends = env:picomputer-s3
build_flags =
${env:picomputer-s3.build_flags}
-D MESHTASTIC_EXCLUDE_WEBSERVER=1
; device-ui's I2CKeyboardScanner unconditionally calls Wire.begin(I2C_SDA, I2C_SCL);
-D I2C_SDA=8
-D I2C_SCL=9
-D INPUTDRIVER_MATRIX_TYPE=1
-D USE_PIN_BUZZER=PIN_BUZZER
-D USE_SX127x
+6 -7
View File
@@ -55,9 +55,9 @@ build_flags =
-D HAS_TFT=1
-D USE_I2S_BUZZER
-D RAM_SIZE=5120
-D LV_LVGL_H_INCLUDE_SIMPLE
-D LV_CONF_INCLUDE_SIMPLE
-D LV_COMP_CONF_INCLUDE_SIMPLE
-D LV_LVGL_H_INCLUDE_SIMPLE
-D LV_CONF_INCLUDE_SIMPLE
-D LV_COMP_CONF_INCLUDE_SIMPLE
-D LV_USE_SYSMON=0
-D LV_USE_PROFILER=0
-D LV_USE_PERF_MONITOR=0
@@ -72,6 +72,7 @@ build_flags =
; -D CALIBRATE_TOUCH=0
-D LGFX_SCREEN_WIDTH=240
-D LGFX_SCREEN_HEIGHT=320
-D LGFX_BUFSIZE=153600
-D DISPLAY_SIZE=320x240 ; landscape mode
-D LGFX_DRIVER=LGFX_TDECK
-D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_T_DECK.h\"
@@ -79,13 +80,11 @@ build_flags =
; -D GFX_DRIVER_INC=\"graphics/LVGL/LVGL_T_DECK.h\"
; -D LV_USE_ST7789=1
-D VIEW_320x240
; -D USE_DOUBLE_BUFFER
-D USE_PACKET_API
-D MAP_FULL_REDRAW
-D CUSTOM_TOUCH_DRIVER
; -D CUSTOM_TOUCH_DRIVER
lib_deps =
${env:t-deck.lib_deps}
${device-ui_base.lib_deps}
# renovate: datasource=github-tags depName=bb_captouch packageName=bitbank2/bb_captouch
https://github.com/bitbank2/bb_captouch/archive/refs/tags/1.3.1.zip
;https://github.com/bitbank2/bb_captouch/archive/refs/tags/1.3.1.zip
@@ -0,0 +1,19 @@
[env:t-impulse-plus]
custom_meshtastic_hw_model = 135
custom_meshtastic_hw_model_slug = T_IMPULSE_PLUS
custom_meshtastic_architecture = nrf52840
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = LILYGO T-Impulse Plus
custom_meshtastic_tags = LilyGo
custom_meshtastic_requires_dfu = true
extends = nrf52840_base
board = t-impulse-plus
board_level = pr
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/t-impulse-plus
-D T_IMPULSE_PLUS
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-impulse-plus>
@@ -0,0 +1,91 @@
/*
* variant.cpp - Digital pin mapping for LilyGo T-Impulse Plus
*
* Board: T-Impulse Plus V1.0 (nRF52840)
* Hardware:
* - SSD1315 OLED
* - SX1262 (S62F)
* - MIA-M10Q GPS
* - ICM20948 IMU
* - ZD25WQ32C Flash
* - TTP223 Touch Button
* - Vibration Motor
*/
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
extern "C" {
const uint32_t g_ADigitalPinMap[] = {
// D0-D6: LoRa SX1262 (S62F module) SPI + control
2, // D0 P0.02 SX1262_RST
29, // D1 P0.29 SX1262_DIO1
31, // D2 P0.31 SX1262_BUSY
46, // D3 P1.14 SX1262_CS
3, // D4 P0.03 SPI_SCK
30, // D5 P0.30 SPI_MISO
28, // D6 P0.28 SPI_MOSI
// D7-D8: RF switch control
45, // D7 P1.13 SX1262_RF_VC1 (TXEN)
39, // D8 P1.07 SX1262_RF_VC2 (RXEN)
// D9-D11: GPS (u-blox MIA-M10Q)
44, // D9 P1.12 GPS_TX (MCU TX -> GPS RX)
43, // D10 P1.11 GPS_RX (MCU RX <- GPS TX)
42, // D11 P1.10 GPS_EN
// D12-D13: Display I2C (SSD1315)
20, // D12 P0.20 SCREEN_SDA
15, // D13 P0.15 SCREEN_SCL
// D14-D15: Sensor I2C (ICM20948, SGM41562)
40, // D14 P1.08 IMU_SDA
11, // D15 P0.11 IMU_SCL
// D16-D17: Battery management
5, // D16 P0.05 BATTERY_ADC
25, // D17 P0.25 BATTERY_MEASUREMENT_CONTROL
// D18: Touch button (TTP223)
36, // D18 P1.04 TTP223_KEY
// D19: Vibration motor
22, // D19 P0.22 VIBRATION_MOTOR
// D20: LDO enable
14, // D20 P0.14 RT9080_EN
// D21-D26: Flash QSPI (ZD25WQ32C)
12, // D21 P0.12 FLASH_CS
4, // D22 P0.04 FLASH_SCLK
6, // D23 P0.06 FLASH_IO0
41, // D24 P1.09 FLASH_IO1
8, // D25 P0.08 FLASH_IO2
26, // D26 P0.26 FLASH_IO3
// D27-D28: Interrupt lines
7, // D27 P0.07 ICM20948_INT
16, // D28 P0.16 SGM41562_INT
// D29: Boot button
24, // D29 P0.24 BOOT
};
}
void initVariant()
{
// Flash CS high (deselect)
pinMode(PIN_QSPI_CS, OUTPUT);
digitalWrite(PIN_QSPI_CS, HIGH);
// Enable battery voltage measurement
pinMode(BAT_READ, OUTPUT);
digitalWrite(BAT_READ, HIGH);
// Enable RT9080 LDO
pinMode(D20, OUTPUT);
digitalWrite(D20, HIGH);
}
+181
View File
@@ -0,0 +1,181 @@
#ifndef _T_IMPULSE_PLUS_H_
#define _T_IMPULSE_PLUS_H_
#include "WVariant.h"
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Clock Configuration
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define VARIANT_MCK (64000000ul)
#define USE_LFXO
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Pin Capacity Definitions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define PINS_COUNT (30u)
#define NUM_DIGITAL_PINS (30u)
#define NUM_ANALOG_INPUTS (1u)
#define NUM_ANALOG_OUTPUTS (0u)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Digital Pin Mapping
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define D0 0 // P0.02 SX1262_RST
#define D1 1 // P0.29 SX1262_DIO1
#define D2 2 // P0.31 SX1262_BUSY
#define D3 3 // P1.14 SX1262_CS
#define D4 4 // P0.03 SPI_SCK
#define D5 5 // P0.30 SPI_MISO
#define D6 6 // P0.28 SPI_MOSI
#define D7 7 // P1.13 RF_VC1 (TXEN)
#define D8 8 // P1.07 RF_VC2 (RXEN)
#define D9 9 // P1.12 GPS module TX → MCU RX
#define D10 10 // P1.11 GPS module RX ← MCU TX
#define D11 11 // P1.10 GPS_EN (active LOW)
#define D12 12 // P0.20 SCREEN_SDA
#define D13 13 // P0.15 SCREEN_SCL
#define D14 14 // P1.08 IMU_SDA
#define D15 15 // P0.11 IMU_SCL
#define D16 16 // P0.05 BATTERY_ADC
#define D17 17 // P0.25 BATTERY_CTL
#define D18 18 // P1.04 TTP223_KEY
#define D19 19 // P0.22 VIBRATION_MOTOR
#define D20 20 // P0.14 RT9080_EN
#define D21 21 // P0.12 FLASH_CS
#define D22 22 // P0.04 FLASH_SCLK
#define D23 23 // P0.06 FLASH_IO0
#define D24 24 // P1.09 FLASH_IO1
#define D25 25 // P0.08 FLASH_IO2
#define D26 26 // P0.26 FLASH_IO3
#define D27 27 // P0.07 ICM20948_INT
#define D28 28 // P0.16 SGM41562_INT
#define D29 29 // P0.24 BOOT
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// LED Configuration (no physical LED)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define LED_STATE_ON 1
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Button Configuration (TTP223 capacitive touch)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define PIN_BUTTON_TOUCH D18
#define BUTTON_TOUCH_ACTIVE_LOW true
#define BUTTON_TOUCH_ACTIVE_PULLUP false
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Analog Pin Definitions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define PIN_VBAT D16 // P0.05 Battery voltage sense
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// I2C Configuration
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Primary I2C: Display (SSD1315)
#define PIN_WIRE_SDA D12 // P0.20
#define PIN_WIRE_SCL D13 // P0.15
// Secondary I2C: IMU (ICM20948) + PMU (SGM41562)
#define WIRE_INTERFACES_COUNT 2
#define PIN_WIRE1_SDA D14 // P1.08
#define PIN_WIRE1_SCL D15 // P0.11
#define I2C_NO_RESCAN
static const uint8_t SDA = PIN_WIRE_SDA;
static const uint8_t SCL = PIN_WIRE_SCL;
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Display (SSD1315, compatible with SSD1306 driver)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define HAS_SCREEN 1
#define USE_SSD1306 1
#define OLED_TINY
#define OLED_GEOMETRY_OVERRIDE GEOMETRY_64_32
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SPI Configuration (SX1262 LoRa)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_SCK D4 // P0.03
#define PIN_SPI_MISO D5 // P0.30
#define PIN_SPI_MOSI D6 // P0.28
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SX1262 LoRa (S62F module)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define USE_SX1262
#define SX126X_CS D3
#define SX126X_DIO1 D1
#define SX126X_BUSY D2
#define SX126X_RESET D0
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define SX126X_TXEN D7 // RF_VC1
#define SX126X_RXEN D8 // RF_VC2
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Power Management
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define BAT_READ D17 // P0.25 Battery measurement control (HIGH = enable)
#define BATTERY_PIN PIN_VBAT
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define ADC_MULTIPLIER 2.0
#define AREF_VOLTAGE 3.6
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// GPS (u-blox MIA-M10Q)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define GPS_UBLOX
#define HAS_GPS 1
#define GPS_RX_PIN D9 // P1.12 — MCU RX, wired to GPS module TX
#define GPS_TX_PIN D10 // P1.11 — MCU TX, wired to GPS module RX
#define PIN_GPS_EN D11 // P1.10
#define GPS_EN_ACTIVE LOW
#define GPS_BAUDRATE 38400
#define GPS_THREAD_INTERVAL 50
#define PIN_SERIAL1_TX GPS_TX_PIN
#define PIN_SERIAL1_RX GPS_RX_PIN
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// On-board QSPI Flash (ZD25WQ32CEIGR)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define PIN_QSPI_SCK D22 // P0.04
#define PIN_QSPI_CS D21 // P0.12
#define PIN_QSPI_IO0 D23 // P0.06
#define PIN_QSPI_IO1 D24 // P1.09
#define PIN_QSPI_IO2 D25 // P0.08
#define PIN_QSPI_IO3 D26 // P0.26
#define EXTERNAL_FLASH_DEVICES W25Q32JV_IQ
#define EXTERNAL_FLASH_USE_QSPI
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Vibration Motor (GPIO active-high)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define LED_NOTIFICATION D19 // P0.22
#define HAPTIC_FEEDBACK_PIN LED_NOTIFICATION
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// IMU (ICM20948 on Wire1)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define HAS_ICM20948
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Charger (SGM41562 on Wire1 @ 0x03)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define HAS_SGM41562
#define SGM41562_WIRE Wire1
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Compatibility Definitions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#ifdef __cplusplus
extern "C" {
#endif
#define PIN_SERIAL2_RX (-1)
#define PIN_SERIAL2_TX (-1)
#ifdef __cplusplus
}
#endif
#endif // _T_IMPULSE_PLUS_H_