Compare commits

...

8 Commits
test ... main

Author SHA1 Message Date
775156bbbe Обновить README.md 2025-12-11 00:55:57 +03:00
7161a065ea Обновить README.md 2025-12-11 00:49:48 +03:00
4LAR
21b382d728 Заставка 2025-12-10 21:45:39 +00:00
ccdc5e151e Обновить README.md 2025-12-11 00:45:02 +03:00
4LAR
9ab4792354 модификаця на релизную версию 2025-12-10 18:08:56 +00:00
99b1896c8d Обновить README.md 2025-12-10 19:09:37 +03:00
4LAR
2f627af12d clone last beta release 2025-12-10 16:01:37 +00:00
4LAR
21a32e1414 clenup 2025-12-10 15:56:17 +00:00
167 changed files with 3790 additions and 7628 deletions

167
.gitignore vendored
View File

@ -1,167 +0,0 @@
questions/*
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
venv_lin/*
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
config.ini
logs/*
..env.swp

View File

@ -1,39 +1,40 @@
<div align="center" markdown="1"> # Meshtastic MOD
<img src=".github/meshtastic_logo.png" alt="Meshtastic Logo" width="80"/> Модификация оригинальной прошивки для meshtastic. Был взят исходный код версии "2.7.15.567b8ea" (Последний стабильный релиз на момент модификации 10.12.25).
<h1>Meshtastic Firmware</h1>
![GitHub release downloads](https://img.shields.io/github/downloads/meshtastic/firmware/total) В данном моде был добавлен новый регион "NO_LIMITS", у которого убраны все возможные ограничения (Frequency Range, Duty Cycle, Power Limit)
[![CI](https://img.shields.io/github/actions/workflow/status/meshtastic/firmware/main_matrix.yml?branch=master&label=actions&logo=github&color=yellow)](https://github.com/meshtastic/firmware/actions/workflows/ci.yml)
[![CLA assistant](https://cla-assistant.io/readme/badge/meshtastic/firmware)](https://cla-assistant.io/meshtastic/firmware)
[![Fiscal Contributors](https://opencollective.com/meshtastic/tiers/badge.svg?label=Fiscal%20Contributors&color=deeppink)](https://opencollective.com/meshtastic/)
[![Vercel](https://img.shields.io/static/v1?label=Powered%20by&message=Vercel&style=flat&logo=vercel&color=000000)](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
<a href="https://trendshift.io/repositories/5524" target="_blank"><img src="https://trendshift.io/api/badge/repositories/5524" alt="meshtastic%2Ffirmware | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> Поставить регион "NO_LIMITS" можно только через настройки на самом устроистве или через консольные команды (https://meshtastic.org/docs/software/python/cli/). Новый регион имеет индекс 27
</div> Данная модификация предназначена исключительно для ознакомления и тестирования в пределах вашей собственной сети :)
</div> ## Сборка и прошивка
<div align="center"> Создаем виртуальное оружение
<a href="https://meshtastic.org">Website</a>
-
<a href="https://meshtastic.org/docs/">Documentation</a>
</div>
## Overview ```
python3 -m venv venv
source venv/bin/activate
```
This repository contains the official device firmware for Meshtastic, an open-source LoRa mesh networking project designed for long-range, low-power communication without relying on internet or cellular infrastructure. The firmware supports various hardware platforms, including ESP32, nRF52, RP2040/RP2350, and Linux-based devices. Устанавливаем PlatformIO
Meshtastic enables text messaging, location sharing, and telemetry over a decentralized mesh network, making it ideal for outdoor adventures, emergency preparedness, and remote operations. ```
pip install platformio
```
### Get Started Собираем прошивку (пример для heltec v3)
- 🔧 **[Building Instructions](https://meshtastic.org/docs/development/firmware/build)** – Learn how to compile the firmware from source. ```
- ⚡ **[Flashing Instructions](https://meshtastic.org/docs/getting-started/flashing-firmware/)** – Install or update the firmware on your device. pio run -e heltec-v3
```
Join our community and help improve Meshtastic! 🚀 После успешной сборки прошивка (firmware.bin) будет лежать в папке .pio/build/{название платы}
Для того чтобы залить прошивку можно воспользоваться официальным веб инструментом: https://flasher.meshtastic.org/, или через CLI https://meshtastic.org/docs/getting-started/flashing-firmware/esp32/cli-script/, но тут уже только бог в помощь
---
Ссылка на оригинальный репозиторий: https://github.com/meshtastic/firmware
## Stats
![Alt](https://repobeats.axiom.co/api/embed/8025e56c482ec63541593cc5bd322c19d5c0bdcf.svg "Repobeats analytics image")

View File

@ -57,7 +57,7 @@ lib_deps =
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip https://github.com/lewisxhe/XPowersLib/archive/v0.3.1.zip
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@ -28,7 +28,7 @@ lib_deps =
${environmental_extra.lib_deps} ${environmental_extra.lib_deps}
${radiolib_base.lib_deps} ${radiolib_base.lib_deps}
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.2 lewisxhe/XPowersLib@0.3.1
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@ -7,13 +7,13 @@ extends = arduino_base
platform_packages = platform_packages =
; our custom Git version until they merge our PR ; our custom Git version until they merge our PR
# TODO renovate # TODO renovate
platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39 platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#e13f5820002a4fb2a5e6754b42ace185277e5adf
; Don't renovate toolchain-gccarmnoneeabi ; Don't renovate toolchain-gccarmnoneeabi
platformio/toolchain-gccarmnoneeabi@~1.90301.0 platformio/toolchain-gccarmnoneeabi@~1.90301.0
build_type = release build_type = debug
build_flags = build_flags =
-include variants/nrf52840/cpp_overrides/lfs_util.h -include arch/nrf52/cpp_overrides/lfs_util.h
${arduino_base.build_flags} ${arduino_base.build_flags}
-DSERIAL_BUFFER_SIZE=1024 -DSERIAL_BUFFER_SIZE=1024
-Wno-unused-variable -Wno-unused-variable
@ -21,17 +21,6 @@ build_flags =
-DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818 -DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818
-DMESHTASTIC_EXCLUDE_AUDIO=1 -DMESHTASTIC_EXCLUDE_AUDIO=1
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
-Os
build_unflags =
-Ofast
-Og
-ggdb3
-ggdb2
-g3
-g2
-g
-g1
-g0
build_src_filter = build_src_filter =
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp> -<serialization/> ${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp> -<serialization/>

View File

@ -8,7 +8,7 @@ lib_deps =
${environmental_base.lib_deps} ${environmental_base.lib_deps}
${environmental_extra.lib_deps} ${environmental_extra.lib_deps}
# renovate: datasource=git-refs depName=Kongduino-Adafruit_nRFCrypto packageName=https://github.com/Kongduino/Adafruit_nRFCrypto gitBranch=master # renovate: datasource=git-refs depName=Kongduino-Adafruit_nRFCrypto packageName=https://github.com/Kongduino/Adafruit_nRFCrypto gitBranch=master
https://github.com/Kongduino/Adafruit_nRFCrypto/archive/8cde7189b5ead9dcd49f72601b43b969c0bbc06e.zip https://github.com/Kongduino/Adafruit_nRFCrypto/archive/5f838d2709461a2c981f642917aa50254a25c14c.zip
; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board. ; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board.

View File

@ -2,7 +2,7 @@
extends = arduino_base extends = arduino_base
platform = platform =
# renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32 # renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32
platformio/ststm32@19.4.0 platformio/ststm32@19.3.0
platform_packages = platform_packages =
# TODO renovate # TODO renovate
platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip

View File

@ -1,165 +0,0 @@
#!/usr/bin/env python3
"""Summarise linker map output to highlight heavy object files and libraries.
Usage:
python bin/analyze_map.py --map .pio/build/rak4631/output.map --top 20
The script parses GNU ld map files and aggregates section sizes per object file
and per archive/library, then prints sortable tables that make it easy to spot
modules worth trimming or hiding behind feature flags.
"""
from __future__ import annotations
import argparse
import collections
import os
import re
import sys
from typing import DefaultDict, Dict, Tuple
SECTION_LINE_RE = re.compile(r"^\s+(?P<section>\S+)\s+0x[0-9A-Fa-f]+\s+0x(?P<size>[0-9A-Fa-f]+)\s+(?P<object>.+)$")
ARCHIVE_MEMBER_RE = re.compile(r"^(?P<archive>.+)\((?P<object>[^)]+)\)$")
def human_size(num_bytes: int) -> str:
"""Return a friendly size string with one decimal place."""
if num_bytes < 1024:
return f"{num_bytes:,} B"
num = float(num_bytes)
for unit in ("KB", "MB", "GB"):
num /= 1024.0
if num < 1024.0:
return f"{num:.1f} {unit}"
return f"{num:.1f} TB"
def shorten_path(path: str, root: str) -> str:
"""Prefer repository-relative paths for readability."""
path = path.strip()
if not path:
return path
# Normalise Windows archives (backslashes) to POSIX style for consistency.
path = path.replace("\\", "/")
# Attempt to strip the root when an absolute path lives inside the repo.
if os.path.isabs(path):
try:
rel = os.path.relpath(path, root)
if not rel.startswith(".."):
return rel
except ValueError:
# relpath can fail on mixed drives on Windows; fall back to basename.
pass
return path
def describe_object(raw_object: str, root: str) -> Tuple[str, str]:
"""Return a human friendly object label and the library it belongs to."""
raw_object = raw_object.strip()
lib_label = "[app]"
match = ARCHIVE_MEMBER_RE.match(raw_object)
if match:
archive = shorten_path(match.group("archive"), root)
obj = match.group("object")
lib_label = os.path.basename(archive) or archive
label = f"{archive}:{obj}"
else:
label = shorten_path(raw_object, root)
# If the object lives under libs, hint at the containing directory.
parent = os.path.basename(os.path.dirname(label))
if parent:
lib_label = parent
return label, lib_label
def parse_map(map_path: str, repo_root: str) -> Tuple[Dict[str, int], Dict[str, int], Dict[str, Dict[str, int]]]:
per_object: DefaultDict[str, int] = collections.defaultdict(int)
per_library: DefaultDict[str, int] = collections.defaultdict(int)
per_object_sections: DefaultDict[str, DefaultDict[str, int]] = collections.defaultdict(lambda: collections.defaultdict(int))
try:
with open(map_path, "r", encoding="utf-8", errors="ignore") as handle:
for line in handle:
match = SECTION_LINE_RE.match(line)
if not match:
continue
section = match.group("section")
if section.startswith("*") or section in {"LOAD", "ORIGIN"}:
continue
size = int(match.group("size"), 16)
if size == 0:
continue
obj_token = match.group("object").strip()
if not obj_token or obj_token.startswith("*") or "load address" in obj_token:
continue
label, lib_label = describe_object(obj_token, repo_root)
per_object[label] += size
per_library[lib_label] += size
per_object_sections[label][section] += size
except FileNotFoundError:
raise SystemExit(f"error: map file '{map_path}' not found. Run a build first.")
return per_object, per_library, per_object_sections
def format_section_breakdown(section_sizes: Dict[str, int], total: int, limit: int = 3) -> str:
items = sorted(section_sizes.items(), key=lambda kv: kv[1], reverse=True)
parts = []
for section, size in items[:limit]:
pct = (size / total) * 100 if total else 0
parts.append(f"{section} {pct:.1f}%")
if len(items) > limit:
remainder = total - sum(size for _, size in items[:limit])
pct = (remainder / total) * 100 if total else 0
parts.append(f"other {pct:.1f}%")
return ", ".join(parts)
def print_report(map_path: str, top_n: int, per_object: Dict[str, int], per_library: Dict[str, int], per_object_sections: Dict[str, Dict[str, int]]):
total_bytes = sum(per_object.values())
if total_bytes == 0:
print("No section data found in map file.")
return
print(f"Map file: {map_path}")
print(f"Accounted size: {human_size(total_bytes)} across {len(per_object)} object files\n")
sorted_objects = sorted(per_object.items(), key=lambda kv: kv[1], reverse=True)
print(f"Top {min(top_n, len(sorted_objects))} object files by linked size:")
for idx, (obj, size) in enumerate(sorted_objects[:top_n], 1):
pct = (size / total_bytes) * 100
breakdown = format_section_breakdown(per_object_sections[obj], size)
print(f"{idx:2}. {human_size(size):>9} ({size:,} B, {pct:5.2f}% of linked size)")
print(f" {obj}")
if breakdown:
print(f" sections: {breakdown}")
print()
sorted_libs = sorted(per_library.items(), key=lambda kv: kv[1], reverse=True)
print(f"Top {min(top_n, len(sorted_libs))} libraries or source roots:")
for idx, (lib, size) in enumerate(sorted_libs[:top_n], 1):
pct = (size / total_bytes) * 100
print(f"{idx:2}. {human_size(size):>9} ({size:,} B, {pct:5.2f}% of linked size) {lib}")
def main() -> None:
parser = argparse.ArgumentParser(description="Highlight heavy object files from a GNU ld map file.")
parser.add_argument("--map", default=".pio/build/rak4631/output.map", help="Path to the map file (default: %(default)s)")
parser.add_argument("--top", type=int, default=20, help="Number of entries to display per table (default: %(default)s)")
args = parser.parse_args()
map_path = os.path.abspath(args.map)
repo_root = os.path.abspath(os.getcwd())
per_object, per_library, per_object_sections = parse_map(map_path, repo_root)
print_report(os.path.relpath(map_path, repo_root), args.top, per_object, per_library, per_object_sections)
if __name__ == "__main__":
main()

View File

@ -87,12 +87,6 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.7.17" date="2025-11-28">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
</release>
<release version="2.7.16" date="2025-11-19">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.16</url>
</release>
<release version="2.7.15" date="2025-11-13"> <release version="2.7.15" date="2025-11-13">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.15</url> <url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.15</url>
</release> </release>

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
Import("env")
platform = env.PioPlatform()
if platform.name == "native":
env.Replace(PROGNAME="meshtasticd")
else:
from readprops import readProps
prefsLoc = env["PROJECT_DIR"] + "/version.properties"
verObj = readProps(prefsLoc)
env.Replace(PROGNAME=f"firmware-{env.get('PIOENV')}-{verObj['long']}")
# Print the new program name for verification
print(f"PROGNAME: {env.get('PROGNAME')}")

View File

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

View File

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

View File

@ -1,41 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "hackaday-communicator"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "hackaday-communicator (16 MB FLASH, 8 MB PSRAM)",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 1500000
},
"url": "hackaday.com",
"vendor": "hackaday"
}

View File

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

View File

@ -1,56 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_MUZI_BASE -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [["0x239A", "0xcafe"]],
"mcu": "nrf52840",
"variant": "muzi-base",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Muzi Base",
"url": "https://muzi.works/",
"vendor": "MuziWorks",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"blackmagic",
"cmsis-dap",
"mbed",
"stlink"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
}
}

13
debian/changelog vendored
View File

@ -1,16 +1,3 @@
meshtasticd (2.7.17.0) unstable; urgency=medium
* Version 2.7.17
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Fri, 28 Nov 2025 15:11:34 +0000
meshtasticd (2.7.16.0) unstable; urgency=medium
* Version 2.7.16
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 19 Nov 2025 16:12:32 +0000
meshtasticd (2.7.15.0) unstable; urgency=medium meshtasticd (2.7.15.0) unstable; urgency=medium
* Version 2.7.15 * Version 2.7.15

View File

@ -1,80 +0,0 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
# trunk-ignore-all(ruff/E402): Hacky esptool import
# trunk-ignore-all(flake8/E402): Hacky esptool import
import sys
from os.path import join
Import("env")
platform = env.PioPlatform()
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
def esp32_create_combined_bin(source, target, env):
# this sub is borrowed from ESPEasy build toolchain. It's licensed under GPL V3
# https://github.com/letscontrolit/ESPEasy/blob/mega/tools/pio/post_esp32.py
print("Generating combined binary for serial flashing")
app_offset = 0x10000
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
board = env.BoardConfig()
flash_size = board.get("upload.flash_size")
flash_freq = board.get("build.f_flash", "40m")
flash_freq = flash_freq.replace("000000L", "m")
flash_mode = board.get("build.flash_mode", "dio")
memory_type = board.get("build.arduino.memory_type", "qio_qspi")
if flash_mode == "qio" or flash_mode == "qout":
flash_mode = "dio"
if memory_type == "opi_opi" or memory_type == "opi_qspi":
flash_mode = "dout"
cmd = [
"--chip",
chip,
"merge_bin",
"-o",
new_file_name,
"--flash_mode",
flash_mode,
"--flash_freq",
flash_freq,
"--flash_size",
flash_size,
]
print(" Offset | File")
for section in sections:
sect_adr, sect_file = section.split(" ", 1)
print(f" - {sect_adr} | {sect_file}")
cmd += [sect_adr, sect_file]
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
print("Using esptool.py arguments: %s" % " ".join(cmd))
esptool.main(cmd)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
esp32_kind = env.GetProjectOption("custom_esp32_kind")
if esp32_kind == "esp32":
# Free up some IRAM by removing auxiliary SPI flash chip drivers.
# Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c.
env.Append(
LINKFLAGS=[
"-Wl,--wrap=esp_flash_chip_gd",
"-Wl,--wrap=esp_flash_chip_issi",
"-Wl,--wrap=esp_flash_chip_winbond",
]
)
else:
# For newer ESP32 targets, using newlib nano works better.
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])

View File

@ -1,73 +0,0 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
import json
import sys
from os.path import isfile
Import("env")
# From https://github.com/platformio/platform-espressif32/blob/develop/builder/main.py
def _parse_size(value):
if isinstance(value, int):
return value
elif value.isdigit():
return int(value)
elif value.startswith("0x"):
return int(value, 16)
elif value[-1].upper() in ("K", "M"):
base = 1024 if value[-1].upper() == "K" else 1024 * 1024
return int(value[:-1]) * base
return value
def _parse_partitions(env):
partitions_csv = env.subst("$PARTITIONS_TABLE_CSV")
if not isfile(partitions_csv):
sys.stderr.write(
"Could not find the file %s with partitions " "table.\n" % partitions_csv
)
env.Exit(1)
return
result = []
# The first offset is 0x9000 because partition table is flashed to 0x8000 and
# occupies an entire flash sector, which size is 0x1000
next_offset = 0x9000
with open(partitions_csv) as fp:
for line in fp.readlines():
line = line.strip()
if not line or line.startswith("#"):
continue
tokens = [t.strip() for t in line.split(",")]
if len(tokens) < 5:
continue
bound = 0x10000 if tokens[1] in ("0", "app") else 4
calculated_offset = (next_offset + bound - 1) & ~(bound - 1)
partition = {
"name": tokens[0],
"type": tokens[1],
"subtype": tokens[2],
"offset": tokens[3] or calculated_offset,
"size": tokens[4],
"flags": tokens[5] if len(tokens) > 5 else None,
}
result.append(partition)
next_offset = _parse_size(partition["offset"]) + _parse_size(
partition["size"]
)
return result
def mtjson_esp32_part(target, source, env):
part = _parse_partitions(env)
pj = json.dumps(part)
# print(f"JSON_PARTITIONS: {pj}")
# Dump json string to 'custom_mtjson_part' variable to use later when writing the manifest
env.Replace(custom_mtjson_part=pj)
env.AddPreAction("mtjson", mtjson_esp32_part)

View File

@ -1,50 +0,0 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
import sys
from os.path import basename
Import("env")
# Custom HEX from ELF
# Convert hex to uf2 for nrf52
def nrf52_hex_to_uf2(source, target, env):
hex_path = target[0].get_abspath()
# When using merged hex, drop 'merged' from uf2 filename
uf2_path = hex_path.replace(".merged.", ".")
uf2_path = uf2_path.replace(".hex", ".uf2")
env.Execute(
env.VerboseAction(
f'"{sys.executable}" ./bin/uf2conv.py "{hex_path}" -c -f 0xADA52840 -o "{uf2_path}"',
f"Generating UF2 file from {basename(hex_path)}",
)
)
def nrf52_mergehex(source, target, env):
hex_path = target[0].get_abspath()
merged_hex_path = hex_path.replace(".hex", ".merged.hex")
merge_with = None
if "wio-sdk-wm1110" == str(env.get("PIOENV")):
merge_with = env.subst("$PROJECT_DIR/bin/s140_nrf52_7.3.0_softdevice.hex")
else:
print("merge_with not defined for this target")
if merge_with is not None:
env.Execute(
env.VerboseAction(
f'"$PROJECT_DIR/bin/mergehex" -m "{hex_path}" "{merge_with}" -o "{merged_hex_path}"',
"Merging HEX with SoftDevice",
)
)
print(f'Merged file saved at "{basename(merged_hex_path)}"')
nrf52_hex_to_uf2([hex_path, merge_with], [env.File(merged_hex_path)], env)
# if WM1110 target, merge hex with softdevice 7.3.0
if "wio-sdk-wm1110" == env.get("PIOENV"):
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", nrf52_mergehex)
else:
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", nrf52_hex_to_uf2)

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
Import("env")
# Custom HEX from ELF
env.AddPostAction(
"$BUILD_DIR/${PROGNAME}.elf",
env.VerboseAction(
" ".join(
[
"$OBJCOPY",
"-O",
"ihex",
"-R",
".eeprom",
"$BUILD_DIR/${PROGNAME}.elf",
"$BUILD_DIR/${PROGNAME}.hex",
]
),
"Building $BUILD_DIR/${PROGNAME}.hex",
),
)

View File

@ -33,6 +33,7 @@ BuildRequires: python3dist(grpcio[protobuf])
BuildRequires: python3dist(grpcio-tools) BuildRequires: python3dist(grpcio-tools)
BuildRequires: git-core BuildRequires: git-core
BuildRequires: gcc-c++ BuildRequires: gcc-c++
BuildRequires: (glibc-devel >= 2.38) or pkgconfig(libbsd-overlay)
BuildRequires: pkgconfig(yaml-cpp) BuildRequires: pkgconfig(yaml-cpp)
BuildRequires: pkgconfig(libgpiod) BuildRequires: pkgconfig(libgpiod)
BuildRequires: pkgconfig(bluez) BuildRequires: pkgconfig(bluez)
@ -49,13 +50,6 @@ BuildRequires: pkgconfig(x11)
BuildRequires: pkgconfig(libinput) BuildRequires: pkgconfig(libinput)
BuildRequires: pkgconfig(xkbcommon-x11) BuildRequires: pkgconfig(xkbcommon-x11)
# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
BuildRequires: glibc-devel >= 2.38
%else
BuildRequires: pkgconfig(libbsd-overlay)
%endif
Requires: systemd-udev Requires: systemd-udev
%description %description

View File

@ -5,7 +5,7 @@
default_envs = tbeam default_envs = tbeam
extra_configs = extra_configs =
variants/*/*.ini arch/*/*.ini
variants/*/*/platformio.ini variants/*/*/platformio.ini
variants/*/diy/*/platformio.ini variants/*/diy/*/platformio.ini
src/graphics/niche/InkHUD/PlatformioConfig.ini src/graphics/niche/InkHUD/PlatformioConfig.ini
@ -62,7 +62,7 @@ monitor_speed = 115200
monitor_filters = direct monitor_filters = direct
lib_deps = lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master # renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master # renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master # renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@ -90,7 +90,7 @@ framework = arduino
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL # renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
end2endzone/NonBlockingRTTTL@1.4.0 end2endzone/NonBlockingRTTTL@1.3.0
build_flags = ${env.build_flags} -Os build_flags = ${env.build_flags} -Os
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/> build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
@ -121,7 +121,7 @@ lib_deps =
[device-ui_base] [device-ui_base]
lib_deps = lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/3bf332240416c5cb8c919fac2a0ec7260eb3be75.zip https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.zip
; Common libs for environmental measurements in telemetry module ; Common libs for environmental measurements in telemetry module
[environmental_base] [environmental_base]
@ -169,7 +169,7 @@ lib_deps =
# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master # renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
# renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226 # renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226
robtillaart/INA226@0.6.5 robtillaart/INA226@0.6.4
# renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library # renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library
sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2 sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2
# renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library # renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library
@ -213,6 +213,6 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
sensirion/Sensirion Core@0.7.2 sensirion/Sensirion Core@0.7.1
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0 sensirion/Sensirion I2C SCD4x@1.1.0

View File

@ -926,7 +926,7 @@ message Config {
BR_902 = 26; BR_902 = 26;
/* /*
* Custom unrestricted region * NO LIMITS
*/ */
NO_LIMITS = 27; NO_LIMITS = 27;
} }

View File

@ -825,21 +825,6 @@ enum HardwareModel {
*/ */
RAK3401 = 117; RAK3401 = 117;
/*
* RAK6421 Hat+
*/
RAK6421 = 118;
/*
* Elecrow ThinkNode M4
*/
THINKNODE_M4 = 119;
/*
* Elecrow ThinkNode M6
*/
THINKNODE_M6 = 120;
/* /*
* ------------------------------------------------------------------------------------------------------------------------------------------ * ------------------------------------------------------------------------------------------------------------------------------------------
* Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. * Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
@ -1386,10 +1371,6 @@ message MeshPacket {
/* /*
* The (immediate) destination for this packet * The (immediate) destination for this packet
* If the value is 4,294,967,295 (maximum value of an unsigned 32bit integer), this indicates that the packet was
* not destined for a specific node, but for a channel as indicated by the value of `channel` below.
* If the value is another, this indicates that the packet was destined for a specific
* node (i.e. a kind of "Direct Message" to this node) and not broadcast on a channel.
*/ */
fixed32 to = 2; fixed32 to = 2;

View File

@ -50,11 +50,8 @@ class AudioThread : public concurrency::OSThread
delete i2sRtttl; delete i2sRtttl;
i2sRtttl = nullptr; i2sRtttl = nullptr;
} }
delete rtttlFile;
if (rtttlFile != nullptr) { rtttlFile = nullptr;
delete rtttlFile;
rtttlFile = nullptr;
}
setCPUFast(false); setCPUFast(false);
#ifdef T_LORA_PAGER #ifdef T_LORA_PAGER
@ -102,9 +99,9 @@ class AudioThread : public concurrency::OSThread
}; };
AudioGeneratorRTTTL *i2sRtttl = nullptr; AudioGeneratorRTTTL *i2sRtttl = nullptr;
AudioOutputI2S *audioOut = nullptr; AudioOutputI2S *audioOut;
AudioFileSourcePROGMEM *rtttlFile = nullptr; AudioFileSourcePROGMEM *rtttlFile;
}; };
#endif #endif

View File

@ -278,11 +278,6 @@ class AnalogBatteryLevel : public HasBatteryLevel
break; break;
} }
} }
#if defined(BATTERY_CHARGING_INV)
// bit of trickery to show 99% up until the charge finishes
if (!digitalRead(BATTERY_CHARGING_INV) && battery_SOC > 99)
battery_SOC = 99;
#endif
return clamp((int)(battery_SOC), 0, 100); return clamp((int)(battery_SOC), 0, 100);
} }
@ -460,8 +455,6 @@ class AnalogBatteryLevel : public HasBatteryLevel
} }
// if it's not HIGH - check the battery // if it's not HIGH - check the battery
#endif #endif
#elif defined(MUZI_BASE)
return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
#endif #endif
return getBattVoltage() > chargingVolt; return getBattVoltage() > chargingVolt;
} }
@ -477,8 +470,6 @@ class AnalogBatteryLevel : public HasBatteryLevel
#endif #endif
#ifdef EXT_CHRG_DETECT #ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value; return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#elif defined(BATTERY_CHARGING_INV)
return !digitalRead(BATTERY_CHARGING_INV);
#else #else
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
if (hasINA()) { if (hasINA()) {
@ -707,18 +698,11 @@ bool Power::setup()
[]() { []() {
power->setIntervalFromNow(0); power->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0;
}, },
CHANGE); CHANGE);
#endif #endif
#ifdef BATTERY_CHARGING_INV
attachInterrupt(
BATTERY_CHARGING_INV,
[]() {
power->setIntervalFromNow(0);
runASAP = true;
},
CHANGE);
#endif
enabled = found; enabled = found;
low_voltage_counter = 0; low_voltage_counter = 0;
@ -775,8 +759,6 @@ void Power::shutdown()
if (screen) { if (screen) {
#ifdef T_DECK_PRO #ifdef T_DECK_PRO
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
#elif defined(USE_EINK)
screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen
#else #else
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
#endif #endif
@ -1453,7 +1435,7 @@ class LipoCharger : public HasBatteryLevel
/** /**
* return true if there is an external power source detected * return true if there is an external power source detected
*/ */
virtual bool isVbusIn() override { return PPM->isVbusIn(); } virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; }
/** /**
* return true if the battery is currently charging * return true if the battery is currently charging

View File

@ -57,21 +57,21 @@ static bool isPowered()
static void sdsEnter() static void sdsEnter()
{ {
LOG_POWERFSM("State: SDS"); LOG_DEBUG("State: SDS");
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, false); doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, false);
} }
static void lowBattSDSEnter() static void lowBattSDSEnter()
{ {
LOG_POWERFSM("State: Lower batt SDS"); LOG_DEBUG("State: Lower batt SDS");
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, true); doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, true);
} }
extern Power *power; extern Power *power;
static void shutdownEnter() static void shutdownEnter()
{ {
LOG_POWERFSM("State: SHUTDOWN"); LOG_DEBUG("State: SHUTDOWN");
shutdownAtMsec = millis(); shutdownAtMsec = millis();
} }
@ -81,7 +81,7 @@ static uint32_t secsSlept;
static void lsEnter() static void lsEnter()
{ {
LOG_POWERFSM("lsEnter begin, ls_secs=%u", config.power.ls_secs); LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs);
if (screen) if (screen)
screen->setOn(false); screen->setOn(false);
secsSlept = 0; // How long have we been sleeping this time secsSlept = 0; // How long have we been sleeping this time
@ -155,12 +155,12 @@ static void lsIdle()
static void lsExit() static void lsExit()
{ {
LOG_POWERFSM("State: lsExit"); LOG_INFO("Exit state: LS");
} }
static void nbEnter() static void nbEnter()
{ {
LOG_POWERFSM("State: nbEnter"); LOG_DEBUG("State: NB");
if (screen) if (screen)
screen->setOn(false); screen->setOn(false);
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
@ -173,7 +173,6 @@ static void nbEnter()
static void darkEnter() static void darkEnter()
{ {
LOG_POWERFSM("State: darkEnter");
setBluetoothEnable(true); setBluetoothEnable(true);
if (screen) if (screen)
screen->setOn(false); screen->setOn(false);
@ -181,7 +180,7 @@ static void darkEnter()
static void serialEnter() static void serialEnter()
{ {
LOG_POWERFSM("State: serialEnter"); LOG_DEBUG("State: SERIAL");
setBluetoothEnable(false); setBluetoothEnable(false);
if (screen) { if (screen) {
screen->setOn(true); screen->setOn(true);
@ -190,14 +189,13 @@ static void serialEnter()
static void serialExit() static void serialExit()
{ {
LOG_POWERFSM("State: serialExit");
// Turn bluetooth back on when we leave serial stream API // Turn bluetooth back on when we leave serial stream API
setBluetoothEnable(true); setBluetoothEnable(true);
} }
static void powerEnter() static void powerEnter()
{ {
LOG_POWERFSM("State: powerEnter"); // LOG_DEBUG("State: POWER");
if (!isPowered()) { if (!isPowered()) {
// If we got here, we are in the wrong state - we should be in powered, let that state handle things // If we got here, we are in the wrong state - we should be in powered, let that state handle things
LOG_INFO("Loss of power in Powered"); LOG_INFO("Loss of power in Powered");
@ -212,7 +210,6 @@ static void powerEnter()
static void powerIdle() static void powerIdle()
{ {
// LOG_POWERFSM("State: powerIdle"); // very chatty
if (!isPowered()) { if (!isPowered()) {
// If we got here, we are in the wrong state // If we got here, we are in the wrong state
LOG_INFO("Loss of power in Powered"); LOG_INFO("Loss of power in Powered");
@ -222,13 +219,14 @@ static void powerIdle()
static void powerExit() static void powerExit()
{ {
LOG_POWERFSM("State: powerExit"); if (screen)
screen->setOn(true);
setBluetoothEnable(true); setBluetoothEnable(true);
} }
static void onEnter() static void onEnter()
{ {
LOG_POWERFSM("State: onEnter"); LOG_DEBUG("State: ON");
if (screen) if (screen)
screen->setOn(true); screen->setOn(true);
setBluetoothEnable(true); setBluetoothEnable(true);
@ -236,7 +234,6 @@ static void onEnter()
static void onIdle() static void onIdle()
{ {
LOG_POWERFSM("State: onIdle");
if (isPowered()) { if (isPowered()) {
// If we got here, we are in the wrong state - we should be in powered, let that state handle things // If we got here, we are in the wrong state - we should be in powered, let that state handle things
powerFSM.trigger(EVENT_POWER_CONNECTED); powerFSM.trigger(EVENT_POWER_CONNECTED);
@ -245,7 +242,7 @@ static void onIdle()
static void bootEnter() static void bootEnter()
{ {
LOG_POWERFSM("State: bootEnter"); LOG_DEBUG("State: BOOT");
} }
State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN"); State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN");
@ -322,6 +319,11 @@ void PowerFSM_setup()
// if any packet destined for phone arrives, turn on bluetooth at least // if any packet destined for phone arrives, turn on bluetooth at least
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
// Removed 2.7: we don't show the nodes individually for every node on the screen anymore
// powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// Show the received text message // Show the received text message
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
@ -370,7 +372,7 @@ void PowerFSM_setup()
// Don't add power saving transitions if we are a power saving tracker or sensor or have Wifi enabled. Sleep will be initiated // Don't add power saving transitions if we are a power saving tracker or sensor or have Wifi enabled. Sleep will be initiated
// through the modules // through the modules
#if HAS_WIFI && !defined(MESHTASTIC_EXCLUDE_WIFI) #if HAS_WIFI || !defined(MESHTASTIC_EXCLUDE_WIFI)
bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR; config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;

View File

@ -2,12 +2,6 @@
#include "configuration.h" #include "configuration.h"
#ifdef PowerFSMDebug
#define LOG_POWERFSM(...) LOG_DEBUG(__VA_ARGS__)
#else
#define LOG_POWERFSM(...)
#endif
// See sw-design.md for documentation // See sw-design.md for documentation
#define EVENT_PRESS 1 #define EVENT_PRESS 1

View File

@ -16,7 +16,6 @@ struct ToneDuration {
}; };
// Some common frequencies. // Some common frequencies.
#define NOTE_SILENT 1
#define NOTE_C3 131 #define NOTE_C3 131
#define NOTE_CS3 139 #define NOTE_CS3 139
#define NOTE_D3 147 #define NOTE_D3 147
@ -30,16 +29,11 @@ struct ToneDuration {
#define NOTE_AS3 233 #define NOTE_AS3 233
#define NOTE_B3 247 #define NOTE_B3 247
#define NOTE_CS4 277 #define NOTE_CS4 277
#define NOTE_B4 494
#define NOTE_F5 698
#define NOTE_G6 1568
#define NOTE_E7 2637
const int DURATION_1_16 = 62; // 1/16 note
const int DURATION_1_8 = 125; // 1/8 note const int DURATION_1_8 = 125; // 1/8 note
const int DURATION_1_4 = 250; // 1/4 note const int DURATION_1_4 = 250; // 1/4 note
const int DURATION_1_2 = 500; // 1/2 note const int DURATION_1_2 = 500; // 1/2 note
const int DURATION_3_4 = 750; // 3/4 note const int DURATION_3_4 = 750; // 1/4 note
const int DURATION_1_1 = 1000; // 1/1 note const int DURATION_1_1 = 1000; // 1/1 note
void playTones(const ToneDuration *tone_durations, int size) void playTones(const ToneDuration *tone_durations, int size)
@ -77,24 +71,13 @@ void playLongBeep()
void playGPSEnableBeep() void playGPSEnableBeep()
{ {
#if defined(R1_NEO) || defined(MUZI_BASE)
ToneDuration melody[] = {
{NOTE_F5, DURATION_1_2}, {NOTE_G6, DURATION_1_8}, {NOTE_E7, DURATION_1_4}, {NOTE_SILENT, DURATION_1_2}};
#else
ToneDuration melody[] = {{NOTE_C3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_CS4, DURATION_1_4}}; ToneDuration melody[] = {{NOTE_C3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_CS4, DURATION_1_4}};
#endif
playTones(melody, sizeof(melody) / sizeof(ToneDuration)); playTones(melody, sizeof(melody) / sizeof(ToneDuration));
} }
void playGPSDisableBeep() void playGPSDisableBeep()
{ {
#if defined(R1_NEO) || defined(MUZI_BASE)
ToneDuration melody[] = {{NOTE_B4, DURATION_1_16}, {NOTE_B4, DURATION_1_16}, {NOTE_SILENT, DURATION_1_8},
{NOTE_F3, DURATION_1_16}, {NOTE_F3, DURATION_1_16}, {NOTE_SILENT, DURATION_1_8},
{NOTE_C3, DURATION_1_1}, {NOTE_SILENT, DURATION_1_1}};
#else
ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_C3, DURATION_1_4}}; ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_C3, DURATION_1_4}};
#endif
playTones(melody, sizeof(melody) / sizeof(ToneDuration)); playTones(melody, sizeof(melody) / sizeof(ToneDuration));
} }

View File

@ -36,29 +36,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Offer chance for variant-specific defines */ /* Offer chance for variant-specific defines */
#include "variant.h" #include "variant.h"
// -----------------------------------------------------------------------------
// Display feature overrides
// -----------------------------------------------------------------------------
// Allow build environments to opt-in explicitly to the E-Ink UI stack while
// keeping headless targets slim by default. Existing variants that already
// define USE_EINK continue to work without additional flags.
#ifndef MESHTASTIC_USE_EINK_UI
#ifdef USE_EINK
#define MESHTASTIC_USE_EINK_UI 1
#else
#define MESHTASTIC_USE_EINK_UI 0
#endif
#endif
#if MESHTASTIC_USE_EINK_UI
#ifndef USE_EINK
#define USE_EINK
#endif
#else
#undef USE_EINK
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Version // Version
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -273,9 +250,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Touchscreen // Touchscreen
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define FT6336U_ADDR 0x48 #define FT6336U_ADDR 0x48
#define CST328_ADDR 0x1A // same address as CST226SE #define CST328_ADDR 0x1A
#define CHSC6X_ADDR 0x2E #define CHSC6X_ADDR 0x2E
#define CST226SE_ADDR_ALT 0x5A
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected) // RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
@ -394,9 +370,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef HAS_BLUETOOTH #ifndef HAS_BLUETOOTH
#define HAS_BLUETOOTH 0 #define HAS_BLUETOOTH 0
#endif #endif
#ifndef USE_TFTDISPLAY
#define USE_TFTDISPLAY 0
#endif
#ifndef HW_VENDOR #ifndef HW_VENDOR
#error HW_VENDOR must be defined #error HW_VENDOR must be defined
@ -423,13 +396,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define HAS_RGB_LED #define HAS_RGB_LED
#endif #endif
#ifndef LED_STATE_OFF
#define LED_STATE_OFF 0
#endif
#ifndef LED_STATE_ON
#define LED_STATE_ON 1
#endif
// default mapping of pins // default mapping of pins
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN) #if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
#define ALT_BUTTON_PIN PIN_BUTTON2 #define ALT_BUTTON_PIN PIN_BUTTON2

View File

@ -85,8 +85,7 @@ class ScanI2C
DRV2605, DRV2605,
BH1750, BH1750,
DA217, DA217,
CHSC6X, CHSC6X
CST226SE
} DeviceType; } DeviceType;
// typedef uint8_t DeviceAddress; // typedef uint8_t DeviceAddress;

View File

@ -499,18 +499,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address); SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address); SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
case CST328_ADDR: SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
// Do we have the CST328 or the CST226SE
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1);
if (registerValue == 0xA9) {
type = CST226SE;
logFoundDevice("CST226SE", (uint8_t)addr.address);
} else {
type = CST328;
logFoundDevice("CST328", (uint8_t)addr.address);
}
break;
SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address); SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address);
case LTR553ALS_ADDR: case LTR553ALS_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
@ -539,12 +528,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#endif #endif
case MLX90614_ADDR_DEF: case MLX90614_ADDR_DEF:
// Do we have the MLX90614 or the MPR121KB or the CST226SE registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1);
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x06), 1); if (registerValue == 0x5a) {
if (registerValue == 0xAB) {
type = CST226SE;
logFoundDevice("CST226SE", (uint8_t)addr.address);
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1) == 0x5a) {
type = MLX90614; type = MLX90614;
logFoundDevice("MLX90614", (uint8_t)addr.address); logFoundDevice("MLX90614", (uint8_t)addr.address);
} else { } else {
@ -562,11 +547,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case ICM20948_ADDR: // same as BMX160_ADDR case ICM20948_ADDR: // same as BMX160_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
#ifdef HAS_ICM20948
type = ICM20948;
logFoundDevice("ICM20948", (uint8_t)addr.address);
break;
#endif
if (registerValue == 0xEA) { if (registerValue == 0xEA) {
type = ICM20948; type = ICM20948;
logFoundDevice("ICM20948", (uint8_t)addr.address); logFoundDevice("ICM20948", (uint8_t)addr.address);

View File

@ -38,16 +38,14 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
return N; return N;
} }
#ifndef GPS_SERIAL_PORT #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
#define GPS_SERIAL_PORT Serial1 #if defined(GPS_SERIAL_PORT)
#endif
#if defined(ARCH_NRF52)
Uart *GPS::_serial_gps = &GPS_SERIAL_PORT;
#elif defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT; HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
#else
HardwareSerial *GPS::_serial_gps = &Serial1;
#endif
#elif defined(ARCH_RP2040) #elif defined(ARCH_RP2040)
SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT; SerialUART *GPS::_serial_gps = &Serial1;
#else #else
HardwareSerial *GPS::_serial_gps = nullptr; HardwareSerial *GPS::_serial_gps = nullptr;
#endif #endif
@ -1527,7 +1525,10 @@ GPS *GPS::createGps()
int8_t _rx_gpio = config.position.rx_gpio; int8_t _rx_gpio = config.position.rx_gpio;
int8_t _tx_gpio = config.position.tx_gpio; int8_t _tx_gpio = config.position.tx_gpio;
int8_t _en_gpio = config.position.gps_en_gpio; int8_t _en_gpio = config.position.gps_en_gpio;
#if HAS_GPS && !defined(ARCH_ESP32)
_rx_gpio = 1; // We only specify GPS serial ports on ESP32. Otherwise, these are just flags.
_tx_gpio = 1;
#endif
#if defined(GPS_RX_PIN) #if defined(GPS_RX_PIN)
if (!_rx_gpio) if (!_rx_gpio)
_rx_gpio = GPS_RX_PIN; _rx_gpio = GPS_RX_PIN;
@ -1601,28 +1602,16 @@ GPS *GPS::createGps()
_serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256 _serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256
#endif #endif
LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio);
LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio);
// ESP32 has a special set of parameters vs other arduino ports // ESP32 has a special set of parameters vs other arduino ports
#if defined(ARCH_ESP32) #if defined(ARCH_ESP32)
LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio);
LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio);
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio); _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
#elif defined(ARCH_RP2040) #elif defined(ARCH_RP2040)
_serial_gps->setPinout(new_gps->tx_gpio, new_gps->rx_gpio);
_serial_gps->setFIFOSize(256); _serial_gps->setFIFOSize(256);
_serial_gps->begin(GPS_BAUDRATE); _serial_gps->begin(GPS_BAUDRATE);
#elif defined(ARCH_NRF52)
_serial_gps->setPins(new_gps->rx_gpio, new_gps->tx_gpio);
_serial_gps->begin(GPS_BAUDRATE);
#elif defined(ARCH_STM32WL)
_serial_gps->setTx(new_gps->tx_gpio);
_serial_gps->setRx(new_gps->rx_gpio);
_serial_gps->begin(GPS_BAUDRATE);
#elif defined(ARCH_PORTDUINO)
// Portduino can't set the GPS pins directly.
_serial_gps->begin(GPS_BAUDRATE);
#else #else
#error Unsupported architecture! _serial_gps->begin(GPS_BAUDRATE);
#endif #endif
} }
return new_gps; return new_gps;

View File

@ -194,8 +194,6 @@ class GPS : private concurrency::OSThread
/** If !NULL we will use this serial port to construct our GPS */ /** If !NULL we will use this serial port to construct our GPS */
#if defined(ARCH_RP2040) #if defined(ARCH_RP2040)
static SerialUART *_serial_gps; static SerialUART *_serial_gps;
#elif defined(ARCH_NRF52)
static Uart *_serial_gps;
#else #else
static HardwareSerial *_serial_gps; static HardwareSerial *_serial_gps;
#endif #endif

View File

@ -112,11 +112,7 @@ RTCSetResult readFromRTC()
#elif defined(RX8130CE_RTC) #elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) { if (rtc_found.address == RX8130CE_RTC) {
uint32_t now = millis(); uint32_t now = millis();
#ifdef MUZI_BASE
ArtronShop_RX8130CE rtc(&Wire1);
#else
ArtronShop_RX8130CE rtc(&Wire); ArtronShop_RX8130CE rtc(&Wire);
#endif
tm t; tm t;
if (rtc.getTime(&t)) { if (rtc.getTime(&t)) {
tv.tv_sec = gm_mktime(&t); tv.tv_sec = gm_mktime(&t);
@ -249,11 +245,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
} }
#elif defined(RX8130CE_RTC) #elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) { if (rtc_found.address == RX8130CE_RTC) {
#ifdef MUZI_BASE
ArtronShop_RX8130CE rtc(&Wire1);
#else
ArtronShop_RX8130CE rtc(&Wire); ArtronShop_RX8130CE rtc(&Wire);
#endif
tm *t = gmtime(&tv->tv_sec); tm *t = gmtime(&tv->tv_sec);
if (rtc.setTime(*t)) { if (rtc.setTime(*t)) {
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,

View File

@ -69,11 +69,7 @@ using graphics::Emote;
using graphics::emotes; using graphics::emotes;
using graphics::numEmotes; using graphics::numEmotes;
#if USE_TFTDISPLAY
extern uint16_t TFT_MESH; extern uint16_t TFT_MESH;
#else
uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif
#if HAS_WIFI && !defined(ARCH_PORTDUINO) #if HAS_WIFI && !defined(ARCH_PORTDUINO)
#include "mesh/wifi/WiFiAPClient.h" #include "mesh/wifi/WiFiAPClient.h"
@ -230,9 +226,24 @@ void Screen::showTextInput(const char *header, const char *initialText, uint32_t
{ {
LOG_INFO("showTextInput called with header='%s', durationMs=%d", header ? header : "NULL", durationMs); LOG_INFO("showTextInput called with header='%s', durationMs=%d", header ? header : "NULL", durationMs);
// Start OnScreenKeyboardModule session (non-touch variant) if (NotificationRenderer::virtualKeyboard) {
OnScreenKeyboardModule::instance().start(header, initialText, durationMs, textCallback); delete NotificationRenderer::virtualKeyboard;
NotificationRenderer::virtualKeyboard = nullptr;
}
NotificationRenderer::textInputCallback = nullptr;
NotificationRenderer::virtualKeyboard = new VirtualKeyboard();
if (header) {
NotificationRenderer::virtualKeyboard->setHeader(header);
}
if (initialText) {
NotificationRenderer::virtualKeyboard->setInputText(initialText);
}
// Set up callback with safer cleanup mechanism
NotificationRenderer::textInputCallback = textCallback; NotificationRenderer::textInputCallback = textCallback;
NotificationRenderer::virtualKeyboard->setCallback([textCallback](const std::string &text) { textCallback(text); });
// Store the message and set the expiration timestamp (use same pattern as other notifications) // Store the message and set the expiration timestamp (use same pattern as other notifications)
strncpy(NotificationRenderer::alertBannerMessage, header ? header : "Text Input", 255); strncpy(NotificationRenderer::alertBannerMessage, header ? header : "Text Input", 255);
@ -313,7 +324,7 @@ static int8_t prevFrame = -1;
// Combined dynamic node list frame cycling through LastHeard, HopSignal, and Distance modes // Combined dynamic node list frame cycling through LastHeard, HopSignal, and Distance modes
// Uses a single frame and changes data every few seconds (E-Ink variant is separate) // Uses a single frame and changes data every few seconds (E-Ink variant is separate)
#if defined(ESP_PLATFORM) && (defined(USE_ST7789) || defined(USE_ST7796)) #if defined(ESP_PLATFORM) && defined(USE_ST7789)
SPIClass SPI1(HSPI); SPIClass SPI1(HSPI);
#endif #endif
@ -345,13 +356,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#else #else
dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
#endif #endif
#elif defined(USE_ST7796) static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#ifdef ESP_PLATFORM
dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7796_SDA,
ST7796_MISO, ST7796_SCK, TFT_SPI_FREQUENCY);
#else
dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
#endif
#elif defined(USE_SSD1306) #elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry, dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@ -364,7 +369,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
LOG_INFO("SSD1306 init success"); LOG_INFO("SSD1306 init success");
} }
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR) defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry, dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
@ -394,12 +399,6 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
isAUTOOled = true; isAUTOOled = true;
#endif #endif
#if defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#elif defined(USE_ST7796)
static_cast<ST7796Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
ui = new OLEDDisplayUi(dispdev); ui = new OLEDDisplayUi(dispdev);
cmdQueue.setReader(this); cmdQueue.setReader(this);
} }
@ -436,14 +435,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
PMU->enablePowerOutput(XPOWERS_ALDO2); PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif #endif
#if defined(MUZI_BASE)
dispdev->init();
dispdev->setBrightness(brightness);
dispdev->flipScreenVertically();
dispdev->resetDisplay();
digitalWrite(SCREEN_12V_ENABLE, HIGH);
delay(100);
#endif
#if !ARCH_PORTDUINO #if !ARCH_PORTDUINO
dispdev->displayOn(); dispdev->displayOn();
#endif #endif
@ -475,15 +466,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
pinMode(VTFT_LEDA, OUTPUT); pinMode(VTFT_LEDA, OUTPUT);
digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON); digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON);
#endif #endif
#endif
#ifdef USE_ST7796
ui->init();
#ifdef ESP_PLATFORM
analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT);
#else
pinMode(VTFT_LEDA, OUTPUT);
digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON);
#endif
#endif #endif
enabled = true; enabled = true;
setInterval(0); // Draw ASAP setInterval(0); // Draw ASAP
@ -502,10 +484,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#endif #endif
dispdev->displayOff(); dispdev->displayOff();
#ifdef SCREEN_12V_ENABLE
digitalWrite(SCREEN_12V_ENABLE, LOW);
#endif
#ifdef USE_ST7789 #ifdef USE_ST7789
SPI1.end(); SPI1.end();
#if defined(ARCH_ESP32) #if defined(ARCH_ESP32)
@ -522,21 +500,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
nrf_gpio_cfg_default(ST7789_NSS); nrf_gpio_cfg_default(ST7789_NSS);
#endif #endif
#endif #endif
#ifdef USE_ST7796
SPI1.end();
#if defined(ARCH_ESP32)
pinMode(VTFT_LEDA, OUTPUT);
digitalWrite(VTFT_LEDA, LOW);
pinMode(ST7796_RESET, ANALOG);
pinMode(ST7796_RS, ANALOG);
pinMode(ST7796_NSS, ANALOG);
#else
nrf_gpio_cfg_default(VTFT_LEDA);
nrf_gpio_cfg_default(ST7796_RESET);
nrf_gpio_cfg_default(ST7796_RS);
nrf_gpio_cfg_default(ST7796_NSS);
#endif
#endif
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
PMU->disablePowerOutput(XPOWERS_ALDO2); PMU->disablePowerOutput(XPOWERS_ALDO2);
@ -571,7 +534,7 @@ void Screen::setup()
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model); static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
#endif #endif
#if defined(USE_SH1107_128_64) || defined(USE_SH1107) #ifdef USE_SH1107_128_64
static_cast<SH1106Wire *>(dispdev)->setSubtype(7); static_cast<SH1106Wire *>(dispdev)->setSubtype(7);
#endif #endif
@ -579,13 +542,6 @@ void Screen::setup()
// Apply custom RGB color (e.g. Heltec T114/T190) // Apply custom RGB color (e.g. Heltec T114/T190)
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH); static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#endif #endif
#if defined(MUZI_BASE)
dispdev->delayPoweron = true;
#endif
#if defined(USE_ST7796) && defined(TFT_MESH)
// Custom text color, if defined in variant.h
static_cast<ST7796Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
// === Initialize display and UI system === // === Initialize display and UI system ===
ui->init(); ui->init();
@ -645,12 +601,10 @@ void Screen::setup()
#else #else
if (!config.display.flip_screen) { if (!config.display.flip_screen) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR) defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically(); static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7789) #elif defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically(); static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7796)
static_cast<ST7796Spi *>(dispdev)->mirrorScreen();
#elif !defined(M5STACK_UNITC6L) #elif !defined(M5STACK_UNITC6L)
dispdev->flipScreenVertically(); dispdev->flipScreenVertically();
#endif #endif
@ -683,7 +637,7 @@ void Screen::setup()
touchScreenImpl1->init(); touchScreenImpl1->init();
} }
} }
#elif HAS_TOUCHSCREEN && !defined(USE_EINK) && !HAS_CST226SE #elif HAS_TOUCHSCREEN && !defined(USE_EINK)
touchScreenImpl1 = touchScreenImpl1 =
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch); new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
touchScreenImpl1->init(); touchScreenImpl1->init();
@ -1498,14 +1452,14 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Incoming message // Incoming message
devicestate.has_rx_text_message = true; // Needed to include the message frame devicestate.has_rx_text_message = true; // Needed to include the message frame
hasUnreadMessage = true; // Enables mail icon in the header hasUnreadMessage = true; // Enables mail icon in the header
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view (no-op during text_input) setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
// Only wake/force display if the configuration allows it // Only wake/force display if the configuration allows it
if (shouldWakeOnReceivedMessage()) { if (shouldWakeOnReceivedMessage()) {
setOn(true); // Wake up the screen first setOn(true); // Wake up the screen first
forceDisplay(); // Forces screen redraw forceDisplay(); // Forces screen redraw
} }
// === Prepare banner/popup content === // === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const meshtastic_Channel channel = const meshtastic_Channel channel =
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex()); channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
@ -1529,84 +1483,38 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Unlike generic messages, alerts (when enabled via the ext notif module) ignore any // Unlike generic messages, alerts (when enabled via the ext notif module) ignore any
// 'mute' preferences set to any specific node or channel. // 'mute' preferences set to any specific node or channel.
// If on-screen keyboard is active, show a transient popup over keyboard instead of interrupting it if (isAlert) {
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
// Wake and force redraw so popup is visible immediately
if (shouldWakeOnReceivedMessage()) {
setOn(true);
forceDisplay();
}
// Build popup: title = message source name, content = message text (sanitized)
// Title
char titleBuf[64] = {0};
if (longName && longName[0]) { if (longName && longName[0]) {
// Sanitize sender name snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
std::string t = sanitizeString(longName);
strncpy(titleBuf, t.c_str(), sizeof(titleBuf) - 1);
} else { } else {
strncpy(titleBuf, "Message", sizeof(titleBuf) - 1); strcpy(banner, "Alert Received");
} }
screen->showSimpleBanner(banner, 3000);
// Content: payload bytes may not be null-terminated, remove ASCII_BELL and sanitize } else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
char content[256] = {0}; if (longName && longName[0]) {
{
std::string raw;
raw.reserve(packet->decoded.payload.size);
for (size_t i = 0; i < packet->decoded.payload.size; ++i) {
char c = msgRaw[i];
if (c == ASCII_BELL)
continue; // strip bell
raw.push_back(c);
}
std::string sanitized = sanitizeString(raw);
strncpy(content, sanitized.c_str(), sizeof(content) - 1);
}
NotificationRenderer::showKeyboardMessagePopupWithTitle(titleBuf, content, 3000);
// Maintain existing buzzer behavior on M5 if applicable
#if defined(M5STACK_UNITC6L) #if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
} else {
strcpy(banner, "New Message");
}
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) || (isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
(!isBroadcast(packet->to) && isToUs(packet))) { (!isBroadcast(packet->to) && isToUs(packet))) {
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
// - packet contains an alert and alert bell buzzer is enabled
// - packet is a non-broadcast that is addressed to this node
playLongBeep(); playLongBeep();
} }
#endif
} else {
// No keyboard active: use regular banner flow, respecting mute settings
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
screen->showSimpleBanner(banner, 3000);
} else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else #else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName); screen->showSimpleBanner(banner, 3000);
#endif #endif
} else {
strcpy(banner, "New Message");
}
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
(!isBroadcast(packet->to) && isToUs(packet))) {
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
// - packet contains an alert and alert bell buzzer is enabled
// - packet is a non-broadcast that is addressed to this node
playLongBeep();
}
#else
screen->showSimpleBanner(banner, 3000);
#endif
}
} }
} }
} }
@ -1641,7 +1549,6 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event)
int Screen::handleInputEvent(const InputEvent *event) int Screen::handleInputEvent(const InputEvent *event)
{ {
LOG_INPUT("Screen Input event %u! kb %u", event->inputEvent, event->kbchar);
if (!screenOn) if (!screenOn)
return 0; return 0;
@ -1689,12 +1596,6 @@ int Screen::handleInputEvent(const InputEvent *event)
showPrevFrame(); showPrevFrame();
} else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) { } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
showNextFrame(); showNextFrame();
} else if (event->inputEvent == INPUT_BROKER_UP_LONG) {
// Long press up button for fast frame switching
showPrevFrame();
} else if (event->inputEvent == INPUT_BROKER_DOWN_LONG) {
// Long press down button for fast frame switching
showNextFrame();
} else if (event->inputEvent == INPUT_BROKER_SELECT) { } else if (event->inputEvent == INPUT_BROKER_SELECT) {
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) { if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
menuHandler::homeBaseMenu(); menuHandler::homeBaseMenu();

View File

@ -83,8 +83,6 @@ class Screen
#include <ST7789Spi.h> #include <ST7789Spi.h>
#elif defined(USE_SPISSD1306) #elif defined(USE_SPISSD1306)
#include <SSD1306Spi.h> #include <SSD1306Spi.h>
#elif defined(USE_ST7796)
#include <ST7796Spi.h>
#else #else
// the SH1106/SSD1306 variant is auto-detected // the SH1106/SSD1306 variant is auto-detected
#include <AutoOLEDWire.h> #include <AutoOLEDWire.h>
@ -251,8 +249,6 @@ class Screen : public concurrency::OSThread
bool isOverlayBannerShowing(); bool isOverlayBannerShowing();
bool isScreenOn() { return screenOn; }
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier // Stores the last 4 of our hardware ID, to make finding the device for pairing easier
// FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class // FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class
char ourId[5]; char ourId[5];

View File

@ -73,8 +73,7 @@
#endif #endif
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts // The screen is bigger so use bigger fonts
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19

View File

@ -17,12 +17,6 @@ namespace graphics
void determineResolution(int16_t screenheight, int16_t screenwidth) void determineResolution(int16_t screenheight, int16_t screenwidth)
{ {
#ifdef FORCE_LOW_RES
isHighResolution = false;
return;
#endif
if (screenwidth > 128) { if (screenwidth > 128) {
isHighResolution = true; isHighResolution = true;
} }
@ -30,6 +24,11 @@ void determineResolution(int16_t screenheight, int16_t screenwidth)
if (screenwidth > 128 && screenheight <= 64) { if (screenwidth > 128 && screenheight <= 64) {
isHighResolution = false; isHighResolution = false;
} }
// Special case for Heltec Wireless Tracker v1.1
if (screenwidth == 160 && screenheight == 80) {
isHighResolution = false;
}
} }
// === Shared External State === // === Shared External State ===

View File

@ -1,6 +1,5 @@
#include "configuration.h" #include "configuration.h"
#include "main.h" #include "main.h"
#if USE_TFTDISPLAY
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h" #include "platform/portduino/PortduinoGlue.h"
@ -124,11 +123,6 @@ static void rak14014_tpIntHandle(void)
_rak14014_touch_int = true; _rak14014_touch_int = true;
} }
#elif defined(HACKADAY_COMMUNICATOR)
#include <Arduino_GFX_Library.h>
Arduino_DataBus *bus = nullptr;
Arduino_GFX *tft = nullptr;
#elif defined(ST72xx_DE) #elif defined(ST72xx_DE)
#include <LovyanGFX.hpp> #include <LovyanGFX.hpp>
#include <TCA9534.h> #include <TCA9534.h>
@ -433,35 +427,33 @@ static LGFX *tft = nullptr;
#include "lgfx/v1/Touch.hpp" #include "lgfx/v1/Touch.hpp"
namespace lgfx namespace lgfx
{ {
inline namespace v1 inline namespace v1
{ {
class TOUCH_CHSC6X : public ITouch class TOUCH_CHSC6X : public ITouch
{ {
public: public:
TOUCH_CHSC6X(void) TOUCH_CHSC6X(void)
{ {
_cfg.i2c_addr = TOUCH_SLAVE_ADDRESS; _cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
_cfg.x_min = 0; _cfg.x_min = 0;
_cfg.x_max = 240; _cfg.x_max = 240;
_cfg.y_min = 0; _cfg.y_min = 0;
_cfg.y_max = 320; _cfg.y_max = 320;
}; };
bool init(void) override bool init(void) override {
{ if(chsc6xTouch==nullptr) {
if (chsc6xTouch == nullptr) { chsc6xTouch=new chsc6x(&Wire1,TOUCH_SDA_PIN,TOUCH_SCL_PIN,TOUCH_INT_PIN,TOUCH_RST_PIN);
chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
} }
chsc6xTouch->chsc6x_init(); chsc6xTouch->chsc6x_init();
return true; return true;
}; };
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override uint_fast8_t getTouchRaw(touch_point_t* tp, uint_fast8_t count) override {
{ uint16_t raw_x,raw_y;
uint16_t raw_x, raw_y; if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y)==0) {
if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y) == 0) { tp[0].x = 320-1-raw_y;
tp[0].x = 320 - 1 - raw_y; tp[0].y = 240-1-raw_x ;
tp[0].y = 240 - 1 - raw_x;
tp[0].size = 1; tp[0].size = 1;
tp[0].id = 1; tp[0].id = 1;
return 1; return 1;
@ -470,14 +462,13 @@ class TOUCH_CHSC6X : public ITouch
return 0; return 0;
}; };
void wakeup(void) override{}; void wakeup(void) override {};
void sleep(void) override{}; void sleep(void) override {};
private: private:
chsc6x *chsc6xTouch = nullptr; chsc6x *chsc6xTouch=nullptr;
}; };
} // namespace v1 }
} // namespace lgfx }
#endif #endif
class LGFX : public lgfx::LGFX_Device class LGFX : public lgfx::LGFX_Device
{ {
@ -522,9 +513,9 @@ class LGFX : public lgfx::LGFX_Device
{ // Set the display panel control. { // Set the display panel control.
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings. auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable) cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable) cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable)
cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable) cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable)
// The following setting values ​​are general initial values ​​for each panel, so please comment out any // The following setting values ​​are general initial values ​​for each panel, so please comment out any
// unknown items and try them. // unknown items and try them.
@ -1139,6 +1130,9 @@ static LGFX *tft = nullptr;
#endif #endif
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \
defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \
(ARCH_PORTDUINO && HAS_SCREEN != 0)
#include "SPILock.h" #include "SPILock.h"
#include "TFTDisplay.h" #include "TFTDisplay.h"
#include <SPI.h> #include <SPI.h>
@ -1274,15 +1268,12 @@ void TFTDisplay::display(bool fromBlank)
x_LastPixelUpdate = x; x_LastPixelUpdate = x;
} }
} }
#if defined(HACKADAY_COMMUNICATOR)
tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate],
(x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1);
#else
// Step 4: Send the changed pixels on this line to the screen as a single block transfer. // Step 4: Send the changed pixels on this line to the screen as a single block transfer.
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port. // This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1, tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
&linePixelBuffer[x_FirstPixelUpdate]); &linePixelBuffer[x_FirstPixelUpdate]);
#endif
somethingChanged = true; somethingChanged = true;
} }
y++; y++;
@ -1346,8 +1337,6 @@ void TFTDisplay::sendCommand(uint8_t com)
display(true); display(true);
if (portduino_config.displayBacklight.pin > 0) if (portduino_config.displayBacklight.pin > 0)
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON); digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
#elif defined(HACKADAY_COMMUNICATOR)
tft->displayOn();
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
tft->wakeup(); tft->wakeup();
tft->powerSaveOff(); tft->powerSaveOff();
@ -1360,8 +1349,7 @@ void TFTDisplay::sendCommand(uint8_t com)
unphone.backlight(true); // using unPhone library unphone.backlight(true); // using unPhone library
#endif #endif
#ifdef RAK14014 #ifdef RAK14014
#elif !defined(M5STACK) && !defined(ST7789_CS) && \ #elif !defined(M5STACK) && !defined(ST7789_CS) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
!defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
tft->setBrightness(172); tft->setBrightness(172);
#endif #endif
break; break;
@ -1373,8 +1361,6 @@ void TFTDisplay::sendCommand(uint8_t com)
tft->clear(); tft->clear();
if (portduino_config.displayBacklight.pin > 0) if (portduino_config.displayBacklight.pin > 0)
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON); digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
#elif defined(HACKADAY_COMMUNICATOR)
tft->displayOff();
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
tft->sleep(); tft->sleep();
tft->powerSaveOn(); tft->powerSaveOn();
@ -1387,7 +1373,7 @@ void TFTDisplay::sendCommand(uint8_t com)
unphone.backlight(false); // using unPhone library unphone.backlight(false); // using unPhone library
#endif #endif
#ifdef RAK14014 #ifdef RAK14014
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) #elif !defined(M5STACK)
tft->setBrightness(0); tft->setBrightness(0);
#endif #endif
break; break;
@ -1403,7 +1389,7 @@ void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
{ {
#ifdef RAK14014 #ifdef RAK14014
// todo // todo
#elif !defined(HACKADAY_COMMUNICATOR) #else
tft->setBrightness(_brightness); tft->setBrightness(_brightness);
LOG_DEBUG("Brightness is set to value: %i ", _brightness); LOG_DEBUG("Brightness is set to value: %i ", _brightness);
#endif #endif
@ -1421,7 +1407,7 @@ bool TFTDisplay::hasTouch(void)
{ {
#ifdef RAK14014 #ifdef RAK14014
return true; return true;
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) #elif !defined(M5STACK)
return tft->touch() != nullptr; return tft->touch() != nullptr;
#else #else
return false; return false;
@ -1440,7 +1426,7 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
} else { } else {
return false; return false;
} }
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) #elif !defined(M5STACK)
return tft->getTouch(x, y); return tft->getTouch(x, y);
#else #else
return false; return false;
@ -1459,12 +1445,6 @@ bool TFTDisplay::connect()
LOG_INFO("Do TFT init"); LOG_INFO("Do TFT init");
#ifdef RAK14014 #ifdef RAK14014
tft = new TFT_eSPI; tft = new TFT_eSPI;
#elif defined(HACKADAY_COMMUNICATOR)
bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, 38 /* SCK */, 21 /* MOSI */, GFX_NOT_DEFINED /* MISO */, HSPI /* spi_num */);
tft = new Arduino_NV3007(bus, 40, 0 /* rotation */, false /* IPS */, 142 /* width */, 428 /* height */, 12 /* col offset 1 */,
0 /* row offset 1 */, 14 /* col offset 2 */, 0 /* row offset 2 */, nv3007_279_init_operations,
sizeof(nv3007_279_init_operations));
#else #else
tft = new LGFX; tft = new LGFX;
#endif #endif
@ -1475,15 +1455,8 @@ bool TFTDisplay::connect()
#ifdef UNPHONE #ifdef UNPHONE
unphone.backlight(true); // using unPhone library unphone.backlight(true); // using unPhone library
#endif #endif
#ifdef HACKADAY_COMMUNICATOR
bool beginStatus = tft->begin();
if (beginStatus)
LOG_DEBUG("TFT Success!");
else
LOG_ERROR("TFT Fail!");
#else
tft->init(); tft->init();
#endif
#if defined(M5STACK) #if defined(M5STACK)
tft->setRotation(0); tft->setRotation(0);
@ -1516,4 +1489,4 @@ bool TFTDisplay::connect()
return true; return true;
} }
#endif // USE_TFTDISPLAY #endif

View File

@ -101,23 +101,3 @@ void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
else else
snprintf(timeStr, maxLength, "unknown age"); snprintf(timeStr, maxLength, "unknown age");
} }
void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs)
{
uint32_t days = uptimeMillis / 86400000;
uint32_t hours = (uptimeMillis % 86400000) / 3600000;
uint32_t mins = (uptimeMillis % 3600000) / 60000;
uint32_t secs = (uptimeMillis % 60000) / 1000;
if (days) {
snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours);
} else if (hours) {
snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins);
} else if (!includeSecs) {
snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins);
} else if (mins) {
snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs);
} else {
snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs);
}
}

View File

@ -24,10 +24,3 @@ bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int
* @param maxLength Maximum length of the resulting string buffer * @param maxLength Maximum length of the resulting string buffer
*/ */
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength); void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
/**
* Get a compact human-readable string that only shows the largest non-zero time components.
* For example, 0 days 1 hour 2 minutes will display as "1h 2m" but 1 day 2 hours 3 minutes
* will display as "1d 2h".
*/
void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs = false);

View File

@ -506,9 +506,6 @@ void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool
centeredTextY -= 1; centeredTextY -= 1;
} }
} }
#ifdef MUZI_BASE // Correct issue with character vertical position on MUZI_BASE
centeredTextY -= 2;
#endif
display->drawString(textX, centeredTextY, keyText.c_str()); display->drawString(textX, centeredTextY, keyText.c_str());
} }

View File

@ -11,7 +11,6 @@
#include "gps/RTC.h" #include "gps/RTC.h"
#include "graphics/ScreenFonts.h" #include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h" #include "graphics/SharedUIDisplay.h"
#include "graphics/TimeFormatters.h"
#include "graphics/images.h" #include "graphics/images.h"
#include "main.h" #include "main.h"
#include "mesh/Channels.h" #include "mesh/Channels.h"
@ -97,7 +96,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796) || ARCH_PORTDUINO) && \ ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
8, imgQuestionL1); 8, imgQuestionL1);
@ -109,8 +108,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
#endif #endif
} else { } else {
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16, display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
8, imgSFL1); 8, imgSFL1);
@ -126,7 +124,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
// TODO: Raspberry Pi supports more than just the one screen size // TODO: Raspberry Pi supports more than just the one screen size
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796) || ARCH_PORTDUINO) && \ ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1); imgInfoL1);
@ -652,7 +650,17 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it
char uptimeStr[32] = ""; char uptimeStr[32] = "";
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr)); uint32_t uptime = millis() / 1000;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
if (days)
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
textWidth = display->getStringWidth(uptimeStr); textWidth = display->getStringWidth(uptimeStr);
nameX = (SCREEN_WIDTH - textWidth) / 2; nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], uptimeStr); display->drawString(nameX, getTextPositions(display)[line++], uptimeStr);
@ -721,4 +729,4 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1
} // namespace DebugRenderer } // namespace DebugRenderer
} // namespace graphics } // namespace graphics
#endif #endif

View File

@ -13,7 +13,6 @@
#include "input/RotaryEncoderInterruptImpl1.h" #include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h" #include "input/UpDownInterruptImpl1.h"
#include "main.h" #include "main.h"
#include "mesh/Default.h"
#include "mesh/MeshTypes.h" #include "mesh/MeshTypes.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h" #include "modules/CannedMessageModule.h"
@ -121,7 +120,6 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
auto changes = SEGMENT_CONFIG; auto changes = SEGMENT_CONFIG;
// This is needed as we wait til picking the LoRa region to generate keys for the first time. // This is needed as we wait til picking the LoRa region to generate keys for the first time.
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (!owner.is_licensed) { if (!owner.is_licensed) {
bool keygenSuccess = false; bool keygenSuccess = false;
if (config.security.private_key.size == 32) { if (config.security.private_key.size == 32) {
@ -142,7 +140,6 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
} }
} }
#endif
config.lora.tx_enabled = true; config.lora.tx_enabled = true;
initRegion(); initRegion();
if (myRegion->dutyCycle < 100) { if (myRegion->dutyCycle < 100) {
@ -578,7 +575,7 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu() void menuHandler::systemBaseMenu()
{ {
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, WiFiToggle, PowerMenu, Test, enumEnd }; enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
int options = 1; int options = 1;
@ -594,10 +591,6 @@ void menuHandler::systemBaseMenu()
optionsArray[options] = "Bluetooth Toggle"; optionsArray[options] = "Bluetooth Toggle";
#endif #endif
optionsEnumArray[options++] = Bluetooth; optionsEnumArray[options++] = Bluetooth;
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
optionsArray[options] = "WiFi Toggle";
optionsEnumArray[options++] = WiFiToggle;
#endif
#if defined(M5STACK_UNITC6L) #if defined(M5STACK_UNITC6L)
optionsArray[options] = "Power"; optionsArray[options] = "Power";
#else #else
@ -635,11 +628,6 @@ void menuHandler::systemBaseMenu()
} else if (selected == Bluetooth) { } else if (selected == Bluetooth) {
menuQueue = bluetooth_toggle_menu; menuQueue = bluetooth_toggle_menu;
screen->runNow(); screen->runNow();
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
} else if (selected == WiFiToggle) {
menuQueue = wifi_toggle_menu;
screen->runNow();
#endif
} else if (selected == Back && !test_enabled) { } else if (selected == Back && !test_enabled) {
test_count++; test_count++;
if (test_count > 4) { if (test_count > 4) {
@ -948,9 +936,7 @@ void menuHandler::BluetoothToggleMenu()
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3; bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) if (selected == 1 || selected == 2) {
return;
else if (selected != (config.bluetooth.enabled ? 1 : 2)) {
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0}; InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event); inputBroker->injectInputEvent(&event);
} }
@ -1049,8 +1035,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 10; bannerOptions.optionsCount = 10;
bannerOptions.bannerCallback = [display](int selected) -> void { bannerOptions.bannerCallback = [display](int selected) -> void {
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \ #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
HAS_TFT || defined(HACKADAY_COMMUNICATOR)
uint8_t TFT_MESH_r = 0; uint8_t TFT_MESH_r = 0;
uint8_t TFT_MESH_g = 0; uint8_t TFT_MESH_g = 0;
uint8_t TFT_MESH_b = 0; uint8_t TFT_MESH_b = 0;
@ -1290,28 +1275,19 @@ void menuHandler::wifiBaseMenu()
void menuHandler::wifiToggleMenu() void menuHandler::wifiToggleMenu()
{ {
enum optionsNumbers { Back, Wifi_disable, Wifi_enable }; enum optionsNumbers { Back, Wifi_toggle };
static const char *optionsArray[] = {"Back", "WiFi Disabled", "WiFi Enabled"}; static const char *optionsArray[] = {"Back", "Disable"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "WiFi Actions"; bannerOptions.message = "Disable Wifi and\nEnable Bluetooth?";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3; bannerOptions.optionsCount = 2;
if (config.network.wifi_enabled == true)
bannerOptions.InitialSelected = 2;
else
bannerOptions.InitialSelected = 1;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Wifi_disable) { if (selected == Wifi_toggle) {
config.network.wifi_enabled = false; config.network.wifi_enabled = false;
config.bluetooth.enabled = true; config.bluetooth.enabled = true;
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} else if (selected == Wifi_enable) {
config.network.wifi_enabled = true;
config.bluetooth.enabled = false;
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@ -1359,7 +1335,7 @@ void menuHandler::screenOptionsMenu()
static int optionsEnumArray[5] = {Back}; static int optionsEnumArray[5] = {Back};
int options = 1; int options = 1;
#if defined(T_DECK) || defined(T_LORA_PAGER) || defined(HACKADAY_COMMUNICATOR) #if defined(T_DECK) || defined(T_LORA_PAGER)
optionsArray[options] = "Show Long/Short Name"; optionsArray[options] = "Show Long/Short Name";
optionsEnumArray[options++] = NodeNameLength; optionsEnumArray[options++] = NodeNameLength;
#endif #endif
@ -1371,13 +1347,12 @@ void menuHandler::screenOptionsMenu()
} }
// Only show screen color for TFT displays // Only show screen color for TFT displays
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \ #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
HAS_TFT || defined(HACKADAY_COMMUNICATOR)
optionsArray[options] = "Screen Color"; optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor; optionsEnumArray[options++] = ScreenColor;
#endif #endif
optionsArray[options] = "Frame Visibility Toggle"; optionsArray[options] = "Frame Visiblity Toggle";
optionsEnumArray[options++] = FrameToggles; optionsEnumArray[options++] = FrameToggles;
optionsArray[options] = "Display Units"; optionsArray[options] = "Display Units";

View File

@ -85,13 +85,9 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat
void NotificationRenderer::resetBanner() void NotificationRenderer::resetBanner()
{ {
notificationTypeEnum previousType = current_notification_type;
alertBannerMessage[0] = '\0'; alertBannerMessage[0] = '\0';
current_notification_type = notificationTypeEnum::none; current_notification_type = notificationTypeEnum::none;
OnScreenKeyboardModule::instance().clearPopup();
inEvent.inputEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0; inEvent.kbchar = 0;
curSelected = 0; curSelected = 0;
@ -104,13 +100,6 @@ void NotificationRenderer::resetBanner()
currentNumber = 0; currentNumber = 0;
nodeDB->pause_sort(false); nodeDB->pause_sort(false);
// If we're exiting from text_input (virtual keyboard), stop module and trigger frame update
// to ensure any messages received during keyboard use are now displayed
if (previousType == notificationTypeEnum::text_input && screen) {
OnScreenKeyboardModule::instance().stop(false);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} }
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state) void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
@ -174,15 +163,13 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
// modulo to extract // modulo to extract
uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1)); uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1));
// Handle input // Handle input
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS || if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
if (this_digit == 9) { if (this_digit == 9) {
currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1)); currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1));
} else { } else {
currentNumber += (pow_of_10(numDigits - curSelected - 1)); currentNumber += (pow_of_10(numDigits - curSelected - 1));
} }
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS || } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
if (this_digit == 0) { if (this_digit == 0) {
currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1)); currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1));
} else { } else {
@ -264,10 +251,10 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
// Handle input // Handle input
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT || if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS || inEvent.inputEvent == INPUT_BROKER_UP_LONG) { inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT || } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
inEvent.inputEvent == INPUT_BROKER_USER_PRESS || inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) { inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
alertBannerCallback(selectedNodenum); alertBannerCallback(selectedNodenum);
@ -381,10 +368,10 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input // Handle input
if (alertBannerOptions > 0) { if (alertBannerOptions > 0) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT || if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS || inEvent.inputEvent == INPUT_BROKER_UP_LONG) { inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT || } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
inEvent.inputEvent == INPUT_BROKER_USER_PRESS || inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) { inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) { if (optionsEnumPtr != nullptr) {
@ -782,8 +769,40 @@ void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiStat
} }
if (inEvent.inputEvent != INPUT_BROKER_NONE) { if (inEvent.inputEvent != INPUT_BROKER_NONE) {
bool handled = OnScreenKeyboardModule::processVirtualKeyboardInput(inEvent, virtualKeyboard); if (inEvent.inputEvent == INPUT_BROKER_UP) {
if (!handled && inEvent.inputEvent == INPUT_BROKER_CANCEL) { // high frequency for move cursor left/right than up/down with encoders
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorLeft();
} else {
virtualKeyboard->moveCursorUp();
}
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN) {
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorRight();
} else {
virtualKeyboard->moveCursorDown();
}
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) {
virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
virtualKeyboard->moveCursorUp();
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
virtualKeyboard->moveCursorDown();
} else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
virtualKeyboard->handlePress();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) {
virtualKeyboard->handleLongPress();
} else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) {
auto callback = textInputCallback; auto callback = textInputCallback;
delete virtualKeyboard; delete virtualKeyboard;
virtualKeyboard = nullptr; virtualKeyboard = nullptr;
@ -802,28 +821,12 @@ void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiStat
inEvent.inputEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
} }
// Re-check pointer before drawing to avoid use-after-free and crashes
if (!virtualKeyboard) {
// Ensure we exit text_input state and restore frames
if (current_notification_type == notificationTypeEnum::text_input) {
resetBanner();
}
if (screen) {
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
// If screen is null, do nothing (safe fallback)
return;
}
// Clear the screen to avoid overlapping with underlying frames or overlays // Clear the screen to avoid overlapping with underlying frames or overlays
display->setColor(BLACK); display->setColor(BLACK);
display->fillRect(0, 0, display->getWidth(), display->getHeight()); display->fillRect(0, 0, display->getWidth(), display->getHeight());
display->setColor(WHITE); display->setColor(WHITE);
// Draw the virtual keyboard // Draw the virtual keyboard
virtualKeyboard->draw(display, 0, 0); virtualKeyboard->draw(display, 0, 0);
// Draw transient popup overlay (if any) managed by OnScreenKeyboardModule
OnScreenKeyboardModule::instance().drawPopupOverlay(display);
} else { } else {
// If virtualKeyboard is null, reset the banner to avoid getting stuck // If virtualKeyboard is null, reset the banner to avoid getting stuck
LOG_INFO("Virtual keyboard is null - resetting banner"); LOG_INFO("Virtual keyboard is null - resetting banner");
@ -836,12 +839,5 @@ bool NotificationRenderer::isOverlayBannerShowing()
return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil); return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil);
} }
void NotificationRenderer::showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs)
{
if (!title || !content || current_notification_type != notificationTypeEnum::text_input)
return;
OnScreenKeyboardModule::instance().showPopup(title, content, durationMs);
}
} // namespace graphics } // namespace graphics
#endif #endif

View File

@ -4,7 +4,6 @@
#include "OLEDDisplayUi.h" #include "OLEDDisplayUi.h"
#include "graphics/Screen.h" #include "graphics/Screen.h"
#include "graphics/VirtualKeyboard.h" #include "graphics/VirtualKeyboard.h"
#include "modules/OnScreenKeyboardModule.h"
#include <functional> #include <functional>
#include <string> #include <string>
#define MAX_LINES 5 #define MAX_LINES 5
@ -32,7 +31,6 @@ class NotificationRenderer
static bool pauseBanner; static bool pauseBanner;
static void resetBanner(); static void resetBanner();
static void showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs);
static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state); static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state); static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state); static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);

View File

@ -11,7 +11,6 @@
#include "graphics/Screen.h" #include "graphics/Screen.h"
#include "graphics/ScreenFonts.h" #include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h" #include "graphics/SharedUIDisplay.h"
#include "graphics/TimeFormatters.h"
#include "graphics/images.h" #include "graphics/images.h"
#include "main.h" #include "main.h"
#include "target_specific.h" #include "target_specific.h"
@ -257,8 +256,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
} }
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
if (isHighResolution) { if (isHighResolution) {
@ -385,7 +383,17 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
// === 4. Uptime (only show if metric is present) === // === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = ""; char uptimeStr[32] = "";
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) { if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
getUptimeStr(node->device_metrics.uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr)); uint32_t uptime = node->device_metrics.uptime_seconds;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
if (days)
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
} }
if (uptimeStr[0] && line < 5) { if (uptimeStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], uptimeStr); display->drawString(x, getTextPositions(display)[line++], uptimeStr);
@ -584,8 +592,18 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online"); drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
#endif #endif
char uptimeStr[32] = ""; char uptimeStr[32] = "";
uint32_t uptime = millis() / 1000;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
#if !defined(M5STACK_UNITC6L) #if !defined(M5STACK_UNITC6L)
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr)); if (days)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
#endif #endif
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr); display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
@ -1030,17 +1048,36 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) { if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
// === Second Row: Last GPS Fix === // === Second Row: Last GPS Fix ===
if (gpsStatus->getLastFixMillis() > 0) { if (gpsStatus->getLastFixMillis() > 0) {
uint32_t delta = millis() - gpsStatus->getLastFixMillis(); uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
char uptimeStr[32]; uint32_t days = delta / 86400;
uint32_t hours = (delta % 86400) / 3600;
uint32_t mins = (delta % 3600) / 60;
uint32_t secs = delta % 60;
char buf[32];
#if defined(USE_EINK) #if defined(USE_EINK)
// E-Ink: skip seconds, show only days/hours/mins // E-Ink: skip seconds, show only days/hours/mins
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false); if (days > 0) {
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
} else if (hours > 0) {
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
} else {
snprintf(buf, sizeof(buf), "Last: %um", mins);
}
#else #else
// Non E-Ink: include seconds where useful // Non E-Ink: include seconds where useful
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true); if (days > 0) {
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
} else if (hours > 0) {
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
} else if (mins > 0) {
snprintf(buf, sizeof(buf), "Last: %um %us", mins, secs);
} else {
snprintf(buf, sizeof(buf), "Last: %us", secs);
}
#endif #endif
display->drawString(0, getTextPositions(display)[line++], uptimeStr); display->drawString(0, getTextPositions(display)[line++], buf);
} else { } else {
display->drawString(0, getTextPositions(display)[line++], "Last: ?"); display->drawString(0, getTextPositions(display)[line++], "Last: ?");
} }

View File

@ -28,7 +28,7 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR) || ARCH_PORTDUINO) && \ ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};

View File

@ -2,21 +2,19 @@
#define icon_width 50 #define icon_width 50
#define icon_height 28 #define icon_height 28
static uint8_t icon_bits[] = { static uint8_t icon_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x03, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0xf6,0xef,0xdf,0x71,0x00,0x00,0x1c,
0x00, 0x00, 0x00, 0x80, 0x07, 0xC0, 0x07, 0x00, 0x00, 0x00, 0xC0, 0x1F, 0xf7,0xef,0xdf,0x71,0x00,0x00,0xbc,0xf7,0xef,0xdf,0x71,0x00,0x00,0xfc,0x77,
0xC0, 0x0F, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0xE0, 0x0F, 0x00, 0x00, 0x00, 0xe0,0xc0,0x71,0x00,0x00,0xfc,0x77,0xe0,0xc0,0x71,0x00,0x00,0xfc,0xf7,0xef,
0xE0, 0x0F, 0xF0, 0x1F, 0x00, 0x00, 0x00, 0xF0, 0x07, 0xF0, 0x3F, 0x00, 0xdf,0x7f,0x00,0x00,0x5c,0xf7,0xef,0xdf,0x7f,0x00,0x00,0x1c,0xf7,0xef,0xdf,
0x00, 0x00, 0xF8, 0x03, 0xF8, 0x7F, 0x00, 0x00, 0x00, 0xF8, 0x03, 0xFC, 0x7f,0x00,0x00,0x1c,0x77,0x00,0xdc,0x71,0x00,0x00,0x1c,0x77,0x00,0xdc,0x71,
0x7F, 0x00, 0x00, 0x00, 0xFC, 0x01, 0xFC, 0xFE, 0x00, 0x00, 0x00, 0xFE, 0x00,0x00,0x1c,0xf7,0xef,0xdf,0x71,0x00,0x00,0x1c,0xf7,0xef,0xdf,0x71,0x00,
0x00, 0xFE, 0xFC, 0x01, 0x00, 0x00, 0xFE, 0x00, 0x7F, 0xFC, 0x01, 0x00, 0x00,0x1c,0xf7,0xef,0xdf,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00, 0x7F, 0x00, 0x3F, 0xF8, 0x03, 0x00, 0x80, 0x3F, 0x80, 0x3F, 0xF0, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xc1,0x7c,0x7e,0x00,0x00,
0x07, 0x00, 0x80, 0x3F, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0xC0, 0x1F, 0xC0, 0x00,0x80,0xe3,0xfe,0xfe,0x00,0x00,0x00,0x80,0xf7,0xc6,0xc6,0x00,0x00,0x00,
0x0F, 0xE0, 0x0F, 0x00, 0xE0, 0x0F, 0xE0, 0x0F, 0xC0, 0x1F, 0x00, 0xE0, 0x80,0xff,0xc6,0xc6,0x00,0x00,0x00,0x80,0xdd,0xc6,0xc6,0x00,0x00,0x00,0x80,
0x0F, 0xF0, 0x07, 0x80, 0x1F, 0x00, 0xF0, 0x07, 0xF8, 0x03, 0x80, 0x3F, 0xc9,0xc6,0xc6,0x00,0x00,0x00,0x80,0xc1,0xc6,0xc6,0x00,0x00,0x00,0x80,0xc1,
0x00, 0xF8, 0x03, 0xF8, 0x03, 0x00, 0x7F, 0x00, 0xFC, 0x03, 0xFC, 0x01, 0xfe,0xfe,0x00,0x00,0x00,0x80,0xc1,0x7c,0x7e,0x00,0x00,0x00,0x00,0x00,0x00,
0x00, 0x7E, 0x00, 0xFC, 0x01, 0xFE, 0x00, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFE, 0x00, 0x00, 0xFC, 0x01, 0x7E, 0x00, 0x7F, 0x00, 0x00, 0xF8, 0x01, 0x00
0x7E, 0x00, 0x3E, 0x00, 0x00, 0xF8, 0x01, 0x38, 0x00, 0x3C, 0x00, 0x00, };
0x70, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, #endif
0x00, 0x00, 0x00, 0x00, };
#endif

View File

@ -1,30 +1,16 @@
#ifndef USERPREFS_HAS_SPLASH #ifndef USERPREFS_HAS_SPLASH
#define icon_width 50 #define icon_width 50
#define icon_height 20 #define icon_height 20
static uint8_t icon_bits[] = { static uint8_t icon_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xec,0xdf,0xbf,0xe3,0x00,0x00,0x38,
0x80, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x80, 0xee,0xdf,0xbf,0xe3,0x00,0x00,0x78,0xef,0xdf,0xbf,0xe3,0x00,0x00,0xf8,0xef,
0x07, 0xc0, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xc0,0x81,0xe3,0x00,0x00,0xf8,0xef,0xc0,0x81,0xe3,0x00,0x00,0xf8,0xef,0xdf,
0xc0, 0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xe0, 0xbf,0xff,0x00,0x00,0xb8,0xee,0xdf,0xbf,0xff,0x00,0x00,0x38,0xee,0xdf,0xbf,
0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x1f, 0xff,0x00,0x00,0x38,0xee,0x00,0xb8,0xe3,0x00,0x00,0x38,0xee,0x00,0xb8,0xe3,
0x00, 0x00, 0x00, 0xf0, 0x03, 0xf0, 0x3f, 0x00, 0x00,0x00,0x38,0xee,0xdf,0xbf,0xe3,0x00,0x00,0x38,0xee,0xdf,0xbf,0xe3,0x00,
0x00, 0x00, 0xf8, 0x03, 0xf8, 0x7f, 0x00, 0x00, 0x00,0x38,0xee,0xdf,0xbf,0xe3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00, 0xf8, 0x01, 0xfc, 0x7e, 0x00, 0x00, 0x00, 0x00,0x00,0x00,0x00,0x91,0x39,0x00,0x00,0x00,0x00,0x00,0x5b,0x4a,0x00,0x00,
0xfc, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0xfe, 0x00,0x00,0x00,0x55,0x4a,0x00,0x00,0x00,0x00,0x00,0x91,0x39,0x00,0x00,0x00,
0x00, 0x7e, 0xf8, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00,0x00,0x00,0x00,0x00
0x3f, 0xf8, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x1f, };
0xf0, 0x01, 0x00, 0x00, 0x1f, 0x80, 0x1f, 0xe0, #endif
0x03, 0x00, 0x80, 0x1f, 0xc0, 0x0f, 0xe0, 0x03,
0x00, 0x80, 0x0f, 0xc0, 0x07, 0xc0, 0x07, 0x00,
0xc0, 0x0f, 0xe0, 0x07, 0x80, 0x0f, 0x00, 0xe0,
0x07, 0xf0, 0x03, 0x80, 0x1f, 0x00, 0xe0, 0x03,
0xf8, 0x03, 0x00, 0x1f, 0x00, 0xf0, 0x03, 0xf8,
0x01, 0x00, 0x3f, 0x00, 0xf8, 0x01, 0xfc, 0x00,
0x00, 0x7e, 0x00, 0xfc, 0x00, 0xfe, 0x00, 0x00,
0x7e, 0x00, 0xfc, 0x00, 0x7e, 0x00, 0x00, 0xfc,
0x00, 0x7e, 0x00, 0x3f, 0x00, 0x00, 0xf8, 0x00,
0x7e, 0x00, 0x3e, 0x00, 0x00, 0xf8, 0x00, 0x38,
0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
#endif

View File

@ -124,7 +124,7 @@ uint32_t InkHUD::AppletFont::toUtf32(std::string utf8)
utf32 |= (utf8.at(3) & 0b00111111); utf32 |= (utf8.at(3) & 0b00111111);
break; break;
default: default:
return 0; assert(false);
} }
return utf32; return utf32;

View File

@ -5,6 +5,7 @@ A pattern / collection of resources for creating custom UIs, to target small gro
For an example, see the `heltec-vision-master-e290-inkhud` platformio env. For an example, see the `heltec-vision-master-e290-inkhud` platformio env.
- platformio.ini - platformio.ini
- suppress default Meshtastic components (Screen, ButtonThread, etc) - suppress default Meshtastic components (Screen, ButtonThread, etc)
- define `MESHTASTIC_INCLUDE_NICHE_GRAPHICS` - define `MESHTASTIC_INCLUDE_NICHE_GRAPHICS`
- (possibly) Edit `build_src_filter` to include our new nicheGraphics.h file - (possibly) Edit `build_src_filter` to include our new nicheGraphics.h file

View File

@ -1,217 +0,0 @@
#if defined(HACKADAY_COMMUNICATOR)
#include "HackadayCommunicatorKeyboard.h"
#include "main.h"
#define _TCA8418_COLS 10
#define _TCA8418_ROWS 8
#define _TCA8418_NUM_KEYS 80
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
using Key = TCA8418KeyboardBase::TCA8418Key;
constexpr uint8_t modifierRightShiftKey = 30;
constexpr uint8_t modifierRightShift = 0b0001;
constexpr uint8_t modifierLeftShiftKey = 76; // keynum -1
constexpr uint8_t modifierLeftShift = 0b0001;
// constexpr uint8_t modifierSymKey = 42;
// constexpr uint8_t modifierSym = 0b0010;
// Num chars per key, Modulus for rotating through characters
static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = {
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 0, 0, 0, 2, 1, 2, 2, 0, 1, 1, 0,
};
static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{},
{},
{'+'},
{'9'},
{'8'},
{'7'},
{'2'},
{'3'},
{'4'},
{'5'},
{Key::ESC},
{'q', 'Q'},
{'w', 'W'},
{'e', 'E'},
{'r', 'R'},
{'t', 'T'},
{'y', 'Y'},
{'u', 'U'},
{'i', 'I'},
{'o', 'O'},
{Key::TAB},
{'a', 'A'},
{'s', 'S'},
{'d', 'D'},
{'f', 'F'},
{'g', 'G'},
{'h', 'H'},
{'j', 'J'},
{'k', 'K'},
{'l', 'L'},
{},
{'z', 'Z'},
{'x', 'X'},
{'c', 'C'},
{'v', 'V'},
{'b', 'B'},
{'n', 'N'},
{'m', 'M'},
{',', '<'},
{'.', '>'},
{},
{},
{},
{'\\'},
{' '},
{},
{Key::RIGHT},
{Key::DOWN},
{Key::LEFT},
{},
{},
{},
{'-'},
{'6', '^'},
{'5', '%'},
{'4', '$'},
{'[', '{'},
{']', '}'},
{'p', 'P'},
{},
{},
{},
{'*'},
{'3', '#'},
{'2', '@'},
{'1', '!'},
{Key::SELECT},
{'\'', '"'},
{';', ':'},
{},
{},
{},
{'/', '?'},
{'='},
{'.', '>'},
{'0', ')'},
{},
{Key::UP},
{Key::BSP},
{}};
HackadayCommunicatorKeyboard::HackadayCommunicatorKeyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
last_tap(0L), char_idx(0), tap_interval(0)
{
reset();
}
void HackadayCommunicatorKeyboard::reset(void)
{
TCA8418KeyboardBase::reset();
enableInterrupts();
}
// handle multi-key presses (shift and alt)
void HackadayCommunicatorKeyboard::trigger()
{
uint8_t count = keyCount();
if (count == 0)
return;
for (uint8_t i = 0; i < count; ++i) {
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
uint8_t key = k & 0x7F;
if (k & 0x80) {
pressed(key);
} else {
released();
state = Idle;
}
}
}
void HackadayCommunicatorKeyboard::pressed(uint8_t key)
{
if (state == Init || state == Busy) {
return;
}
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
modifierFlag = 0;
}
uint8_t next_key = 0;
int row = (key - 1) / 10;
int col = (key - 1) % 10;
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
return; // Invalid key
}
next_key = row * _TCA8418_COLS + col;
state = Held;
uint32_t now = millis();
tap_interval = now - last_tap;
updateModifierFlag(next_key);
if (isModifierKey(next_key)) {
last_modifier_time = now;
}
if (tap_interval < 0) {
last_tap = 0;
state = Busy;
return;
}
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
char_idx = 0;
} else {
char_idx += 1;
}
last_key = next_key;
last_tap = now;
}
void HackadayCommunicatorKeyboard::released()
{
if (state != Held) {
return;
}
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
last_key = -1;
state = Idle;
return;
}
uint32_t now = millis();
last_tap = now;
if (HackadayCommunicatorTapMod[last_key])
queueEvent(HackadayCommunicatorTapMap[last_key][modifierFlag % HackadayCommunicatorTapMod[last_key]]);
if (isModifierKey(last_key) == false)
modifierFlag = 0;
}
void HackadayCommunicatorKeyboard::updateModifierFlag(uint8_t key)
{
if (key == modifierRightShiftKey) {
modifierFlag ^= modifierRightShift;
} else if (key == modifierLeftShiftKey) {
modifierFlag ^= modifierLeftShift;
}
}
bool HackadayCommunicatorKeyboard::isModifierKey(uint8_t key)
{
return (key == modifierRightShiftKey || key == modifierLeftShiftKey);
}
#endif

View File

@ -1,26 +0,0 @@
#include "TCA8418KeyboardBase.h"
class HackadayCommunicatorKeyboard : public TCA8418KeyboardBase
{
public:
HackadayCommunicatorKeyboard();
void reset(void);
void trigger(void) override;
virtual ~HackadayCommunicatorKeyboard() {}
protected:
void pressed(uint8_t key) override;
void released(void) override;
void updateModifierFlag(uint8_t key);
bool isModifierKey(uint8_t key);
private:
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
uint32_t last_modifier_time; // Timestamp of the last modifier key press
int8_t last_key;
int8_t next_key;
uint32_t last_tap;
uint8_t char_idx;
int32_t tap_interval;
};

View File

@ -52,7 +52,7 @@ int InputBroker::handleInputEvent(const InputEvent *event)
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule && if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule &&
moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) { moduleConfig.external_notification.enabled) {
externalNotificationModule->stopNow(); externalNotificationModule->stopNow();
} }

View File

@ -3,12 +3,6 @@
#include "Observer.h" #include "Observer.h"
#include "freertosinc.h" #include "freertosinc.h"
#ifdef InputBrokerDebug
#define LOG_INPUT(...) LOG_DEBUG(__VA_ARGS__)
#else
#define LOG_INPUT(...)
#endif
enum input_broker_event { enum input_broker_event {
INPUT_BROKER_NONE = 0, INPUT_BROKER_NONE = 0,
INPUT_BROKER_SELECT = 10, INPUT_BROKER_SELECT = 10,

View File

@ -2,8 +2,6 @@
#include "configuration.h" #include "configuration.h"
#include <Throttle.h> #include <Throttle.h>
SerialKeyboard *globalSerialKeyboard = nullptr;
#ifdef INPUTBROKER_SERIAL_TYPE #ifdef INPUTBROKER_SERIAL_TYPE
#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file #define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file
@ -27,8 +25,6 @@ unsigned char KeyMap[3][4][10] = {{{'.', 'a', 'd', 'g', 'j', 'm', 'p', 't', 'w',
SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name) SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name)
{ {
this->_originName = name; this->_originName = name;
globalSerialKeyboard = this;
} }
void SerialKeyboard::erase() void SerialKeyboard::erase()
@ -89,21 +85,9 @@ int32_t SerialKeyboard::runOnce()
e.source = this->_originName; e.source = this->_originName;
// SELECT OR SEND OR CANCEL EVENT // SELECT OR SEND OR CANCEL EVENT
if (!(shiftRegister2 & (1 << 3))) { if (!(shiftRegister2 & (1 << 3))) {
if (shift > 0) { e.inputEvent = INPUT_BROKER_UP;
e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED
e.kbchar = 0x09; // TAB
shift = 0; // reset shift after TAB
} else {
e.inputEvent = INPUT_BROKER_LEFT;
}
} else if (!(shiftRegister2 & (1 << 2))) { } else if (!(shiftRegister2 & (1 << 2))) {
if (shift > 0) { e.inputEvent = INPUT_BROKER_RIGHT;
e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED
e.kbchar = 0x09; // TAB
shift = 0; // reset shift after TAB
} else {
e.inputEvent = INPUT_BROKER_RIGHT;
}
e.kbchar = 0; e.kbchar = 0;
} else if (!(shiftRegister2 & (1 << 1))) { } else if (!(shiftRegister2 & (1 << 1))) {
e.inputEvent = INPUT_BROKER_SELECT; e.inputEvent = INPUT_BROKER_SELECT;

View File

@ -8,8 +8,6 @@ class SerialKeyboard : public Observable<const InputEvent *>, public concurrency
public: public:
explicit SerialKeyboard(const char *name); explicit SerialKeyboard(const char *name);
uint8_t getShift() const { return shift; }
protected: protected:
virtual int32_t runOnce() override; virtual int32_t runOnce() override;
void erase(); void erase();
@ -24,6 +22,4 @@ class SerialKeyboard : public Observable<const InputEvent *>, public concurrency
int lastKeyPressed = 13; int lastKeyPressed = 13;
int quickPress = 0; int quickPress = 0;
unsigned long lastPressTime = 0; unsigned long lastPressTime = 0;
}; };
extern SerialKeyboard *globalSerialKeyboard;

View File

@ -88,50 +88,6 @@ int32_t TrackballInterruptBase::runOnce()
} }
} }
if (directionDetected && directionStartTime > 0) {
uint32_t directionDuration = millis() - directionStartTime;
uint8_t directionPressedNow = 0;
directionInterval++;
if (!digitalRead(_pinUp)) {
directionPressedNow = TB_ACTION_UP;
} else if (!digitalRead(_pinDown)) {
directionPressedNow = TB_ACTION_DOWN;
} else if (!digitalRead(_pinLeft)) {
directionPressedNow = TB_ACTION_LEFT;
} else if (!digitalRead(_pinRight)) {
directionPressedNow = TB_ACTION_RIGHT;
}
const uint8_t DIRECTION_REPEAT_THRESHOLD = 3;
if (directionPressedNow == TB_ACTION_NONE) {
// Reset state
directionDetected = false;
directionStartTime = 0;
directionInterval = 0;
this->action = TB_ACTION_NONE;
} else if (directionDuration >= LONG_PRESS_DURATION && directionInterval >= DIRECTION_REPEAT_THRESHOLD) {
// repeat event when long press these direction.
switch (directionPressedNow) {
case TB_ACTION_UP:
e.inputEvent = this->_eventUp;
break;
case TB_ACTION_DOWN:
e.inputEvent = this->_eventDown;
break;
case TB_ACTION_LEFT:
e.inputEvent = this->_eventLeft;
break;
case TB_ACTION_RIGHT:
e.inputEvent = this->_eventRight;
break;
}
directionInterval = 0;
}
}
#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball #if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
if (this->action == TB_ACTION_PRESSED && !pressDetected) { if (this->action == TB_ACTION_PRESSED && !pressDetected) {
// Start long press detection // Start long press detection
@ -157,22 +113,17 @@ int32_t TrackballInterruptBase::runOnce()
pressDetected = true; pressDetected = true;
pressStartTime = millis(); pressStartTime = millis();
// Don't send event yet, wait to see if it's a long press // Don't send event yet, wait to see if it's a long press
} else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp) && !directionDetected) { } else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp)) {
directionDetected = true; // LOG_DEBUG("Trackball event UP");
directionStartTime = millis();
e.inputEvent = this->_eventUp; e.inputEvent = this->_eventUp;
// send event first,will automatically trigger every 50ms * 3 after 500ms } else if (this->action == TB_ACTION_DOWN && !digitalRead(_pinDown)) {
} else if (this->action == TB_ACTION_DOWN && !digitalRead(_pinDown) && !directionDetected) { // LOG_DEBUG("Trackball event DOWN");
directionDetected = true;
directionStartTime = millis();
e.inputEvent = this->_eventDown; e.inputEvent = this->_eventDown;
} else if (this->action == TB_ACTION_LEFT && !digitalRead(_pinLeft) && !directionDetected) { } else if (this->action == TB_ACTION_LEFT && !digitalRead(_pinLeft)) {
directionDetected = true; // LOG_DEBUG("Trackball event LEFT");
directionStartTime = millis();
e.inputEvent = this->_eventLeft; e.inputEvent = this->_eventLeft;
} else if (this->action == TB_ACTION_RIGHT && !digitalRead(_pinRight) && !directionDetected) { } else if (this->action == TB_ACTION_RIGHT && !digitalRead(_pinRight)) {
directionDetected = true; // LOG_DEBUG("Trackball event RIGHT");
directionStartTime = millis();
e.inputEvent = this->_eventRight; e.inputEvent = this->_eventRight;
} }
#endif #endif

View File

@ -49,14 +49,10 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
// Long press detection for press button // Long press detection for press button
uint32_t pressStartTime = 0; uint32_t pressStartTime = 0;
uint32_t directionStartTime = 0;
uint8_t directionInterval = 0;
bool pressDetected = false; bool pressDetected = false;
bool directionDetected = false;
uint32_t lastLongPressEventTime = 0; uint32_t lastLongPressEventTime = 0;
uint32_t lastDirectionPressEventTime = 0;
static const uint32_t LONG_PRESS_DURATION = 500; // ms static const uint32_t LONG_PRESS_DURATION = 500; // ms
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300; // ms - interval between repeated long press events static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 500; // ms - interval between repeated long press events
private: private:
input_broker_event _eventDown = INPUT_BROKER_NONE; input_broker_event _eventDown = INPUT_BROKER_NONE;

View File

@ -3,14 +3,6 @@
#include "InputBroker.h" #include "InputBroker.h"
#include "mesh/NodeDB.h" #include "mesh/NodeDB.h"
#ifndef UPDOWN_LONG_PRESS_DURATION
#define UPDOWN_LONG_PRESS_DURATION 300
#endif
#ifndef UPDOWN_LONG_PRESS_REPEAT_INTERVAL
#define UPDOWN_LONG_PRESS_REPEAT_INTERVAL 300
#endif
class UpDownInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread class UpDownInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
{ {
public: public:
@ -48,8 +40,8 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
uint32_t lastPressLongEventTime = 0; uint32_t lastPressLongEventTime = 0;
uint32_t lastUpLongEventTime = 0; uint32_t lastUpLongEventTime = 0;
uint32_t lastDownLongEventTime = 0; uint32_t lastDownLongEventTime = 0;
static const uint32_t LONG_PRESS_DURATION = UPDOWN_LONG_PRESS_DURATION; static const uint32_t LONG_PRESS_DURATION = 300;
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = UPDOWN_LONG_PRESS_REPEAT_INTERVAL; static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300;
private: private:
uint8_t _pinDown = 0; uint8_t _pinDown = 0;

View File

@ -7,8 +7,6 @@
#include "TDeckProKeyboard.h" #include "TDeckProKeyboard.h"
#elif defined(T_LORA_PAGER) #elif defined(T_LORA_PAGER)
#include "TLoraPagerKeyboard.h" #include "TLoraPagerKeyboard.h"
#elif defined(HACKADAY_COMMUNICATOR)
#include "HackadayCommunicatorKeyboard.h"
#else #else
#include "TCA8418Keyboard.h" #include "TCA8418Keyboard.h"
#endif #endif
@ -22,8 +20,6 @@ KbI2cBase::KbI2cBase(const char *name)
TCAKeyboard(*(new TDeckProKeyboard())) TCAKeyboard(*(new TDeckProKeyboard()))
#elif defined(T_LORA_PAGER) #elif defined(T_LORA_PAGER)
TCAKeyboard(*(new TLoraPagerKeyboard())) TCAKeyboard(*(new TLoraPagerKeyboard()))
#elif defined(HACKADAY_COMMUNICATOR)
TCAKeyboard(*(new HackadayCommunicatorKeyboard()))
#else #else
TCAKeyboard(*(new TCA8418Keyboard())) TCAKeyboard(*(new TCA8418Keyboard()))
#endif #endif
@ -332,7 +328,7 @@ int32_t KbI2cBase::runOnce()
break; break;
} }
if (e.inputEvent != INPUT_BROKER_NONE) { if (e.inputEvent != INPUT_BROKER_NONE) {
// LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar);
this->notifyObservers(&e); this->notifyObservers(&e);
} }
TCAKeyboard.trigger(); TCAKeyboard.trigger();

View File

@ -394,10 +394,7 @@ void setup()
io.pinMode(EXPANDS_GPIO_EN, OUTPUT); io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
io.digitalWrite(EXPANDS_GPIO_EN, HIGH); io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
io.pinMode(EXPANDS_SD_PULLEN, INPUT); io.pinMode(EXPANDS_SD_PULLEN, INPUT);
#elif defined(HACKADAY_COMMUNICATOR)
pinMode(KB_INT, INPUT);
#endif #endif
concurrency::hasBeenSetup = true; concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
@ -480,10 +477,6 @@ void setup()
#ifdef RESET_OLED #ifdef RESET_OLED
pinMode(RESET_OLED, OUTPUT); pinMode(RESET_OLED, OUTPUT);
digitalWrite(RESET_OLED, 1); digitalWrite(RESET_OLED, 1);
delay(2);
digitalWrite(RESET_OLED, 0);
delay(10);
digitalWrite(RESET_OLED, 1);
#endif #endif
#ifdef SENSOR_POWER_CTRL_PIN #ifdef SENSOR_POWER_CTRL_PIN
@ -881,7 +874,7 @@ void setup()
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_SPISSD1306) || defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR) defined(USE_SPISSD1306)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry); screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) && if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) &&
@ -966,7 +959,6 @@ void setup()
i2cScanner.reset(); i2cScanner.reset();
#endif #endif
#if !defined(MESHTASTIC_EXCLUDE_PKI)
// warn the user about a low entropy key // warn the user about a low entropy key
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) { if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
LOG_WARN(LOW_ENTROPY_WARNING); LOG_WARN(LOW_ENTROPY_WARNING);
@ -977,7 +969,6 @@ void setup()
service->sendClientNotification(cn); service->sendClientNotification(cn);
nodeDB->hasWarned = true; nodeDB->hasWarned = true;
} }
#endif
// buttons are now inputBroker, so have to come after setupModules // buttons are now inputBroker, so have to come after setupModules
#if HAS_BUTTON #if HAS_BUTTON
@ -1158,7 +1149,7 @@ void setup()
// the current region name) // the current region name)
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_ST7796) || defined(USE_SPISSD1306) || defined(HACKADAY_COMMUNICATOR) defined(USE_SPISSD1306)
if (screen) if (screen)
screen->setup(); screen->setup();
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)

View File

@ -57,7 +57,14 @@ class Default
// Note: Kept as uint32_t to match the public API parameter type // Note: Kept as uint32_t to match the public API parameter type
static float congestionScalingCoefficient(uint32_t numOnlineNodes) static float congestionScalingCoefficient(uint32_t numOnlineNodes)
{ {
if (numOnlineNodes <= 40) { // Increase frequency of broadcasts for small networks regardless of preset
if (numOnlineNodes <= 10) {
return 0.6;
} else if (numOnlineNodes <= 20) {
return 0.7;
} else if (numOnlineNodes <= 30) {
return 0.8;
} else if (numOnlineNodes <= 40) {
return 1.0; return 1.0;
} else { } else {
float throttlingFactor = 0.075; float throttlingFactor = 0.075;

View File

@ -244,8 +244,6 @@ template <typename T> void LR11x0Interface<T>::startReceive()
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
int err = int err =
lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
if (err)
LOG_ERROR("StartReceive error: %d", err);
assert(err == RADIOLIB_ERR_NONE); assert(err == RADIOLIB_ERR_NONE);
RadioLibInterface::startReceive(); RadioLibInterface::startReceive();
@ -306,4 +304,4 @@ template <typename T> bool LR11x0Interface<T>::sleep()
return true; return true;
} }
#endif #endif

View File

@ -653,7 +653,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32); strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \ #if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT)) && \ defined(ELECROW_PANEL)||defined(HELTEC_V4_TFT)) && \
HAS_TFT HAS_TFT
// switch BT off by default; use TFT programming mode or hotkey to enable // switch BT off by default; use TFT programming mode or hotkey to enable
config.bluetooth.enabled = false; config.bluetooth.enabled = false;
@ -664,8 +664,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.bluetooth.fixed_pin = defaultBLEPin; config.bluetooth.fixed_pin = defaultBLEPin;
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) || \ defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306)
defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR)
bool hasScreen = true; bool hasScreen = true;
#ifdef HELTEC_MESH_NODE_T114 #ifdef HELTEC_MESH_NODE_T114
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET); uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
@ -735,9 +734,6 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.display.screen_on_secs = 30; config.display.screen_on_secs = 30;
config.display.wake_on_tap_or_motion = true; config.display.wake_on_tap_or_motion = true;
#endif #endif
#ifdef COMPASS_ORIENTATION
config.display.compass_orientation = COMPASS_ORIENTATION;
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
if (WiFiOTA::isUpdated()) { if (WiFiOTA::isUpdated()) {
WiFiOTA::recoverConfig(&config.network); WiFiOTA::recoverConfig(&config.network);
@ -2012,7 +2008,6 @@ UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed; return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
} }
#if !defined(MESHTASTIC_EXCLUDE_PKI)
bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest) bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest)
{ {
if (keyToTest.size == 32) { if (keyToTest.size == 32) {
@ -2027,7 +2022,6 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
} }
return false; return false;
} }
#endif
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location) bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
{ {

View File

@ -283,9 +283,7 @@ class NodeDB
bool hasValidPosition(const meshtastic_NodeInfoLite *n); bool hasValidPosition(const meshtastic_NodeInfoLite *n);
#if !defined(MESHTASTIC_EXCLUDE_PKI)
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest); bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
#endif
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location); bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location, bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
@ -375,4 +373,4 @@ extern uint32_t error_address;
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
ModuleConfig_TelemetryConfig_size + ModuleConfig_size) ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
// Please do not remove this comment, it makes trunk and compiler happy at the same time. // Please do not remove this comment, it makes trunk and compiler happy at the same time.

View File

@ -193,14 +193,15 @@ const RegionInfo regions[] = {
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 0, 10, true, false, true), RDEF(LORA_24, 2400.0f, 2483.5f, 100, 0, 10, true, false, true),
/* /*
Custom region Without limits
*/ */
RDEF(NO_LIMITS, 863.0f, 870.0f, 100, 0, 30, true, false, false), RDEF(NO_LIMITS, 863.0f, 870.0f, 100, 0, 36, true, false, false),
/* /*
This needs to be last. Same as US. This needs to be last. Same as US.
*/ */
RDEF(UNSET, 902.0f, 928.0f, 100, 0, 30, true, false, false) RDEF(UNSET, 902.0f, 928.0f, 100, 0, 30, true, false, false)
}; };
const RegionInfo *myRegion; const RegionInfo *myRegion;

View File

@ -286,7 +286,7 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode {
meshtastic_Config_LoRaConfig_RegionCode_NP_865 = 25, meshtastic_Config_LoRaConfig_RegionCode_NP_865 = 25,
/* Brazil 902MHz */ /* Brazil 902MHz */
meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26, meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26,
/* NO_LIMITS */ /* NO LIMITS */
meshtastic_Config_LoRaConfig_RegionCode_NO_LIMITS = 27 meshtastic_Config_LoRaConfig_RegionCode_NO_LIMITS = 27
} meshtastic_Config_LoRaConfig_RegionCode; } meshtastic_Config_LoRaConfig_RegionCode;

View File

@ -288,12 +288,6 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_WISMESH_TAP_V2 = 116, meshtastic_HardwareModel_WISMESH_TAP_V2 = 116,
/* RAK3401 */ /* RAK3401 */
meshtastic_HardwareModel_RAK3401 = 117, meshtastic_HardwareModel_RAK3401 = 117,
/* RAK6421 Hat+ */
meshtastic_HardwareModel_RAK6421 = 118,
/* Elecrow ThinkNode M4 */
meshtastic_HardwareModel_THINKNODE_M4 = 119,
/* Elecrow ThinkNode M6 */
meshtastic_HardwareModel_THINKNODE_M6 = 120,
/* ------------------------------------------------------------------------------------------------------------------------------------------ /* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */ ------------------------------------------------------------------------------------------------------------------------------------------ */
@ -831,11 +825,7 @@ typedef struct _meshtastic_MeshPacket {
Note: Our crypto implementation uses this field as well. Note: Our crypto implementation uses this field as well.
See [crypto](/docs/overview/encryption) for details. */ See [crypto](/docs/overview/encryption) for details. */
uint32_t from; uint32_t from;
/* The (immediate) destination for this packet /* The (immediate) destination for this packet */
If the value is 4,294,967,295 (maximum value of an unsigned 32bit integer), this indicates that the packet was
not destined for a specific node, but for a channel as indicated by the value of `channel` below.
If the value is another, this indicates that the packet was destined for a specific
node (i.e. a kind of "Direct Message" to this node) and not broadcast on a channel. */
uint32_t to; uint32_t to;
/* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on. /* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on.
If unset, packet was on the primary channel. If unset, packet was on the primary channel.

View File

@ -773,7 +773,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
config.lora = validatedLora; config.lora = validatedLora;
// If we're setting region for the first time, init the region and regenerate the keys // If we're setting region for the first time, init the region and regenerate the keys
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (!owner.is_licensed) { if (!owner.is_licensed) {
bool keygenSuccess = false; bool keygenSuccess = false;
if (config.security.private_key.size == 32) { if (config.security.private_key.size == 32) {
@ -792,7 +791,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
} }
} }
#endif
config.lora.tx_enabled = true; config.lora.tx_enabled = true;
initRegion(); initRegion();
if (myRegion->dutyCycle < 100) { if (myRegion->dutyCycle < 100) {

View File

@ -16,7 +16,6 @@
#include "graphics/draw/NotificationRenderer.h" #include "graphics/draw/NotificationRenderer.h"
#include "graphics/emotes.h" #include "graphics/emotes.h"
#include "graphics/images.h" #include "graphics/images.h"
#include "input/SerialKeyboard.h"
#include "main.h" // for cardkb_found #include "main.h" // for cardkb_found
#include "mesh/generated/meshtastic/cannedmessages.pb.h" #include "mesh/generated/meshtastic/cannedmessages.pb.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
@ -837,7 +836,6 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) { if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) {
payload = 0x08; payload = 0x08;
lastTouchMillis = millis(); lastTouchMillis = millis();
requestFocus();
runOnce(); runOnce();
return true; return true;
} }
@ -846,7 +844,6 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
if (event->inputEvent == INPUT_BROKER_LEFT) { if (event->inputEvent == INPUT_BROKER_LEFT) {
payload = INPUT_BROKER_LEFT; payload = INPUT_BROKER_LEFT;
lastTouchMillis = millis(); lastTouchMillis = millis();
requestFocus();
runOnce(); runOnce();
return true; return true;
} }
@ -854,7 +851,6 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
if (event->inputEvent == INPUT_BROKER_RIGHT) { if (event->inputEvent == INPUT_BROKER_RIGHT) {
payload = INPUT_BROKER_RIGHT; payload = INPUT_BROKER_RIGHT;
lastTouchMillis = millis(); lastTouchMillis = millis();
requestFocus();
runOnce(); runOnce();
return true; return true;
} }
@ -1018,7 +1014,8 @@ int32_t CannedMessageModule::runOnce()
// Clean up virtual keyboard if needed when going inactive // Clean up virtual keyboard if needed when going inactive
if (graphics::NotificationRenderer::virtualKeyboard && graphics::NotificationRenderer::textInputCallback == nullptr) { if (graphics::NotificationRenderer::virtualKeyboard && graphics::NotificationRenderer::textInputCallback == nullptr) {
LOG_INFO("Performing delayed virtual keyboard cleanup"); LOG_INFO("Performing delayed virtual keyboard cleanup");
graphics::OnScreenKeyboardModule::instance().stop(false); delete graphics::NotificationRenderer::virtualKeyboard;
graphics::NotificationRenderer::virtualKeyboard = nullptr;
} }
temporaryMessage = ""; temporaryMessage = "";
@ -1035,7 +1032,9 @@ int32_t CannedMessageModule::runOnce()
// Clean up virtual keyboard after sending // Clean up virtual keyboard after sending
if (graphics::NotificationRenderer::virtualKeyboard) { if (graphics::NotificationRenderer::virtualKeyboard) {
LOG_INFO("Cleaning up virtual keyboard after message send"); LOG_INFO("Cleaning up virtual keyboard after message send");
graphics::OnScreenKeyboardModule::instance().stop(false); delete graphics::NotificationRenderer::virtualKeyboard;
graphics::NotificationRenderer::virtualKeyboard = nullptr;
graphics::NotificationRenderer::textInputCallback = nullptr;
graphics::NotificationRenderer::resetBanner(); graphics::NotificationRenderer::resetBanner();
} }
@ -1093,7 +1092,9 @@ int32_t CannedMessageModule::runOnce()
// Clean up virtual keyboard if it exists during timeout // Clean up virtual keyboard if it exists during timeout
if (graphics::NotificationRenderer::virtualKeyboard) { if (graphics::NotificationRenderer::virtualKeyboard) {
LOG_INFO("Cleaning up virtual keyboard due to module timeout"); LOG_INFO("Cleaning up virtual keyboard due to module timeout");
graphics::OnScreenKeyboardModule::instance().stop(false); delete graphics::NotificationRenderer::virtualKeyboard;
graphics::NotificationRenderer::virtualKeyboard = nullptr;
graphics::NotificationRenderer::textInputCallback = nullptr;
graphics::NotificationRenderer::resetBanner(); graphics::NotificationRenderer::resetBanner();
} }
@ -1844,88 +1845,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer);
} }
#if INPUTBROKER_SERIAL_TYPE == 1 // --- Draw Free Text input with multi-emote support and proper line wrapping ---
// Chatter Modifier key mode label (right side)
{
uint8_t mode = globalSerialKeyboard ? globalSerialKeyboard->getShift() : 0;
const char *label = (mode == 0) ? "a" : (mode == 1) ? "A" : "#";
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const int16_t th = FONT_HEIGHT_SMALL;
const int16_t tw = display->getStringWidth(label);
const int16_t padX = 3;
const int16_t padY = 2;
const int16_t r = 3;
const int16_t bw = tw + padX * 2;
const int16_t bh = th + padY * 2;
const int16_t bx = x + display->getWidth() - bw - 2;
const int16_t by = y + display->getHeight() - bh - 2;
display->setColor(WHITE);
display->fillRect(bx + r, by, bw - r * 2, bh);
display->fillRect(bx, by + r, r, bh - r * 2);
display->fillRect(bx + bw - r, by + r, r, bh - r * 2);
display->fillCircle(bx + r, by + r, r);
display->fillCircle(bx + bw - r - 1, by + r, r);
display->fillCircle(bx + r, by + bh - r - 1, r);
display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r);
display->setColor(BLACK);
display->drawString(bx + padX, by + padY, label);
}
// LEFT-SIDE DESTINATION-HINT BOX (“Dest: Shift + ◄”)
{
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *label = "Dest: Shift + ";
int16_t labelW = display->getStringWidth(label);
// triangle size visually matches glyph height, not full line height
const int triH = FONT_HEIGHT_SMALL - 3;
const int triW = triH * 0.7;
const int16_t padX = 3;
const int16_t padY = 2;
const int16_t r = 3;
const int16_t bw = labelW + triW + padX * 2 + 2;
const int16_t bh = FONT_HEIGHT_SMALL + padY * 2;
const int16_t bx = x + 2;
const int16_t by = y + display->getHeight() - bh - 2;
// Rounded white box
display->setColor(WHITE);
display->fillRect(bx + r, by, bw - (r * 2), bh);
display->fillRect(bx, by + r, r, bh - (r * 2));
display->fillRect(bx + bw - r, by + r, r, bh - (r * 2));
display->fillCircle(bx + r, by + r, r);
display->fillCircle(bx + bw - r - 1, by + r, r);
display->fillCircle(bx + r, by + bh - r - 1, r);
display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r);
// Draw text
display->setColor(BLACK);
display->drawString(bx + padX, by + padY, label);
// Perfectly center triangle on text baseline
int16_t tx = bx + padX + labelW;
int16_t ty = by + padY + (FONT_HEIGHT_SMALL / 2) - (triH / 2) - 1; // -1 for optical centering
// ◄ Left-pointing triangle
display->fillTriangle(tx + triW, ty, // top-right
tx, ty + triH / 2, // left center
tx + triW, ty + triH // bottom-right
);
}
#endif
// Draw Free Text input with multi-emote support and proper line wrapping
display->setColor(WHITE); display->setColor(WHITE);
{ {
int inputY = 0 + y + FONT_HEIGHT_SMALL; int inputY = 0 + y + FONT_HEIGHT_SMALL;

View File

@ -168,7 +168,7 @@ int32_t ExternalNotificationModule::runOnce()
delay = EXT_NOTIFICATION_FAST_THREAD_MS; delay = EXT_NOTIFICATION_FAST_THREAD_MS;
#endif #endif
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) #ifdef T_WATCH_S3
drv.go(); drv.go();
#endif #endif
} }
@ -283,7 +283,7 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
#ifdef UNPHONE #ifdef UNPHONE
unphone.rgb(red, green, blue); unphone.rgb(red, green, blue);
#endif #endif
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) #ifdef T_WATCH_S3
if (on) { if (on) {
drv.go(); drv.go();
} else { } else {
@ -310,16 +310,18 @@ void ExternalNotificationModule::stopNow()
rtttl::stop(); rtttl::stop();
#ifdef HAS_I2S #ifdef HAS_I2S
LOG_INFO("Stop audioThread playback"); LOG_INFO("Stop audioThread playback");
audioThread->stop(); if (audioThread->isPlaying())
audioThread->stop();
#endif #endif
// Turn off all outputs // Turn off all outputs
LOG_INFO("Turning off setExternalStates"); LOG_INFO("Turning off setExternalStates: ");
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
setExternalState(i, false); setExternalState(i, false);
externalTurnedOn[i] = 0; externalTurnedOn[i] = 0;
LOG_INFO("%d ", i);
} }
setIntervalFromNow(0); setIntervalFromNow(0);
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) #ifdef T_WATCH_S3
drv.stop(); drv.stop();
#endif #endif
@ -541,19 +543,6 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
(!isBroadcast(mp.to) && isToUs(&mp))) { (!isBroadcast(mp.to) && isToUs(&mp))) {
// Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
isNagging = true; isNagging = true;
#ifdef T_LORA_PAGER
if (canBuzz()) {
drv.setWaveform(0, 16); // Long buzzer 100%
drv.setWaveform(1, 0); // Pause
drv.setWaveform(2, 16);
drv.setWaveform(3, 0);
drv.setWaveform(4, 16);
drv.setWaveform(5, 0);
drv.setWaveform(6, 16);
drv.setWaveform(7, 0);
drv.go();
}
#endif
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
setExternalState(2, true); setExternalState(2, true);
} else { } else {

View File

@ -13,8 +13,6 @@
#include "input/TrackballInterruptImpl1.h" #include "input/TrackballInterruptImpl1.h"
#endif #endif
#include "modules/StatusLEDModule.h"
#if !MESHTASTIC_EXCLUDE_I2C #if !MESHTASTIC_EXCLUDE_I2C
#include "input/cardKbI2cImpl.h" #include "input/cardKbI2cImpl.h"
#endif #endif
@ -121,10 +119,6 @@ void setupModules()
buzzerFeedbackThread = new BuzzerFeedbackThread(); buzzerFeedbackThread = new BuzzerFeedbackThread();
} }
#endif #endif
#if defined(LED_CHARGE) || defined(LED_PAIRING)
statusLEDModule = new StatusLEDModule();
#endif
#if !MESHTASTIC_EXCLUDE_ADMIN #if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule(); adminModule = new AdminModule();
#endif #endif
@ -181,25 +175,24 @@ void setupModules()
// new ReplyModule(); // new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(T_LORA_PAGER) rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
#ifdef T_LORA_PAGER
// use a special FSM based rotary encoder version for T-LoRa Pager // use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl(); rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) { if (!rotaryEncoderImpl->init()) {
delete rotaryEncoderImpl; delete rotaryEncoderImpl;
rotaryEncoderImpl = nullptr; rotaryEncoderImpl = nullptr;
} }
#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2) #else
upDownInterruptImpl1 = new UpDownInterruptImpl1(); upDownInterruptImpl1 = new UpDownInterruptImpl1();
if (!upDownInterruptImpl1->init()) { if (!upDownInterruptImpl1->init()) {
delete upDownInterruptImpl1; delete upDownInterruptImpl1;
upDownInterruptImpl1 = nullptr; upDownInterruptImpl1 = nullptr;
} }
#else
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
#endif #endif
cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init(); cardKbI2cImpl->init();

View File

@ -1,272 +0,0 @@
#include "configuration.h"
#if HAS_SCREEN
#include "graphics/SharedUIDisplay.h"
#include "graphics/draw/NotificationRenderer.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#include "modules/OnScreenKeyboardModule.h"
#include <Arduino.h>
#include <algorithm>
namespace graphics
{
OnScreenKeyboardModule &OnScreenKeyboardModule::instance()
{
static OnScreenKeyboardModule inst;
return inst;
}
OnScreenKeyboardModule::~OnScreenKeyboardModule()
{
if (keyboard) {
delete keyboard;
keyboard = nullptr;
}
}
void OnScreenKeyboardModule::start(const char *header, const char *initialText, uint32_t durationMs,
std::function<void(const std::string &)> cb)
{
if (keyboard) {
delete keyboard;
keyboard = nullptr;
}
keyboard = new VirtualKeyboard();
callback = cb;
if (header)
keyboard->setHeader(header);
if (initialText)
keyboard->setInputText(initialText);
// Route VK submission/cancel events back into the module
keyboard->setCallback([this](const std::string &text) {
if (text.empty()) {
this->onCancel();
} else {
this->onSubmit(text);
}
});
// Maintain legacy compatibility hooks
NotificationRenderer::virtualKeyboard = keyboard;
NotificationRenderer::textInputCallback = callback;
}
void OnScreenKeyboardModule::stop(bool callEmptyCallback)
{
auto cb = callback;
callback = nullptr;
if (keyboard) {
delete keyboard;
keyboard = nullptr;
}
// Keep NotificationRenderer legacy pointers in sync
NotificationRenderer::virtualKeyboard = nullptr;
NotificationRenderer::textInputCallback = nullptr;
clearPopup();
if (callEmptyCallback && cb)
cb("");
}
void OnScreenKeyboardModule::handleInput(const InputEvent &event)
{
if (!keyboard)
return;
if (processVirtualKeyboardInput(event, keyboard))
return;
if (event.inputEvent == INPUT_BROKER_CANCEL)
onCancel();
}
bool OnScreenKeyboardModule::processVirtualKeyboardInput(const InputEvent &event, VirtualKeyboard *targetKeyboard)
{
if (!targetKeyboard)
return false;
switch (event.inputEvent) {
case INPUT_BROKER_UP:
case INPUT_BROKER_UP_LONG:
targetKeyboard->moveCursorUp();
return true;
case INPUT_BROKER_DOWN:
case INPUT_BROKER_DOWN_LONG:
targetKeyboard->moveCursorDown();
return true;
case INPUT_BROKER_LEFT:
case INPUT_BROKER_ALT_PRESS:
targetKeyboard->moveCursorLeft();
return true;
case INPUT_BROKER_RIGHT:
case INPUT_BROKER_USER_PRESS:
targetKeyboard->moveCursorRight();
return true;
case INPUT_BROKER_SELECT:
targetKeyboard->handlePress();
return true;
case INPUT_BROKER_SELECT_LONG:
targetKeyboard->handleLongPress();
return true;
default:
return false;
}
}
bool OnScreenKeyboardModule::draw(OLEDDisplay *display)
{
if (!keyboard)
return false;
// Timeout
if (keyboard->isTimedOut()) {
onCancel();
return false;
}
// Clear full screen behind keyboard
display->setColor(BLACK);
display->fillRect(0, 0, display->getWidth(), display->getHeight());
display->setColor(WHITE);
keyboard->draw(display, 0, 0);
// Draw popup overlay if needed
drawPopup(display);
return true;
}
void OnScreenKeyboardModule::onSubmit(const std::string &text)
{
auto cb = callback;
stop(false);
if (cb)
cb(text);
}
void OnScreenKeyboardModule::onCancel()
{
stop(true);
}
void OnScreenKeyboardModule::showPopup(const char *title, const char *content, uint32_t durationMs)
{
if (!title || !content)
return;
strncpy(popupTitle, title, sizeof(popupTitle) - 1);
popupTitle[sizeof(popupTitle) - 1] = '\0';
strncpy(popupMessage, content, sizeof(popupMessage) - 1);
popupMessage[sizeof(popupMessage) - 1] = '\0';
popupUntil = millis() + durationMs;
popupVisible = true;
}
void OnScreenKeyboardModule::clearPopup()
{
popupTitle[0] = '\0';
popupMessage[0] = '\0';
popupUntil = 0;
popupVisible = false;
}
void OnScreenKeyboardModule::drawPopupOverlay(OLEDDisplay *display)
{
// Only render the popup overlay (without drawing the keyboard)
drawPopup(display);
}
void OnScreenKeyboardModule::drawPopup(OLEDDisplay *display)
{
if (!popupVisible)
return;
if (millis() > popupUntil || popupMessage[0] == '\0') {
popupVisible = false;
return;
}
// Build lines and leverage NotificationRenderer inverted box drawing for consistent style
constexpr uint16_t maxContentLines = 3;
const bool hasTitle = popupTitle[0] != '\0';
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const uint16_t maxWrapWidth = display->width() - 40;
auto wrapText = [&](const char *text, uint16_t availableWidth) -> std::vector<std::string> {
std::vector<std::string> wrapped;
std::string current;
std::string word;
const char *p = text;
while (*p && wrapped.size() < maxContentLines) {
while (*p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
if (*p == '\n') {
if (!current.empty()) {
wrapped.push_back(current);
current.clear();
if (wrapped.size() >= maxContentLines)
break;
}
}
++p;
}
if (!*p || wrapped.size() >= maxContentLines)
break;
word.clear();
while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r')
word += *p++;
if (word.empty())
continue;
std::string test = current.empty() ? word : (current + " " + word);
uint16_t w = display->getStringWidth(test.c_str(), test.length(), true);
if (w <= availableWidth)
current = test;
else {
if (!current.empty()) {
wrapped.push_back(current);
current = word;
if (wrapped.size() >= maxContentLines)
break;
} else {
current = word;
while (current.size() > 1 &&
display->getStringWidth(current.c_str(), current.length(), true) > availableWidth)
current.pop_back();
}
}
}
if (!current.empty() && wrapped.size() < maxContentLines)
wrapped.push_back(current);
return wrapped;
};
std::vector<std::string> allLines;
if (hasTitle)
allLines.emplace_back(popupTitle);
char buf[sizeof(popupMessage)];
strncpy(buf, popupMessage, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
char *paragraph = strtok(buf, "\n");
while (paragraph && allLines.size() < maxContentLines + (hasTitle ? 1 : 0)) {
auto wrapped = wrapText(paragraph, maxWrapWidth);
for (const auto &ln : wrapped) {
if (allLines.size() >= maxContentLines + (hasTitle ? 1 : 0))
break;
allLines.push_back(ln);
}
paragraph = strtok(nullptr, "\n");
}
std::vector<const char *> ptrs;
for (const auto &ln : allLines)
ptrs.push_back(ln.c_str());
ptrs.push_back(nullptr);
// Use the standard notification box drawing from NotificationRenderer
NotificationRenderer::drawNotificationBox(display, nullptr, ptrs.data(), allLines.size(), 0, 0);
}
} // namespace graphics
#endif // HAS_SCREEN

View File

@ -1,55 +0,0 @@
#pragma once
#include "configuration.h"
#if HAS_SCREEN
#include "graphics/Screen.h" // InputEvent
#include "graphics/VirtualKeyboard.h"
#include <OLEDDisplay.h>
#include <functional>
#include <string>
namespace graphics
{
class OnScreenKeyboardModule
{
public:
static OnScreenKeyboardModule &instance();
void start(const char *header, const char *initialText, uint32_t durationMs,
std::function<void(const std::string &)> callback);
void stop(bool callEmptyCallback);
void handleInput(const InputEvent &event);
static bool processVirtualKeyboardInput(const InputEvent &event, VirtualKeyboard *keyboard);
bool draw(OLEDDisplay *display);
void showPopup(const char *title, const char *content, uint32_t durationMs);
void clearPopup();
// Draw only the popup overlay (used when legacy virtualKeyboard draws the keyboard)
void drawPopupOverlay(OLEDDisplay *display);
private:
OnScreenKeyboardModule() = default;
~OnScreenKeyboardModule();
OnScreenKeyboardModule(const OnScreenKeyboardModule &) = delete;
OnScreenKeyboardModule &operator=(const OnScreenKeyboardModule &) = delete;
void onSubmit(const std::string &text);
void onCancel();
void drawPopup(OLEDDisplay *display);
VirtualKeyboard *keyboard = nullptr;
std::function<void(const std::string &)> callback;
char popupTitle[64] = {0};
char popupMessage[256] = {0};
uint32_t popupUntil = 0;
bool popupVisible = false;
};
} // namespace graphics
#endif // HAS_SCREEN

View File

@ -64,8 +64,7 @@ SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio; SerialModuleRadio *serialModuleRadio;
#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ #if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || \ defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE)
defined(MUZI_BASE)
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
{ {
api_type = TYPE_SERIAL; api_type = TYPE_SERIAL;
@ -205,7 +204,7 @@ int32_t SerialModule::runOnce()
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
} }
#elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ #elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
#ifdef ARCH_RP2040 #ifdef ARCH_RP2040
Serial2.setFIFOSize(RX_BUFFER); Serial2.setFIFOSize(RX_BUFFER);
@ -262,7 +261,7 @@ int32_t SerialModule::runOnce()
} }
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ #if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
processWXSerial(); processWXSerial();
@ -537,8 +536,7 @@ ParsedLine parseLine(const char *line)
void SerialModule::processWXSerial() void SerialModule::processWXSerial()
{ {
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \ #if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && \ !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL)
!defined(ARCH_STM32WL) && !defined(MUZI_BASE)
static unsigned int lastAveraged = 0; static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
static double dir_sum_sin = 0; static double dir_sum_sin = 0;

View File

@ -1,94 +0,0 @@
#include "StatusLEDModule.h"
#include "MeshService.h"
#include "configuration.h"
#include <Arduino.h>
/*
StatusLEDModule manages the device's status LEDs, updating their states based on power and Bluetooth status.
It reflects charging, charged, discharging, and Bluetooth connection states using the appropriate LEDs.
*/
StatusLEDModule *statusLEDModule;
StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
{
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
powerStatusObserver.observe(&powerStatus->onNewStatus);
}
int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
{
switch (arg->getStatusType()) {
case STATUS_TYPE_POWER: {
meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg;
if (powerStatus->getHasUSB()) {
power_state = charging;
if (powerStatus->getBatteryChargePercent() >= 100) {
power_state = charged;
}
} else {
power_state = discharging;
}
break;
}
case STATUS_TYPE_BLUETOOTH: {
meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)arg;
switch (bluetoothStatus->getConnectionState()) {
case meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED: {
ble_state = unpaired;
PAIRING_LED_starttime = millis();
break;
}
case meshtastic::BluetoothStatus::ConnectionState::PAIRING: {
ble_state = pairing;
PAIRING_LED_starttime = millis();
break;
}
case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: {
ble_state = connected;
PAIRING_LED_starttime = millis();
break;
}
}
break;
}
}
return 0;
};
int32_t StatusLEDModule::runOnce()
{
if (power_state == charging) {
CHARGE_LED_state = !CHARGE_LED_state;
} else if (power_state == charged) {
CHARGE_LED_state = LED_STATE_ON;
} else {
CHARGE_LED_state = LED_STATE_OFF;
}
if (!config.bluetooth.enabled || PAIRING_LED_starttime + 30 * 1000 < millis()) {
PAIRING_LED_state = LED_STATE_OFF;
} else if (ble_state == unpaired) {
if (slowTrack) {
PAIRING_LED_state = !PAIRING_LED_state;
slowTrack = false;
} else {
slowTrack = true;
}
} else if (ble_state == pairing) {
PAIRING_LED_state = !PAIRING_LED_state;
} else {
PAIRING_LED_state = LED_STATE_ON;
}
#ifdef LED_CHARGE
digitalWrite(LED_CHARGE, CHARGE_LED_state);
#endif
// digitalWrite(green_LED_PIN, LED_STATE_OFF);
#ifdef LED_PAIRING
digitalWrite(LED_PAIRING, PAIRING_LED_state);
#endif
return (my_interval);
}

View File

@ -1,44 +0,0 @@
#pragma once
#include "BluetoothStatus.h"
#include "MeshModule.h"
#include "PowerStatus.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include <Arduino.h>
#include <functional>
class StatusLEDModule : private concurrency::OSThread
{
bool slowTrack = false;
public:
StatusLEDModule();
int handleStatusUpdate(const meshtastic::Status *);
protected:
unsigned int my_interval = 1000; // interval in millisconds
virtual int32_t runOnce() override;
CallbackObserver<StatusLEDModule, const meshtastic::Status *> bluetoothStatusObserver =
CallbackObserver<StatusLEDModule, const meshtastic::Status *>(this, &StatusLEDModule::handleStatusUpdate);
CallbackObserver<StatusLEDModule, const meshtastic::Status *> powerStatusObserver =
CallbackObserver<StatusLEDModule, const meshtastic::Status *>(this, &StatusLEDModule::handleStatusUpdate);
private:
bool CHARGE_LED_state = LED_STATE_OFF;
bool PAIRING_LED_state = LED_STATE_OFF;
uint32_t PAIRING_LED_starttime = 0;
enum PowerState { discharging, charging, charged };
PowerState power_state = discharging;
enum BLEState { unpaired, pairing, connected };
BLEState ble_state = unpaired;
};
extern StatusLEDModule *statusLEDModule;

View File

@ -1,5 +1,4 @@
#include "SystemCommandsModule.h" #include "SystemCommandsModule.h"
#include "input/InputBroker.h"
#include "meshUtils.h" #include "meshUtils.h"
#if HAS_SCREEN #if HAS_SCREEN
#include "graphics/Screen.h" #include "graphics/Screen.h"
@ -23,7 +22,7 @@ SystemCommandsModule::SystemCommandsModule()
int SystemCommandsModule::handleInputEvent(const InputEvent *event) int SystemCommandsModule::handleInputEvent(const InputEvent *event)
{ {
LOG_INPUT("SystemCommands Input event %u! kb %u", event->inputEvent, event->kbchar); LOG_INFO("Input event %u! kb %u", event->inputEvent, event->kbchar);
// System commands (all others fall through) // System commands (all others fall through)
switch (event->kbchar) { switch (event->kbchar) {
// Fn key symbols // Fn key symbols
@ -86,8 +85,10 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
switch (event->inputEvent) { switch (event->inputEvent) {
// GPS // GPS
case INPUT_BROKER_GPS_TOGGLE: case INPUT_BROKER_GPS_TOGGLE:
LOG_WARN("GPS Toggle");
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
if (gps) { if (gps) {
LOG_WARN("GPS Toggle2");
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED && if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED &&
config.position.fixed_position == false) { config.position.fixed_position == false) {
nodeDB->clearLocalPosition(); nodeDB->clearLocalPosition();

View File

@ -35,7 +35,7 @@ bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement)
// prefer other sensors like bmp280, bmp3xx // prefer other sensors like bmp280, bmp3xx
if (!measurement->variant.environment_metrics.has_temperature) { if (!measurement->variant.environment_metrics.has_temperature) {
measurement->variant.environment_metrics.has_temperature = true; measurement->variant.environment_metrics.has_temperature = true;
measurement->variant.environment_metrics.temperature = temp.temperature + AHT10_TEMP_OFFSET; measurement->variant.environment_metrics.temperature = temp.temperature;
} }
if (!measurement->variant.environment_metrics.has_relative_humidity) { if (!measurement->variant.environment_metrics.has_relative_humidity) {

View File

@ -6,10 +6,6 @@
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<Adafruit_AHTX0.h>) #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<Adafruit_AHTX0.h>)
#ifndef AHT10_TEMP_OFFSET
#define AHT10_TEMP_OFFSET 0
#endif
#include "../mesh/generated/meshtastic/telemetry.pb.h" #include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h" #include "TelemetrySensor.h"
#include <Adafruit_AHTX0.h> #include <Adafruit_AHTX0.h>

View File

@ -13,10 +13,7 @@ DFRobotGravitySensor::DFRobotGravitySensor() : TelemetrySensor(meshtastic_Teleme
DFRobotGravitySensor::~DFRobotGravitySensor() DFRobotGravitySensor::~DFRobotGravitySensor()
{ {
if (gravity) { if (gravity) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
delete gravity; delete gravity;
#pragma GCC diagnostic pop
gravity = nullptr; gravity = nullptr;
} }
} }

View File

@ -11,113 +11,6 @@ extern graphics::Screen *screen;
TraceRouteModule *traceRouteModule; TraceRouteModule *traceRouteModule;
void TraceRouteModule::setResultText(const String &text)
{
resultText = text;
resultLines.clear();
resultLinesDirty = true;
}
void TraceRouteModule::clearResultLines()
{
resultLines.clear();
resultLinesDirty = false;
}
#if HAS_SCREEN
void TraceRouteModule::rebuildResultLines(OLEDDisplay *display)
{
if (!display) {
resultLinesDirty = false;
return;
}
resultLines.clear();
if (resultText.length() == 0) {
resultLinesDirty = false;
return;
}
int maxWidth = display->getWidth() - 4;
if (maxWidth <= 0) {
resultLinesDirty = false;
return;
}
int start = 0;
int textLength = resultText.length();
while (start <= textLength) {
int newlinePos = resultText.indexOf('\n', start);
String segment;
if (newlinePos != -1) {
segment = resultText.substring(start, newlinePos);
start = newlinePos + 1;
} else {
segment = resultText.substring(start);
start = textLength + 1;
}
if (segment.length() == 0) {
resultLines.push_back("");
continue;
}
if (display->getStringWidth(segment) <= maxWidth) {
resultLines.push_back(segment);
continue;
}
String remaining = segment;
while (remaining.length() > 0) {
String tempLine = "";
int lastGoodBreak = -1;
bool lineComplete = false;
for (int i = 0; i < static_cast<int>(remaining.length()); i++) {
char ch = remaining.charAt(i);
String testLine = tempLine + ch;
if (display->getStringWidth(testLine) > maxWidth) {
if (lastGoodBreak >= 0) {
resultLines.push_back(remaining.substring(0, lastGoodBreak + 1));
remaining = remaining.substring(lastGoodBreak + 1);
lineComplete = true;
break;
} else if (tempLine.length() > 0) {
resultLines.push_back(tempLine);
remaining = remaining.substring(i);
lineComplete = true;
break;
} else {
resultLines.push_back(String(ch));
remaining = remaining.substring(i + 1);
lineComplete = true;
break;
}
} else {
tempLine = testLine;
if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')' || ch == ',') {
lastGoodBreak = i;
}
}
}
if (!lineComplete) {
if (tempLine.length() > 0) {
resultLines.push_back(tempLine);
}
break;
}
}
}
resultLinesDirty = false;
}
#endif
bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r) bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r)
{ {
// We only alter the packet in alterReceivedProtobuf() // We only alter the packet in alterReceivedProtobuf()
@ -513,7 +406,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
if (node == 0 || node == NODENUM_BROADCAST) { if (node == 0 || node == NODENUM_BROADCAST) {
LOG_ERROR("Invalid node number for trace route: 0x%08x", node); LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
setResultText("Invalid node"); resultText = "Invalid node";
resultShowTime = millis(); resultShowTime = millis();
tracingNode = 0; tracingNode = 0;
@ -527,7 +420,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
if (node == nodeDB->getNodeNum()) { if (node == nodeDB->getNodeNum()) {
LOG_ERROR("Cannot trace route to self: 0x%08x", node); LOG_ERROR("Cannot trace route to self: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
setResultText("Cannot trace self"); resultText = "Cannot trace self";
resultShowTime = millis(); resultShowTime = millis();
tracingNode = 0; tracingNode = 0;
@ -554,8 +447,6 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000; unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
bannerText = String("Wait for ") + String(wait) + String("s"); bannerText = String("Wait for ") + String(wait) + String("s");
runState = TRACEROUTE_STATE_COOLDOWN; runState = TRACEROUTE_STATE_COOLDOWN;
resultText = "";
clearResultLines();
requestFocus(); requestFocus();
UIFrameEvent e; UIFrameEvent e;
@ -568,8 +459,6 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
tracingNode = node; tracingNode = node;
lastTraceRouteTime = now; lastTraceRouteTime = now;
runState = TRACEROUTE_STATE_TRACKING; runState = TRACEROUTE_STATE_TRACKING;
resultText = "";
clearResultLines();
bannerText = String("Tracing ") + getNodeName(node); bannerText = String("Tracing ") + getNodeName(node);
LOG_INFO("TraceRoute UI: Starting trace route to node 0x%08x, requesting focus", node); LOG_INFO("TraceRoute UI: Starting trace route to node 0x%08x, requesting focus", node);
@ -612,7 +501,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
} else { } else {
LOG_ERROR("MeshService is NULL!"); LOG_ERROR("MeshService is NULL!");
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
setResultText("Service unavailable"); resultText = "Service unavailable";
resultShowTime = millis(); resultShowTime = millis();
tracingNode = 0; tracingNode = 0;
@ -625,7 +514,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
} else { } else {
LOG_ERROR("Failed to allocate TraceRoute packet from router"); LOG_ERROR("Failed to allocate TraceRoute packet from router");
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
setResultText("Failed to send"); resultText = "Failed to send";
resultShowTime = millis(); resultShowTime = millis();
tracingNode = 0; tracingNode = 0;
@ -643,7 +532,7 @@ void TraceRouteModule::launch(NodeNum node)
if (node == 0 || node == NODENUM_BROADCAST) { if (node == 0 || node == NODENUM_BROADCAST) {
LOG_ERROR("Invalid node number for trace route: 0x%08x", node); LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
setResultText("Invalid node"); resultText = "Invalid node";
resultShowTime = millis(); resultShowTime = millis();
tracingNode = 0; tracingNode = 0;
@ -657,7 +546,7 @@ void TraceRouteModule::launch(NodeNum node)
if (node == nodeDB->getNodeNum()) { if (node == nodeDB->getNodeNum()) {
LOG_ERROR("Cannot trace route to self: 0x%08x", node); LOG_ERROR("Cannot trace route to self: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
setResultText("Cannot trace self"); resultText = "Cannot trace self";
resultShowTime = millis(); resultShowTime = millis();
tracingNode = 0; tracingNode = 0;
@ -679,8 +568,6 @@ void TraceRouteModule::launch(NodeNum node)
unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000; unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
bannerText = String("Wait for ") + String(wait) + String("s"); bannerText = String("Wait for ") + String(wait) + String("s");
runState = TRACEROUTE_STATE_COOLDOWN; runState = TRACEROUTE_STATE_COOLDOWN;
resultText = "";
clearResultLines();
requestFocus(); requestFocus();
UIFrameEvent e; UIFrameEvent e;
@ -693,8 +580,6 @@ void TraceRouteModule::launch(NodeNum node)
runState = TRACEROUTE_STATE_TRACKING; runState = TRACEROUTE_STATE_TRACKING;
tracingNode = node; tracingNode = node;
lastTraceRouteTime = now; lastTraceRouteTime = now;
resultText = "";
clearResultLines();
bannerText = String("Tracing ") + getNodeName(node); bannerText = String("Tracing ") + getNodeName(node);
requestFocus(); requestFocus();
@ -729,14 +614,14 @@ void TraceRouteModule::launch(NodeNum node)
} else { } else {
LOG_ERROR("MeshService is NULL!"); LOG_ERROR("MeshService is NULL!");
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
setResultText("Service unavailable"); resultText = "Service unavailable";
resultShowTime = millis(); resultShowTime = millis();
tracingNode = 0; tracingNode = 0;
} }
} else { } else {
LOG_ERROR("Failed to allocate TraceRoute packet from router"); LOG_ERROR("Failed to allocate TraceRoute packet from router");
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
setResultText("Failed to send"); resultText = "Failed to send";
resultShowTime = millis(); resultShowTime = millis();
tracingNode = 0; tracingNode = 0;
} }
@ -744,7 +629,7 @@ void TraceRouteModule::launch(NodeNum node)
void TraceRouteModule::handleTraceRouteResult(const String &result) void TraceRouteModule::handleTraceRouteResult(const String &result)
{ {
setResultText(result); resultText = result;
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
resultShowTime = millis(); resultShowTime = millis();
tracingNode = 0; tracingNode = 0;
@ -794,15 +679,83 @@ void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
if (resultText.length() > 0) { if (resultText.length() > 0) {
if (resultLinesDirty) { std::vector<String> lines;
rebuildResultLines(display); String currentLine = "";
int maxWidth = display->getWidth() - 4;
int start = 0;
int newlinePos = resultText.indexOf('\n', start);
while (newlinePos != -1 || start < static_cast<int>(resultText.length())) {
String segment;
if (newlinePos != -1) {
segment = resultText.substring(start, newlinePos);
start = newlinePos + 1;
newlinePos = resultText.indexOf('\n', start);
} else {
segment = resultText.substring(start);
start = resultText.length();
}
if (display->getStringWidth(segment) <= maxWidth) {
lines.push_back(segment);
} else {
// Try to break at better positions (space, >, <, -)
String remaining = segment;
while (remaining.length() > 0) {
String tempLine = "";
int lastGoodBreak = -1;
bool lineComplete = false;
for (int i = 0; i < static_cast<int>(remaining.length()); i++) {
char ch = remaining.charAt(i);
String testLine = tempLine + ch;
if (display->getStringWidth(testLine) > maxWidth) {
if (lastGoodBreak >= 0) {
// Break at the last good position
lines.push_back(remaining.substring(0, lastGoodBreak + 1));
remaining = remaining.substring(lastGoodBreak + 1);
lineComplete = true;
break;
} else if (tempLine.length() > 0) {
lines.push_back(tempLine);
remaining = remaining.substring(i);
lineComplete = true;
break;
} else {
// Single character exceeds width
lines.push_back(String(ch));
remaining = remaining.substring(i + 1);
lineComplete = true;
break;
}
} else {
tempLine = testLine;
// Mark good break positions
if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')') {
lastGoodBreak = i;
}
}
}
if (!lineComplete) {
// Reached end of remaining text
if (tempLine.length() > 0) {
lines.push_back(tempLine);
}
break;
}
}
}
} }
int lineHeight = FONT_HEIGHT_SMALL + 1; // Use proper font height with 1px spacing int lineHeight = FONT_HEIGHT_SMALL + 1; // Use proper font height with 1px spacing
for (size_t i = 0; i < resultLines.size(); i++) { for (size_t i = 0; i < lines.size(); i++) {
int lineY = contentStartY + (i * lineHeight); int lineY = contentStartY + (i * lineHeight);
if (lineY + FONT_HEIGHT_SMALL <= display->getHeight()) { if (lineY + FONT_HEIGHT_SMALL <= display->getHeight()) {
display->drawString(x + 2, lineY, resultLines[i]); display->drawString(x + 2, lineY, lines[i]);
} }
} }
} }
@ -826,7 +779,7 @@ int32_t TraceRouteModule::runOnce()
if (runState == TRACEROUTE_STATE_TRACKING && now - lastTraceRouteTime > trackingTimeoutMs) { if (runState == TRACEROUTE_STATE_TRACKING && now - lastTraceRouteTime > trackingTimeoutMs) {
LOG_INFO("TraceRoute timeout, no response received"); LOG_INFO("TraceRoute timeout, no response received");
runState = TRACEROUTE_STATE_RESULT; runState = TRACEROUTE_STATE_RESULT;
setResultText("No response received"); resultText = "No response received";
resultShowTime = now; resultShowTime = now;
tracingNode = 0; tracingNode = 0;
@ -862,8 +815,6 @@ int32_t TraceRouteModule::runOnce()
// Cooldown finished // Cooldown finished
LOG_INFO("TraceRoute cooldown finished, returning to IDLE"); LOG_INFO("TraceRoute cooldown finished, returning to IDLE");
runState = TRACEROUTE_STATE_IDLE; runState = TRACEROUTE_STATE_IDLE;
resultText = "";
clearResultLines();
bannerText = ""; bannerText = "";
UIFrameEvent e; UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
@ -877,7 +828,6 @@ int32_t TraceRouteModule::runOnce()
LOG_INFO("TraceRoute result display timeout, returning to IDLE"); LOG_INFO("TraceRoute result display timeout, returning to IDLE");
runState = TRACEROUTE_STATE_IDLE; runState = TRACEROUTE_STATE_IDLE;
resultText = ""; resultText = "";
clearResultLines();
bannerText = ""; bannerText = "";
tracingNode = 0; tracingNode = 0;
UIFrameEvent e; UIFrameEvent e;

View File

@ -7,7 +7,6 @@
#if HAS_SCREEN #if HAS_SCREEN
#include "OLEDDisplayUi.h" #include "OLEDDisplayUi.h"
#endif #endif
#include <vector>
#define ROUTE_SIZE sizeof(((meshtastic_RouteDiscovery *)0)->route) / sizeof(((meshtastic_RouteDiscovery *)0)->route[0]) #define ROUTE_SIZE sizeof(((meshtastic_RouteDiscovery *)0)->route) / sizeof(((meshtastic_RouteDiscovery *)0)->route[0])
@ -50,11 +49,6 @@ class TraceRouteModule : public ProtobufModule<meshtastic_RouteDiscovery>,
virtual int32_t runOnce() override; virtual int32_t runOnce() override;
private: private:
void setResultText(const String &text);
void clearResultLines();
#if HAS_SCREEN
void rebuildResultLines(OLEDDisplay *display);
#endif
// Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit // Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit
void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination); void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination);
@ -80,8 +74,6 @@ class TraceRouteModule : public ProtobufModule<meshtastic_RouteDiscovery>,
unsigned long trackingTimeoutMs = 10000; unsigned long trackingTimeoutMs = 10000;
String bannerText; String bannerText;
String resultText; String resultText;
std::vector<String> resultLines;
bool resultLinesDirty = false;
NodeNum tracingNode = 0; NodeNum tracingNode = 0;
bool initialized = false; bool initialized = false;
}; };

View File

@ -115,13 +115,7 @@ int32_t BMX160Sensor::runOnce()
void BMX160Sensor::calibrate(uint16_t forSeconds) void BMX160Sensor::calibrate(uint16_t forSeconds)
{ {
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) #if !defined(MESHTASTIC_EXCLUDE_SCREEN)
sBmx160SensorData_t magAccel;
sBmx160SensorData_t gAccel;
LOG_DEBUG("BMX160 calibration started for %is", forSeconds); LOG_DEBUG("BMX160 calibration started for %is", forSeconds);
sensor.getAllData(&magAccel, NULL, &gAccel);
highestX = magAccel.x, lowestX = magAccel.x;
highestY = magAccel.y, lowestY = magAccel.y;
highestZ = magAccel.z, lowestZ = magAccel.z;
doCalibration = true; doCalibration = true;
uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided
@ -133,4 +127,4 @@ void BMX160Sensor::calibrate(uint16_t forSeconds)
#endif #endif
#endif #endif

View File

@ -47,21 +47,6 @@ int32_t ICM20948Sensor::runOnce()
int32_t ICM20948Sensor::runOnce() int32_t ICM20948Sensor::runOnce()
{ {
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN #if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
#if defined(MUZI_BASE) // temporarily gated to single device due to feature freeze
if (screen && !screen->isScreenOn() && !config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) {
if (!isAsleep) {
LOG_DEBUG("sleeping IMU");
sensor->sleep(true);
isAsleep = true;
}
return MOTION_SENSOR_CHECK_INTERVAL_MS;
}
if (isAsleep) {
sensor->sleep(false);
isAsleep = false;
}
#endif
float magX = 0, magY = 0, magZ = 0; float magX = 0, magY = 0, magZ = 0;
if (sensor->dataReady()) { if (sensor->dataReady()) {
sensor->getAGMT(); sensor->getAGMT();
@ -171,20 +156,7 @@ int32_t ICM20948Sensor::runOnce()
void ICM20948Sensor::calibrate(uint16_t forSeconds) void ICM20948Sensor::calibrate(uint16_t forSeconds)
{ {
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN #if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
LOG_DEBUG("Old calibration data: highestX = %f, lowestX = %f, highestY = %f, lowestY = %f, highestZ = %f, lowestZ = %f",
highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
LOG_DEBUG("BMX160 calibration started for %is", forSeconds); LOG_DEBUG("BMX160 calibration started for %is", forSeconds);
if (sensor->dataReady()) {
sensor->getAGMT();
highestX = sensor->agmt.mag.axes.x;
lowestX = sensor->agmt.mag.axes.x;
highestY = sensor->agmt.mag.axes.y;
lowestY = sensor->agmt.mag.axes.y;
highestZ = sensor->agmt.mag.axes.z;
lowestZ = sensor->agmt.mag.axes.z;
} else {
highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
}
doCalibration = true; doCalibration = true;
uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided
@ -323,4 +295,4 @@ bool ICM20948Singleton::setWakeOnMotion()
return true; return true;
} }
#endif #endif

View File

@ -82,13 +82,7 @@ class ICM20948Sensor : public MotionSensor
private: private:
ICM20948Singleton *sensor = nullptr; ICM20948Singleton *sensor = nullptr;
bool showingScreen = false; bool showingScreen = false;
#ifdef MUZI_BASE
bool isAsleep = false;
float highestX = 449.000000, lowestX = -140.000000, highestY = 422.000000, lowestY = -232.000000, highestZ = 749.000000,
lowestZ = 98.000000;
#else
float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
#endif
public: public:
explicit ICM20948Sensor(ScanI2C::FoundDevice foundDevice); explicit ICM20948Sensor(ScanI2C::FoundDevice foundDevice);

View File

@ -69,8 +69,7 @@ void MotionSensor::wakeScreen()
{ {
if (powerFSM.getState() == &stateDARK) { if (powerFSM.getState() == &stateDARK) {
LOG_DEBUG("Motion wakeScreen detected"); LOG_DEBUG("Motion wakeScreen detected");
if (config.display.wake_on_tap_or_motion) powerFSM.trigger(EVENT_INPUT);
powerFSM.trigger(EVENT_INPUT);
} }
} }
@ -88,4 +87,4 @@ void MotionSensor::buttonPress() {}
#endif #endif
#endif #endif

View File

@ -692,10 +692,7 @@ void MQTT::publishNodeInfo()
} }
void MQTT::publishQueuedMessages() void MQTT::publishQueuedMessages()
{ {
if (mqttQueue.isEmpty()) if (mqttQueue.isEmpty() || !isConnected)
return;
if (!moduleConfig.mqtt.proxy_to_client_enabled && !isConnected)
return; return;
LOG_DEBUG("Publish enqueued MQTT message"); LOG_DEBUG("Publish enqueued MQTT message");

View File

@ -20,14 +20,13 @@
#include "PowerStatus.h" #include "PowerStatus.h"
#endif #endif
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
#if defined(CONFIG_NIMBLE_CPP_IDF) #if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h" #include "host/ble_gap.h"
#else #else
#include "nimble/nimble/host/include/host/ble_gap.h" #include "nimble/nimble/host/include/host/ble_gap.h"
#endif #endif
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
namespace namespace
{ {
constexpr uint16_t kPreferredBleMtu = 517; constexpr uint16_t kPreferredBleMtu = 517;
@ -777,35 +776,16 @@ bool NimbleBluetooth::isConnected()
int NimbleBluetooth::getRssi() int NimbleBluetooth::getRssi()
{ {
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) if (bleServer && isConnected()) {
if (!bleServer || !isConnected()) { auto service = bleServer->getServiceByUUID(MESH_SERVICE_UUID);
return 0; // No active BLE connection uint16_t handle = service->getHandle();
} #ifdef NIMBLE_TWO
return NimBLEDevice::getClientByHandle(handle)->getRssi();
uint16_t connHandle = nimbleBluetoothConnHandle.load(); #else
return NimBLEDevice::getClientByID(handle)->getRssi();
if (connHandle == BLE_HS_CONN_HANDLE_NONE) {
const auto peers = bleServer->getPeerDevices();
if (!peers.empty()) {
connHandle = peers.front();
nimbleBluetoothConnHandle = connHandle;
}
}
if (connHandle == BLE_HS_CONN_HANDLE_NONE) {
return 0; // Connection handle not available yet
}
int8_t rssi = 0;
const int rc = ble_gap_conn_rssi(connHandle, &rssi);
if (rc == 0) {
return rssi;
}
LOG_DEBUG("BLE RSSI read failed, rc=%d", rc);
#endif #endif
}
return 0; return 0; // FIXME figure out where to source this
} }
void NimbleBluetooth::setup() void NimbleBluetooth::setup()

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