Compare commits

..

3 Commits

Author SHA1 Message Date
Ben Meadors
9d222c9a55 Merge branch 'master' into 128-redux 2025-08-20 10:10:19 -05:00
Ben Meadors
2dd4ca8c52 Line 2025-08-20 06:01:21 -05:00
Ben Meadors
2a50ae05dd Account for 128x128 SH1107 displays 2025-08-20 06:00:24 -05:00
167 changed files with 642 additions and 3427 deletions

View File

@@ -11,6 +11,11 @@ runs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Uncomment build epoch
shell: bash
run: |
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
- name: Install dependencies
shell: bash
run: |
@@ -18,7 +23,7 @@ runs:
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: 3.x
cache: pip

View File

@@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
- uses: actions/setup-python@v5
with:
python-version: 3.x
cache: pip
@@ -370,7 +370,7 @@ jobs:
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: 3.x
@@ -439,7 +439,7 @@ jobs:
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: 3.x
@@ -494,7 +494,7 @@ jobs:
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: 3.x

View File

@@ -31,7 +31,7 @@ jobs:
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: 3.x

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Check for PR labels
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const labels = context.payload.pull_request.labels.map(label => label.name);

View File

@@ -177,7 +177,7 @@ jobs:
- name: Comment test results on PR
if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped'
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');

View File

@@ -63,7 +63,7 @@ jobs:
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: 3.x

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Stale PR+Issues
uses: actions/stale@v10.0.0
uses: actions/stale@v9.1.0
with:
days-before-stale: 45
exempt-issue-labels: pinned,3.0

View File

@@ -47,7 +47,7 @@ jobs:
pio upgrade
- name: Setup Node
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22

View File

@@ -39,7 +39,7 @@ jobs:
git push
- name: Comment on PR
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |

View File

@@ -4,28 +4,28 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.7.2
ref: v1.7.1
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.469
- renovate@41.94.0
- checkov@3.2.461
- renovate@41.74.0
- prettier@3.6.2
- trufflehog@3.90.5
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.66.0
- taplo@0.10.0
- ruff@0.12.11
- trivy@0.64.1
- taplo@0.9.3
- ruff@0.12.7
- isort@6.0.1
- markdownlint@0.45.0
- oxipng@9.1.5
- svgo@4.0.0
- actionlint@1.7.7
- flake8@7.3.0
- hadolint@2.13.1
- hadolint@2.12.1-beta
- shfmt@3.6.0
- shellcheck@0.11.0
- shellcheck@0.10.0
- black@25.1.0
- git-diff-check
- gitleaks@8.28.0

View File

@@ -2,7 +2,7 @@
[portduino_base]
platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
https://github.com/meshtastic/platform-native/archive/c490bcd019e0658404088a61b96e653c9da22c45.zip
https://github.com/meshtastic/platform-native/archive/37d986499ce24511952d7146db72d667c6bdaff7.zip
framework = arduino
build_src_filter =
@@ -31,8 +31,6 @@ lib_deps =
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
adafruit/Adafruit seesaw Library@1.7.9
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main
https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip
build_flags =
${arduino_base.build_flags}

View File

@@ -50,7 +50,7 @@ lib_deps =
${radiolib_base.lib_deps}
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
https://github.com/caveman99/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
lib_ignore =
OneButton

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env bash
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
export PIP_BREAK_SYSTEM_PACKAGES=1
if (echo $2 | grep -q "esp32"); then

View File

@@ -1,8 +0,0 @@
Lora:
### RAK13300in Slot 2 pins
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
# Ant_sw: 23 # IO3
spidev: spidev0.1
# CS: 7

View File

@@ -9,4 +9,13 @@ Lora:
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: 8
# CS: 8
### RAK13300in Slot 2 pins
# IRQ: 18 #IO6
# Reset: 24 # IO4
# Busy: 19 # IO5
# # Ant_sw: 23 # IO3
# spidev: spidev0.1
# # CS: 7

View File

@@ -7,7 +7,6 @@ SET "DEBUG=0"
SET "PYTHON="
SET "TFT_BUILD=0"
SET "BIGDB8=0"
SET "MUIDB8=0"
SET "BIGDB16=0"
SET "ESPTOOL_BAUD=115200"
SET "ESPTOOL_CMD="
@@ -15,12 +14,11 @@ SET "LOGCOUNTER=0"
SET "BPS_RESET=0"
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv"
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone"
SET "C3=esp32c3"
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv"
SET "BIGDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite t-watch-s3"
GOTO getopts
:help
@@ -102,6 +100,7 @@ IF NOT "!FILENAME:update=!"=="!FILENAME!" (
)
:skip-filename
SET "ESPTOOL_BAUD=1200"
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
IF NOT "__%PYTHON%__"=="____" (
@@ -121,10 +120,11 @@ IF NOT "__%PYTHON%__"=="____" (
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
!ESPTOOL_CMD! >nul 2>&1
IF %ERRORLEVEL% EQU 9009 (
@REM 9009 = command not found on Windows
IF %ERRORLEVEL% GEQ 2 (
@REM esptool exits with code 1 if help is displayed.
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
EXIT /B 1
GOTO eof
)
IF %DEBUG% EQU 1 (
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
@@ -142,7 +142,7 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
IF %BPS_RESET% EQU 1 (
@REM Attempt to change mode via 1200bps Reset.
CALL :RUN_ESPTOOL 1200 --after no_reset read_flash_status
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
GOTO eof
)
@@ -164,15 +164,6 @@ FOR %%a IN (%BIGDB_8MB%) DO (
)
:end_loop_bigdb_8mb
FOR %%a IN (%MUIDB_8MB%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %MUIDB_8MB%.
SET "MUIDB8=1"
GOTO end_loop_muidb_8mb
)
)
:end_loop_muidb_8mb
FOR %%a IN (%BIGDB_16MB%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %BIGDB_16MB%.
@@ -183,7 +174,6 @@ FOR %%a IN (%BIGDB_16MB%) DO (
:end_loop_bigdb_16mb
IF %BIGDB8% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 8mb partition selected."
IF %MUIDB8% EQU 1 CALL :LOG_MESSAGE INFO "MUIDB 8mb partition selected."
IF %BIGDB16% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 16mb partition selected."
@REM Extract BASENAME from %FILENAME% for later use.
@@ -228,12 +218,6 @@ IF %BIGDB8% EQU 1 (
SET "SPIFFS_OFFSET=0x670000"
)
@REM Offsets for MUIDB 8mb.
IF %MUIDB8% EQU 1 (
SET "OTA_OFFSET=0x5D0000"
SET "SPIFFS_OFFSET=0x670000"
)
@REM Offsets for BigDB 16mb.
IF %BIGDB16% EQU 1 (
SET "OTA_OFFSET=0x650000"

View File

@@ -5,43 +5,38 @@ BPS_RESET=false
TFT_BUILD=false
MCU=""
# Constants
RESET_BAUD=1200
FIRMWARE_OFFSET=0x00
# Variant groups
BIGDB_8MB=(
"crowpanel-esp32s3"
"heltec_capsule_sensor_v3"
"heltec-v3"
"heltec-vision-master-e213"
"heltec-vision-master-e290"
"heltec-vision-master-t190"
"heltec-wireless-paper"
"heltec-wireless-tracker"
"heltec-wsl-v3"
"icarus"
"seeed-xiao-s3"
"tbeam-s3-core"
"tracksenger"
)
MUIDB_8MB=(
"picomputer-s3"
"unphone"
"seeed-sensecap-indicator"
"picomputer-s3"
"unphone"
"seeed-sensecap-indicator"
"crowpanel-esp32s3"
"heltec_capsule_sensor_v3"
"heltec-v3"
"heltec-vision-master-e213"
"heltec-vision-master-e290"
"heltec-vision-master-t190"
"heltec-wireless-paper"
"heltec-wireless-tracker"
"heltec-wsl-v3"
"icarus"
"seeed-xiao-s3"
"tbeam-s3-core"
"tracksenger"
)
BIGDB_16MB=(
"t-deck"
"mesh-tab"
"t-energy-s3"
"dreamcatcher"
"ESP32-S3-Pico"
"m5stack-cores3"
"station-g2"
"t-deck"
"mesh-tab"
"t-energy-s3"
"dreamcatcher"
"ESP32-S3-Pico"
"m5stack-cores3"
"station-g2"
"t-eth-elite"
"tlora-pager"
"t-watch-s3"
"elecrow-adv"
"elecrow-adv-35-tft"
"elecrow-adv-24-28-tft"
"elecrow-adv1-43-50-70-tft"
)
S3_VARIANTS=(
"s3"
@@ -52,7 +47,6 @@ S3_VARIANTS=(
"station-g2"
"unphone"
"t-eth-elite"
"tlora-pager"
"mesh-tab"
"dreamcatcher"
"ESP32-S3-Pico"
@@ -112,8 +106,8 @@ while [ $# -gt 0 ]; do
shift
;;
--1200bps-reset)
BPS_RESET=true
;;
BPS_RESET=true
;;
--) # Stop parsing options
shift
break
@@ -127,7 +121,7 @@ while [ $# -gt 0 ]; do
done
if [[ $BPS_RESET == true ]]; then
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
exit 0
fi
@@ -164,13 +158,6 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
fi
done
for variant in "${MUIDB_8MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
OFFSET=0x670000
OTA_OFFSET=0x5D0000
fi
done
# littlefs* offset for BigDB 16mb and OTA OFFSET.
for variant in "${BIGDB_16MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
@@ -214,8 +201,8 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
fi
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
$ESPTOOL_CMD erase-flash
$ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
$ESPTOOL_CMD erase_flash
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"

View File

@@ -6,8 +6,6 @@ SET "SCRIPT_NAME=%~nx0"
SET "DEBUG=0"
SET "PYTHON="
SET "ESPTOOL_BAUD=115200"
SET "RESET_BAUD=1200"
SET "UPDATE_OFFSET=0x10000"
SET "ESPTOOL_CMD="
SET "LOGCOUNTER=0"
SET "CHANGE_MODE=0"
@@ -87,13 +85,14 @@ IF "!FILENAME:update=!"=="!FILENAME!" (
)
:skip-filename
SET "ESPTOOL_BAUD=1200"
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
IF NOT "__%PYTHON%__"=="____" (
SET "ESPTOOL_CMD=""!PYTHON!"" -m esptool"
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
) ELSE (
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool..."
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool...
WHERE esptool >nul 2>&1
IF %ERRORLEVEL% EQU 0 (
@REM WHERE exits with code 0 if esptool is found.
@@ -106,11 +105,11 @@ IF NOT "__%PYTHON%__"=="____" (
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
!ESPTOOL_CMD! >nul 2>&1
CALL :LOG_MESSAGE DEBUG "esptool exit code: %ERRORLEVEL%"
IF %ERRORLEVEL% EQU 9009 (
@REM 9009 = command not found on Windows
IF %ERRORLEVEL% GEQ 2 (
@REM esptool exits with code 1 if help is displayed.
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
EXIT /B 1
GOTO eof
)
IF %DEBUG% EQU 1 (
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
@@ -128,13 +127,13 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
IF %CHANGE_MODE% EQU 1 (
@REM Attempt to change mode via 1200bps Reset.
CALL :RUN_ESPTOOL !RESET_BAUD! --after no_reset read_flash_status
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
GOTO eof
)
@REM Flashing operations.
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET !UPDATE_OFFSET!..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !UPDATE_OFFSET! "!FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET 0x10000..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x10000 "!FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Script complete!."
@@ -146,9 +145,9 @@ EXIT /B %ERRORLEVEL%
:RUN_ESPTOOL
@REM Subroutine used to run ESPTOOL_CMD with arguments.
@REM Also handles %ERRORLEVEL%.
@REM CALL :RUN_ESPTOOL [Baud] [erase-flash|write-flash] [OFFSET] [Filename]
@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename]
@REM.
@REM Example:: CALL :RUN_ESPTOOL 115200 write-flash 0x10000 "firmwarefile.bin"
@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin"
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
CALL :RESET_ERROR
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4

View File

@@ -3,11 +3,6 @@
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
CHANGE_MODE=false
# Constants
FLASH_BAUD=115200
RESET_BAUD=1200
UPDATE_OFFSET=0x10000
# Determine the correct esptool command to use
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
ESPTOOL_CMD="$PYTHON -m esptool"
@@ -69,7 +64,7 @@ done
shift "$((OPTIND-1))"
if [ "$CHANGE_MODE" = true ]; then
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
exit 0
fi
@@ -80,7 +75,7 @@ fi
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
echo "Trying to flash update ${FILENAME}"
$ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
$ESPTOOL_CMD --baud 115200 write_flash 0x10000 "${FILENAME}"
else
show_help
echo "Invalid file: ${FILENAME}"

View File

@@ -87,15 +87,6 @@
</screenshots>
<releases>
<release version="2.7.9" date="2025-09-03">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9</url>
</release>
<release version="2.7.8" date="2025-08-30">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.8</url>
</release>
<release version="2.7.7" date="2025-08-28">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.7</url>
</release>
<release version="2.7.6" date="2025-08-12">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6</url>
</release>

View File

@@ -6,8 +6,6 @@ from os.path import join
import subprocess
import json
import re
import time
from datetime import datetime
from readprops import readProps
@@ -127,16 +125,11 @@ for pref in userPrefs:
pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "")
# General options that are passed to the C and C++ compilers
# Calculate unix epoch for current day (midnight)
current_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
build_epoch = int(current_date.timestamp())
flags = [
"-DAPP_VERSION=" + verObj["long"],
"-DAPP_VERSION_SHORT=" + verObj["short"],
"-DAPP_ENV=" + env.get("PIOENV"),
"-DAPP_REPO=" + repo_owner,
"-DBUILD_EPOCH=" + str(build_epoch),
] + pref_flags
print ("Using flags:")

View File

@@ -1,54 +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"],
["0x239A", "0x0071"]
],
"usb_product": "HT-n5262",
"mcu": "nrf52840",
"variant": "heltec_mesh_solar",
"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": "Heltec nrf (Adafruit BSP)",
"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://heltec.org/project/meshsolar/",
"vendor": "Heltec"
}

View File

@@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "partition-table-8MB.csv",
"partitions": "default_8MB.csv",
"memory_type": "qio_opi"
},
"core": "esp32",

View File

@@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "partition-table-8MB.csv"
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [

View File

@@ -5,7 +5,7 @@
},
"core": "stm32",
"cpu": "cortex-m4",
"extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_RAK3172_MODULE",
"extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_GENERIC_WLE5CCUX",
"f_cpu": "48000000L",
"mcu": "stm32wle5ccu",
"variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U",

11
debian/changelog vendored
View File

@@ -1,4 +1,4 @@
meshtasticd (2.7.9.0) UNRELEASED; urgency=medium
meshtasticd (2.7.6.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging
@@ -39,12 +39,5 @@ meshtasticd (2.7.9.0) UNRELEASED; urgency=medium
[ ]
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Wed, 03 Sep 2025 23:39:17 +0000
-- <github-actions[bot]@users.noreply.github.com> Tue, 12 Aug 2025 23:48:48 +0000

View File

@@ -1,7 +0,0 @@
# This is a layout for 8MB of flash for MUI devices
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x5C0000,
flashApp, app, ota_1, 0x5D0000,0x0A0000,
spiffs, data, spiffs, 0x670000,0x180000
1 # This is a layout for 8MB of flash for MUI devices
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x5000,
4 otadata, data, ota, 0xe000, 0x2000,
5 app0, app, ota_0, 0x10000, 0x5C0000,
6 flashApp, app, ota_1, 0x5D0000,0x0A0000,
7 spiffs, data, spiffs, 0x670000,0x180000

View File

@@ -53,14 +53,14 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-DBUILD_EPOCH=$UNIX_TIME
#-D OLED_PL=1
monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/9573abb64dc9c94f3051348f2bf4fc5cedf03c22.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@@ -118,7 +118,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/3677476c8a823ee85056b5fb1d146a3e193f8276.zip
https://github.com/meshtastic/device-ui/archive/8f5094b248c15ea2f9acf19cedfef6d2248fc1ff.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
@@ -157,8 +157,8 @@ lib_deps =
emotibit/EmotiBit MLX90632@1.0.8
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
adafruit/Adafruit MLX90614 Library@2.1.5
# renovate: datasource=github-tags depName=INA3221 packageName=sgtwilko/INA3221
https://github.com/sgtwilko/INA3221#bb03d7e9bfcc74fc798838a54f4f99738f29fc6a
# renovate: datasource=github-tags depName=INA3221 packageName=KodinLanewave/INA3221
https://github.com/KodinLanewave/INA3221/archive/1.0.1.zip
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
mprograms/QMC5883LCompass@1.2.3
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
@@ -177,8 +177,6 @@ lib_deps =
adafruit/Adafruit PCT2075@1.0.5
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
dfrobot/DFRobot_BMM150@1.0.0
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
adafruit/Adafruit TSL2561@1.1.2
; (not included in native / portduino)
[environmental_extra]

View File

@@ -2,12 +2,6 @@
#include "configuration.h"
// Forward declarations
#if defined(DEBUG_HEAP)
class MemGet;
extern MemGet memGet;
#endif
// DEBUG LED
#ifndef LED_STATE_ON
#define LED_STATE_ON 1
@@ -29,7 +23,6 @@ extern MemGet memGet;
#define MESHTASTIC_LOG_LEVEL_ERROR "ERROR"
#define MESHTASTIC_LOG_LEVEL_CRIT "CRIT "
#define MESHTASTIC_LOG_LEVEL_TRACE "TRACE"
#define MESHTASTIC_LOG_LEVEL_HEAP "HEAP"
#include "SerialConsole.h"
@@ -69,25 +62,6 @@ extern MemGet memGet;
#endif
#endif
#if defined(DEBUG_HEAP)
#define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__)
// Macro-based heap debugging
#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap();
#define DEBUG_HEAP_AFTER(context, ptr) \
do { \
auto heapAfter = memGet.getFreeHeap(); \
if (heapBefore != heapAfter) { \
LOG_HEAP("Alloc in %s pointer 0x%x, size: %u, free: %u", context, ptr, heapBefore - heapAfter, heapAfter); \
} \
} while (0)
#else
#define LOG_HEAP(...)
#define DEBUG_HEAP_BEFORE
#define DEBUG_HEAP_AFTER(context, ptr)
#endif
/// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic
extern "C" void logLegacy(const char *level, const char *fmt, ...);

View File

@@ -1,14 +1,7 @@
#include "DisplayFormatters.h"
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset)
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName)
{
// If use_preset is false, always return "Custom"
if (!usePreset) {
return "Custom";
}
switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
return useShortName ? "ShortT" : "ShortTurbo";

View File

@@ -4,6 +4,5 @@
class DisplayFormatters
{
public:
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset);
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName);
};

View File

@@ -128,7 +128,6 @@ RAK9154Sensor rak9154Sensor;
#ifdef HAS_PPM
// note: XPOWERS_CHIP_XXX must be defined in variant.h
#include <XPowersLib.h>
XPowersPPM *PPM = NULL;
#endif
#ifdef HAS_BQ27220
@@ -682,8 +681,6 @@ bool Power::setup()
found = true;
} else if (lipoChargerInit()) {
found = true;
} else if (meshSolarInit()) {
found = true;
} else if (analogInit()) {
found = true;
}
@@ -746,11 +743,7 @@ void Power::shutdown()
#if HAS_SCREEN
if (screen) {
#ifdef T_DECK_PRO
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
#else
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
#endif
}
#endif
#if !defined(ARCH_STM32WL)
@@ -768,7 +761,7 @@ void Power::shutdown()
#ifdef PIN_LED3
ledOff(PIN_LED3);
#endif
doDeepSleep(DELAY_FOREVER, true, true);
doDeepSleep(DELAY_FOREVER, false, true);
#elif defined(ARCH_PORTDUINO)
exit(EXIT_SUCCESS);
#else
@@ -833,27 +826,18 @@ void Power::readPowerStatus()
newStatus.notifyObservers(&powerStatus2);
#ifdef DEBUG_HEAP
if (lastheap != memGet.getFreeHeap()) {
// Use stack-allocated buffer to avoid heap allocations in monitoring code
char threadlist[256] = "Threads running:";
int threadlistLen = strlen(threadlist);
std::string threadlist = "Threads running:";
int running = 0;
for (int i = 0; i < MAX_THREADS; i++) {
auto thread = concurrency::mainController.get(i);
if ((thread != nullptr) && (thread->enabled)) {
// Use snprintf to safely append to stack buffer without heap allocation
int remaining = sizeof(threadlist) - threadlistLen - 1;
if (remaining > 0) {
int written = snprintf(threadlist + threadlistLen, remaining, " %s", thread->ThreadName.c_str());
if (written > 0 && written < remaining) {
threadlistLen += written;
}
}
threadlist += vformat(" %s", thread->ThreadName.c_str());
running++;
}
}
LOG_HEAP(threadlist);
LOG_HEAP("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
LOG_DEBUG(threadlist.c_str());
LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
lastheap = memGet.getFreeHeap();
}
#ifdef DEBUG_HEAP_MQTT
@@ -865,19 +849,15 @@ void Power::readPowerStatus()
sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]);
auto newHeap = memGet.getFreeHeap();
// Use stack-allocated buffers to avoid heap allocations in monitoring code
char heapTopic[128];
snprintf(heapTopic, sizeof(heapTopic), "%s/2/heap/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
char heapString[16];
snprintf(heapString, sizeof(heapString), "%u", newHeap);
mqtt->pubSub.publish(heapTopic, heapString, false);
std::string heapTopic =
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/heap/") + std::string(mac);
std::string heapString = std::to_string(newHeap);
mqtt->pubSub.publish(heapTopic.c_str(), heapString.c_str(), false);
auto wifiRSSI = WiFi.RSSI();
char wifiTopic[128];
snprintf(wifiTopic, sizeof(wifiTopic), "%s/2/wifi/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
char wifiString[16];
snprintf(wifiString, sizeof(wifiString), "%d", wifiRSSI);
mqtt->pubSub.publish(wifiTopic, wifiString, false);
std::string wifiTopic =
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/wifi/") + std::string(mac);
std::string wifiString = std::to_string(wifiRSSI);
mqtt->pubSub.publish(wifiTopic.c_str(), wifiString.c_str(), false);
}
#endif
@@ -1338,6 +1318,7 @@ bool Power::lipoInit()
class LipoCharger : public HasBatteryLevel
{
private:
XPowersPPM *ppm = nullptr;
BQ27220 *bq = nullptr;
public:
@@ -1346,41 +1327,41 @@ class LipoCharger : public HasBatteryLevel
*/
bool runOnce()
{
if (PPM == nullptr) {
PPM = new XPowersPPM;
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
if (ppm == nullptr) {
ppm = new XPowersPPM;
bool result = ppm->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
if (result) {
LOG_INFO("PPM BQ25896 init succeeded");
// Set the minimum operating voltage. Below this voltage, the PPM will protect
// PPM->setSysPowerDownVoltage(3100);
// ppm->setSysPowerDownVoltage(3100);
// Set input current limit, default is 500mA
// PPM->setInputCurrentLimit(800);
// ppm->setInputCurrentLimit(800);
// Disable current limit pin
// PPM->disableCurrentLimitPin();
// ppm->disableCurrentLimitPin();
// Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV
PPM->setChargeTargetVoltage(4288);
ppm->setChargeTargetVoltage(4288);
// Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA
// PPM->setPrechargeCurr(64);
// ppm->setPrechargeCurr(64);
// The premise is that limit pin is disabled, or it will
// only follow the maximum charging current set by limit pin.
// Set the charging current , Range:0~5056mA ,step:64mA
PPM->setChargerConstantCurr(1024);
ppm->setChargerConstantCurr(1024);
// To obtain voltage data, the ADC must be enabled first
PPM->enableMeasure();
ppm->enableMeasure();
// Turn on charging function
// If there is no battery connected, do not turn on the charging function
PPM->enableCharge();
ppm->enableCharge();
} else {
LOG_WARN("PPM BQ25896 init failed");
delete PPM;
PPM = nullptr;
delete ppm;
ppm = nullptr;
return false;
}
}
@@ -1421,23 +1402,23 @@ class LipoCharger : public HasBatteryLevel
/**
* return true if there is a battery installed in this unit
*/
virtual bool isBatteryConnect() override { return PPM->getBattVoltage() > 0; }
virtual bool isBatteryConnect() override { return ppm->getBattVoltage() > 0; }
/**
* return true if there is an external power source detected
*/
virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; }
virtual bool isVbusIn() override { return ppm->getVbusVoltage() > 0; }
/**
* return true if the battery is currently charging
*/
virtual bool isCharging() override
{
bool isCharging = PPM->isCharging();
bool isCharging = ppm->isCharging();
if (isCharging) {
LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull());
} else {
if (!PPM->isVbusIn()) {
if (!ppm->isVbusIn()) {
LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity());
}
}
@@ -1469,73 +1450,3 @@ bool Power::lipoChargerInit()
return false;
}
#endif
#ifdef HELTEC_MESH_SOLAR
#include "meshSolarApp.h"
/**
* meshSolar class for an SMBUS battery sensor.
*/
class meshSolarBatteryLevel : public HasBatteryLevel
{
public:
/**
* Init the I2C meshSolar battery level sensor
*/
bool runOnce()
{
meshSolarStart();
return true;
}
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
*/
virtual int getBatteryPercent() override { return meshSolarGetBatteryPercent(); }
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override { return meshSolarGetBattVoltage(); }
/**
* return true if there is a battery installed in this unit
*/
virtual bool isBatteryConnect() override { return meshSolarIsBatteryConnect(); }
/**
* return true if there is an external power source detected
*/
virtual bool isVbusIn() override { return meshSolarIsVbusIn(); }
/**
* return true if the battery is currently charging
*/
virtual bool isCharging() override { return meshSolarIsCharging(); }
};
meshSolarBatteryLevel meshSolarLevel;
/**
* Init the meshSolar battery level sensor
*/
bool Power::meshSolarInit()
{
bool result = meshSolarLevel.runOnce();
LOG_DEBUG("Power::meshSolarInit mesh solar sensor is %s", result ? "ready" : "not ready yet");
if (!result)
return false;
batteryLevel = &meshSolarLevel;
return true;
}
#else
/**
* The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
*/
bool Power::meshSolarInit()
{
return false;
}
#endif

View File

@@ -64,14 +64,6 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
int32_t SerialConsole::runOnce()
{
#ifdef HELTEC_MESH_SOLAR
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
{
return 250;
}
#endif
return runOncePart();
}

View File

@@ -86,9 +86,9 @@ void OSThread::run()
#ifdef DEBUG_HEAP
auto newHeap = memGet.getFreeHeap();
if (newHeap < heap)
LOG_HEAP("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
LOG_DEBUG("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
if (heap < newHeap)
LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
LOG_DEBUG("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
#endif
runned();

View File

@@ -26,10 +26,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <Arduino.h>
#if __has_include("Melopero_RV3028.h")
#ifdef RV3028_RTC
#include "Melopero_RV3028.h"
#endif
#if __has_include("pcf8563.h")
#ifdef PCF8563_RTC
#include "pcf8563.h"
#endif
@@ -135,7 +135,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------
// OLED & Input
// -----------------------------------------------------------------------------
#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
#if defined(SEEED_WIO_TRACKER_L1)
#define SSD1306_ADDRESS 0x3D
#define USE_SH1106
#else

View File

@@ -79,9 +79,7 @@ class ScanI2C
BQ27220,
LTR553ALS,
BHI260AP,
BMM150,
TSL2561,
DRV2605
BMM150
} DeviceType;
// typedef uint8_t DeviceAddress;

View File

@@ -461,17 +461,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
case TSL25911_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
if (registerValue == 0x50) {
type = TSL2591;
logFoundDevice("TSL25911", (uint8_t)addr.address);
} else {
type = TSL2561;
logFoundDevice("TSL2561", (uint8_t)addr.address);
}
break;
SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
@@ -493,14 +483,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = MLX90614;
logFoundDevice("MLX90614", (uint8_t)addr.address);
} else {
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // DRV2605_REG_STATUS
if (registerValue == 0xe0) {
type = DRV2605;
logFoundDevice("DRV2605", (uint8_t)addr.address);
} else {
type = MPR121KB;
logFoundDevice("MPR121KB", (uint8_t)addr.address);
}
type = MPR121KB;
logFoundDevice("MPR121KB", (uint8_t)addr.address);
}
break;

View File

@@ -1,4 +1,5 @@
#include <cstring> // Include for strstr
#include <string>
#include <vector>
#include "configuration.h"
@@ -842,6 +843,9 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
setPowerPMU(true); // Power (PMU): on
writePinStandby(false); // Standby (pin): awake (not standby)
setPowerUBLOX(true); // Standby (UBLOX): awake
#ifdef GNSS_AIROHA
lastFixStartMsec = 0;
#endif
break;
case GPS_SOFTSLEEP:
@@ -859,7 +863,9 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
writePinStandby(true); // Standby (pin): asleep (not awake)
setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed
#ifdef GNSS_AIROHA
digitalWrite(PIN_GPS_EN, LOW);
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) {
digitalWrite(PIN_GPS_EN, LOW);
}
#endif
break;
@@ -871,7 +877,9 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
writePinStandby(true); // Standby (pin): asleep
setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely
#ifdef GNSS_AIROHA
digitalWrite(PIN_GPS_EN, LOW);
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) {
digitalWrite(PIN_GPS_EN, LOW);
}
#endif
break;
}
@@ -1054,8 +1062,6 @@ void GPS::down()
}
// If update interval long enough (or softsleep unsupported): hardsleep instead
setPowerState(GPS_HARDSLEEP, sleepTime);
// Reset the fix quality to 0, since we're off.
fixQual = 0;
}
}
@@ -1115,19 +1121,11 @@ int32_t GPS::runOnce()
shouldPublish = true;
}
uint8_t prev_fixQual = fixQual;
bool gotLoc = lookForLocation();
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
LOG_DEBUG("hasValidLocation RISING EDGE");
hasValidLocation = true;
shouldPublish = true;
// Hold for 20secs after getting a lock to download ephemeris etc
fixHoldEnds = millis() + 20000;
}
if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on.
fixHoldEnds = millis() + 20000;
shouldPublish = true; // Publish immediately, since next publish is at end of hold
}
bool tooLong = scheduling.searchedTooLong();
@@ -1136,7 +1134,8 @@ int32_t GPS::runOnce()
// Once we get a location we no longer desperately want an update
if ((gotLoc && gotTime) || tooLong) {
if (tooLong && !gotLoc) {
if (tooLong) {
// we didn't get a location during this ack window, therefore declare loss of lock
if (hasValidLocation) {
LOG_DEBUG("hasValidLocation FALLING EDGE");
@@ -1144,15 +1143,9 @@ int32_t GPS::runOnce()
p = meshtastic_Position_init_default;
hasValidLocation = false;
}
if (millis() > fixHoldEnds) {
shouldPublish = true; // publish our update at the end of the lock hold
publishUpdate();
down();
#ifdef GPS_DEBUG
} else {
LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view);
#endif
}
down();
shouldPublish = true; // publish our update for this just finished acquisition window
}
// If state has changed do a publish
@@ -1205,7 +1198,7 @@ static const char *DETECTED_MESSAGE = "%s detected";
LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \
clearBuffer(); \
_serial_gps->write(COMMAND "\r\n"); \
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP, serialSpeed); \
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP); \
if (detectedDriver != GNSS_MODEL_UNKNOWN) { \
return detectedDriver; \
} \
@@ -1367,55 +1360,36 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_UNKNOWN;
}
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed)
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap)
{
// Calculate buffer size based on baud rate - 256 bytes for 9600 baud as baseline
// Higher baud rates get proportionally larger buffers to handle more data
int bufferSize = (serialSpeed * 256) / 9600;
// Clamp buffer size between reasonable limits
if (bufferSize < 128)
bufferSize = 128;
if (bufferSize > 2048)
bufferSize = 2048;
char *response = new char[bufferSize](); // Dynamically allocate based on baud rate
uint16_t responseLen = 0;
String response = "";
unsigned long start = millis();
while (millis() - start < timeout) {
if (_serial_gps->available()) {
char c = _serial_gps->read();
response += (char)_serial_gps->read();
// Add char to buffer if there's space
if (responseLen < bufferSize - 1) {
response[responseLen++] = c;
response[responseLen] = '\0';
}
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
if (response.endsWith(",") || response.endsWith("\r\n")) {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
LOG_DEBUG(response.c_str());
#endif
// check if we can see our chips
for (const auto &chipInfo : responseMap) {
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
if (strstr(response.c_str(), chipInfo.detectionString.c_str()) != nullptr) {
LOG_INFO("%s detected", chipInfo.chipName.c_str());
delete[] response; // Cleanup before return
return chipInfo.driver;
}
}
}
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
// Reset the response buffer for the next potential message
responseLen = 0;
response[0] = '\0';
if (response.endsWith("\r\n")) {
response.trim();
response = ""; // Reset the response string for the next potential message
}
}
}
#ifdef GPS_DEBUG
LOG_DEBUG(response);
LOG_DEBUG(response.c_str());
#endif
delete[] response; // Cleanup before return
return GNSS_MODEL_UNKNOWN; // Return unknown on timeout
return GNSS_MODEL_UNKNOWN; // Return empty string on timeout
}
GPS *GPS::createGps()
@@ -1530,10 +1504,28 @@ static int32_t toDegInt(RawDegrees d)
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
* Override this method to check for new locations
*
* @return true if we've set a new time
* @return true if we've acquired a new location
*/
bool GPS::lookForTime()
{
#ifdef GNSS_AIROHA
uint8_t fix = reader.fixQuality();
if (fix >= 1 && fix <= 5) {
if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false;
} else {
clearBuffer();
}
} else {
lastFixStartMsec = millis();
return false;
}
} else {
return false;
}
#endif
auto ti = reader.time;
auto d = reader.date;
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
@@ -1550,13 +1542,13 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
t.tm_year = d.year() - 1900;
t.tm_isdst = false;
if (t.tm_mon > -1) {
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) {
LOG_DEBUG("NMEA GPS time set %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour,
t.tm_min, t.tm_sec, ti.age());
return true;
} else {
return false;
LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
t.tm_sec, ti.age());
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
// Clear the GPS buffer if we got an invalid time
clearBuffer();
}
return true;
} else
return false;
} else
@@ -1571,6 +1563,25 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
*/
bool GPS::lookForLocation()
{
#ifdef GNSS_AIROHA
if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) {
uint8_t fix = reader.fixQuality();
if (fix >= 1 && fix <= 5) {
if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false;
} else {
clearBuffer();
}
} else {
lastFixStartMsec = millis();
return false;
}
} else {
return false;
}
}
#endif
// By default, TinyGPS++ does not parse GPGSA lines, which give us
// the 2D/3D fixType (see NMEAGPS.h)
// At a minimum, use the fixQuality indicator in GPGGA (FIXME?)

View File

@@ -159,7 +159,7 @@ class GPS : private concurrency::OSThread
uint8_t fixType = 0; // fix type from GPGSA
#endif
uint32_t fixHoldEnds = 0;
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0;
uint32_t rx_gpio = 0;
uint32_t tx_gpio = 0;
@@ -236,7 +236,7 @@ class GPS : private concurrency::OSThread
virtual int32_t runOnce() override;
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed);
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap);
// Get GNSS model
GnssModel_t probe(int serialSpeed);

View File

@@ -9,9 +9,6 @@
static RTCQuality currentQuality = RTCQualityNone;
uint32_t lastSetFromPhoneNtpOrGps = 0;
static uint32_t lastTimeValidationWarning = 0;
static const uint32_t TIME_VALIDATION_WARNING_INTERVAL_MS = 15000; // 15 seconds
RTCQuality getRTCQuality()
{
return currentQuality;
@@ -26,7 +23,7 @@ static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only upda
* Reads the current date and time from the RTC module and updates the system time.
* @return True if the RTC was successfully read and the system time was updated, false otherwise.
*/
RTCSetResult readFromRTC()
void readFromRTC()
{
struct timeval tv; /* btw settimeofday() is helpful here too*/
#ifdef RV3028_RTC
@@ -47,25 +44,15 @@ RTCSetResult readFromRTC()
t.tm_sec = rtc.getSecond();
tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
}
return RTCSetResultInvalidTime;
}
#endif
LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
if (currentQuality == RTCQualityNone) {
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice;
}
return RTCSetResultSuccess;
}
#elif defined(PCF8563_RTC)
if (rtc_found.address == PCF8563_RTC) {
@@ -88,26 +75,15 @@ RTCSetResult readFromRTC()
t.tm_sec = tc.second;
tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
}
#endif
LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
if (currentQuality == RTCQualityNone) {
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice;
}
return RTCSetResultSuccess;
}
#else
if (!gettimeofday(&tv, NULL)) {
@@ -116,10 +92,8 @@ RTCSetResult readFromRTC()
LOG_DEBUG("Read RTC time as %ld", printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
return RTCSetResultSuccess;
}
#endif
return RTCSetResultNotSet;
}
/**
@@ -127,7 +101,7 @@ RTCSetResult readFromRTC()
*
* @param q The quality of the provided time.
* @param tv A pointer to a timeval struct containing the time to potentially set the RTC to.
* @return RTCSetResult
* @return True if the RTC was set, false otherwise.
*
* If we haven't yet set our RTC this boot, set it from a GPS derived time
*/
@@ -138,20 +112,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv->tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
} else if (tv->tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
// Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
}
#endif
@@ -269,20 +230,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
} else if (tv.tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
// Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
}
#endif

View File

@@ -48,13 +48,10 @@ uint32_t getTime(bool local = false);
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
uint32_t getValidTime(RTCQuality minQuality, bool local = false);
RTCSetResult readFromRTC();
void readFromRTC();
time_t gm_mktime(struct tm *tm);
#define SEC_PER_DAY 86400
#define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60
#ifdef BUILD_EPOCH
#define FORTY_YEARS (40ULL * 365 * SEC_PER_DAY) // Use 64-bit arithmetic to prevent overflow
#endif

View File

@@ -67,28 +67,20 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
const bool flipped = config.display.flip_screen;
// HACK for L1 EInk
#if defined(SEEED_WIO_TRACKER_L1_EINK)
// For SEEED_WIO_TRACKER_L1_EINK, setRotation(3) is correct but mirrored; flip both axes
for (uint32_t y = 0; y < displayHeight; y++) {
for (uint32_t x = 0; x < displayWidth; x++) {
auto b = buffer[x + (y / 8) * displayWidth];
auto isset = b & (1 << (y & 7));
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
}
}
#else
for (uint32_t y = 0; y < displayHeight; y++) {
for (uint32_t x = 0; x < displayWidth; x++) {
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
auto b = buffer[x + (y / 8) * displayWidth];
auto isset = b & (1 << (y & 7));
// Handle flip here, rather than with setRotation(),
// Avoids issues when display width is not a multiple of 8
if (flipped)
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
else
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
}
}
#endif
// Trigger the refresh in GxEPD2
LOG_DEBUG("Update E-Paper");
@@ -243,7 +235,7 @@ bool EInkDisplay::connect()
adafruitDisplay->setRotation(1);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
}
#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
#elif defined(HELTEC_MESH_POCKET)
{
spi1 = &SPI1;
spi1->begin();
@@ -257,7 +249,6 @@ bool EInkDisplay::connect()
// Init GxEPD2
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
}
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)

View File

@@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay
SPIClass *hspi = NULL;
#endif
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
#if defined(HELTEC_MESH_POCKET)
SPIClass *spi1 = NULL;
#endif

View File

@@ -318,7 +318,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#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(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
@@ -550,7 +550,7 @@ void Screen::setup()
#else
if (!config.display.flip_screen) {
#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(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();

View File

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

View File

@@ -562,91 +562,6 @@ class LGFX : public lgfx::LGFX_Device
static LGFX *tft = nullptr;
#elif defined(ST7796_CS)
#include <LovyanGFX.hpp> // Graphics and font library for ST7796 driver chip
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7796 _panel_instance;
lgfx::Bus_SPI _bus_instance;
lgfx::Light_PWM _light_instance;
public:
LGFX(void)
{
{
auto cfg = _bus_instance.config();
// SPI
cfg.spi_host = ST7796_SPI_HOST;
cfg.spi_mode = 0;
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
// 80MHz by an integer)
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
cfg.spi_3wire = false;
cfg.use_lock = true; // Set to true to use transaction locking
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
// SPI_DMA_CH_AUTO=auto setting)
cfg.pin_sclk = ST7796_SCK; // Set SPI SCLK pin number
cfg.pin_mosi = ST7796_SDA; // Set SPI MOSI pin number
cfg.pin_miso = ST7796_MISO; // Set SPI MISO pin number (-1 = disable)
cfg.pin_dc = ST7796_RS; // Set SPI DC pin number (-1 = disable)
_bus_instance.config(cfg); // applies the set value to the bus.
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
}
{ // Set the display panel control.
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
cfg.pin_cs = ST7796_CS; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = ST7796_RESET; // Pin number where RST is connected (-1 = disable)
cfg.pin_busy = ST7796_BUSY; // Pin number where BUSY is connected (-1 = disable)
// cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
// cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
cfg.panel_width = TFT_WIDTH; // actual displayable width
cfg.panel_height = TFT_HEIGHT; // actual displayable height
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
#ifdef TFT_DUMMY_READ_PIXELS
cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout
#else
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
#endif
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
cfg.readable = true; // Set to true if data can be read
cfg.invert = true; // Set to true if the light/darkness of the panel is reversed
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
cfg.dlen_16bit =
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
_panel_instance.config(cfg);
}
#ifdef ST7796_BL
// Set the backlight control. (delete if not necessary)
{
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
cfg.pin_bl = ST7796_BL; // Pin number to which the backlight is connected
cfg.invert = false; // true to invert the brightness of the backlight
cfg.freq = 44100;
cfg.pwm_channel = 7;
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
}
#endif
setPanel(&_panel_instance); // Sets the panel to use.
}
};
static LGFX *tft = nullptr;
#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER)
#include <LovyanGFX.hpp> // Graphics and font library for ILI9341/ILI9342 driver chip
@@ -752,19 +667,15 @@ static LGFX *tft = nullptr;
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
#elif ARCH_PORTDUINO
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
#if defined(LGFX_SDL)
#include <lgfx/v1/platforms/sdl/Panel_sdl.hpp>
#endif
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_Device *_panel_instance;
lgfx::Bus_SPI _bus_instance;
lgfx::ITouch *_touch_instance;
public:
lgfx::Panel_Device *_panel_instance;
LGFX(void)
{
if (settingsMap[displayPanel] == st7789)
@@ -783,11 +694,6 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance = new lgfx::Panel_ILI9488;
else if (settingsMap[displayPanel] == hx8357d)
_panel_instance = new lgfx::Panel_HX8357D;
#if defined(LGFX_SDL)
else if (settingsMap[displayPanel] == x11) {
_panel_instance = new lgfx::Panel_sdl;
}
#endif
else {
_panel_instance = new lgfx::Panel_NULL;
LOG_ERROR("Unknown display panel configured!");
@@ -848,13 +754,7 @@ class LGFX : public lgfx::LGFX_Device
_touch_instance->config(touch_cfg);
_panel_instance->setTouch(_touch_instance);
}
#if defined(LGFX_SDL)
if (settingsMap[displayPanel] == x11) {
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
sdl_panel_->setup();
sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER);
}
#endif
setPanel(_panel_instance); // Sets the panel to use.
}
};
@@ -1082,9 +982,8 @@ static LGFX *tft = nullptr;
#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)
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_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 "TFTDisplay.h"
#include <SPI.h>
@@ -1128,154 +1027,37 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
#endif
}
TFTDisplay::~TFTDisplay()
{
// Clean up allocated line pixel buffer to prevent memory leak
if (linePixelBuffer != nullptr) {
free(linePixelBuffer);
linePixelBuffer = nullptr;
}
}
// Write the buffer to the display memory
void TFTDisplay::display(bool fromBlank)
{
if (fromBlank)
tft->fillScreen(TFT_BLACK);
// tft->clear();
concurrency::LockGuard g(spiLock);
uint32_t x, y;
uint32_t y_byteIndex;
uint8_t y_byteMask;
uint32_t x_FirstPixelUpdate;
uint32_t x_LastPixelUpdate;
bool isset, dblbuf_isset;
uint16_t colorTftMesh, colorTftBlack;
bool somethingChanged = false;
uint16_t x, y;
// Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8);
colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8);
y = 0;
while (y < displayHeight) {
y_byteIndex = (y / 8) * displayWidth;
y_byteMask = (1 << (y & 7));
// Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas.
if (y_byteMask == 1) {
for (y = 0; y < displayHeight; y++) {
for (x = 0; x < displayWidth; x++) {
auto isset = buffer[x + (y / 8) * displayWidth] & (1 << (y & 7));
if (!fromBlank) {
for (x = 0; x < displayWidth; x++) {
if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex])
break;
}
} else {
for (x = 0; x < displayWidth; x++) {
if (buffer[x + y_byteIndex] != 0)
break;
}
}
if (x >= displayWidth) {
// No changed pixels found in these 8 rows, fast-forward to the next 8
y = y + 8;
continue;
}
}
// Step 2: Scan each of the 8 rows individually. Find the first pixel in each row that needs updating
for (x_FirstPixelUpdate = 0; x_FirstPixelUpdate < displayWidth; x_FirstPixelUpdate++) {
isset = buffer[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
if (!fromBlank) {
// get src pixel in the page based ordering the OLED lib uses
dblbuf_isset = buffer_back[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7));
if (isset != dblbuf_isset) {
break;
tft->drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK);
}
} else if (isset) {
break;
tft->drawPixel(x, y, TFT_MESH);
}
}
// Did we find a pixel that needs updating on this row?
if (x_FirstPixelUpdate < displayWidth) {
// Quickly write out the first changed pixel (saves another array lookup)
linePixelBuffer[x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack;
x_LastPixelUpdate = x_FirstPixelUpdate;
// Step 3: copy all remaining pixels in this row into the pixel line buffer,
// while also recording the last pixel in the row that needs updating
for (x = x_FirstPixelUpdate + 1; x < displayWidth; x++) {
isset = buffer[x + y_byteIndex] & y_byteMask;
linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack;
if (!fromBlank) {
dblbuf_isset = buffer_back[x + y_byteIndex] & y_byteMask;
if (isset != dblbuf_isset) {
x_LastPixelUpdate = x;
}
} else if (isset) {
x_LastPixelUpdate = x;
}
}
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
&linePixelBuffer[x_FirstPixelUpdate]);
somethingChanged = true;
}
y++;
}
// Copy the Buffer to the Back Buffer
if (somethingChanged)
memcpy(buffer_back, buffer, displayBufferSize);
}
void TFTDisplay::sdlLoop()
{
#if defined(LGFX_SDL)
static int lastPressed = 0;
static int shuttingDown = false;
if (settingsMap[displayPanel] == x11) {
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance;
if (sdl_panel_->loop() && !shuttingDown) {
LOG_WARN("Window Closed!");
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
// debounce
if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed))
return;
if (!lgfx::v1::gpio_in(37)) {
lastPressed = 37;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!lgfx::v1::gpio_in(36)) {
lastPressed = 36;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!lgfx::v1::gpio_in(38)) {
lastPressed = 38;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!lgfx::v1::gpio_in(39)) {
lastPressed = 39;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) {
lastPressed = SDL_SCANCODE_KP_ENTER;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else {
lastPressed = 0;
for (y = 0; y < (displayHeight / 8); y++) {
for (x = 0; x < displayWidth; x++) {
uint16_t pos = x + y * displayWidth;
buffer_back[pos] = buffer[pos];
}
}
#endif
}
// Send a command to the display (low level function)
@@ -1424,21 +1206,13 @@ bool TFTDisplay::connect()
tft->setRotation(1); // T-Deck has the TFT in landscape
#elif defined(T_WATCH_S3)
tft->setRotation(2); // T-Watch S3 left-handed orientation
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER)
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR)
tft->setRotation(0); // use config.yaml to set rotation
#else
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
#endif
tft->fillScreen(TFT_BLACK);
if (this->linePixelBuffer == NULL) {
this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth);
if (!this->linePixelBuffer) {
LOG_ERROR("Not enough memory to create TFT line buffer\n");
return false;
}
}
return true;
}

View File

@@ -20,13 +20,9 @@ class TFTDisplay : public OLEDDisplay
*/
TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C);
// Destructor to clean up allocated memory
~TFTDisplay();
// Write the buffer to the display memory
virtual void display() override { display(false); };
virtual void display(bool fromBlank);
void sdlLoop();
// Turn the display upside down
virtual void flipScreenVertically();
@@ -61,6 +57,4 @@ class TFTDisplay : public OLEDDisplay
// Connect to the display
virtual bool connect() override;
uint16_t *linePixelBuffer = nullptr;
};

View File

@@ -94,8 +94,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
#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) || \
ARCH_PORTDUINO) && \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
8, imgQuestionL1);
@@ -107,7 +106,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
#endif
} else {
#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(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
8, imgSFL1);
@@ -122,8 +121,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
} else {
// 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) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
ARCH_PORTDUINO) && \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1);
@@ -263,6 +261,12 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
display->drawString(x + 1, y, "USB");
}
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
// if (config.display.heading_bold)
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
uint32_t currentMillis = millis();
uint32_t seconds = currentMillis / 1000;
uint32_t minutes = seconds / 60;
@@ -392,7 +396,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
// === Second Row: Radio Preset ===
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
char regionradiopreset[25];
const char *region = myRegion ? myRegion->name : NULL;
if (region != nullptr) {

View File

@@ -434,8 +434,8 @@ void menuHandler::systemBaseMenu()
optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications;
#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \
defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Options";
optionsEnumArray[options++] = ScreenOptions;
#endif
@@ -725,7 +725,7 @@ void menuHandler::BrightnessPickerMenu()
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
// For HELTEC devices, use analogWrite to control backlight
analogWrite(VTFT_LEDA, uiconfig.screen_brightness);
#elif defined(ST7789_CS) || defined(ST7796_CS)
#elif defined(ST7789_CS)
static_cast<TFTDisplay *>(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness);
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness);
@@ -768,7 +768,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 10;
bannerOptions.bannerCallback = [display](int selected) -> void {
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
uint8_t TFT_MESH_r = 0;
uint8_t TFT_MESH_g = 0;
uint8_t TFT_MESH_b = 0;
@@ -1045,7 +1045,7 @@ void menuHandler::screenOptionsMenu()
}
// Only show screen color for TFT displays
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor;
#endif

View File

@@ -194,7 +194,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) || \
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(DISPLAY_FORCE_SMALL_FONTS)
if (isHighResolution) {

View File

@@ -27,8 +27,7 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
#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) || \
ARCH_PORTDUINO) && \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \
!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 imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};

View File

@@ -1,68 +0,0 @@
#include "./ZJY122250_0213BAAMFGN.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void ZJY122250_0213BAAMFGN::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 249 (vertical resolution 250px)
sendCommand(0x01);
sendData(0xF9);
sendData(0x00);
sendData(0x00);
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
// the controller IC's OTP memory, when the update procedure begins.
void ZJY122250_0213BAAMFGN::configWaveform()
{
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x80); // VCOM
break;
case FULL:
default:
sendCommand(0x3C); // Border waveform:
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
break;
}
sendCommand(0x18); // Temperature sensor:
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
}
void ZJY122250_0213BAAMFGN::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void ZJY122250_0213BAAMFGN::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 2000); // At least 2 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@@ -1,42 +0,0 @@
/*
E-Ink display driver
- ZJY122250_0213BAAMFGN
- Manufacturer: Zhongjingyuan
- Size: 2.13 inch
- Resolution: 250px x 122px
- Flex connector marking (not a unique identifier): FPC-A002
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class ZJY122250_0213BAAMFGN : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
ZJY122250_0213BAAMFGN() : SSD16XX(width, height, supported) {}
protected:
virtual void configScanning() override;
virtual void configWaveform() override;
virtual void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@@ -7,7 +7,12 @@ using namespace NicheGraphics;
// Timing for "maintenance"
// Paying off full-refresh debt with unprovoked updates, if the display is not very active
#ifdef SEEED_WIO_TRACKER_L1
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 5 * 1000UL;
#else
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
#endif
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator")

View File

@@ -1,73 +0,0 @@
#ifdef T_LORA_PAGER
#include "RotaryEncoderImpl.h"
#include "InputBroker.h"
#include "RotaryEncoder.h"
#define ORIGIN_NAME "RotaryEncoder"
RotaryEncoderImpl *rotaryEncoderImpl;
RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME)
{
rotary = nullptr;
}
bool RotaryEncoderImpl::init()
{
if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
moduleConfig.canned_message.inputbroker_pin_b == 0) {
// Input device is disabled.
disable();
return false;
}
eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
moduleConfig.canned_message.inputbroker_pin_press);
rotary->resetButton();
inputBroker->registerSource(this);
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
eventPressed);
return true;
}
int32_t RotaryEncoderImpl::runOnce()
{
InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0};
static uint32_t lastPressed = millis();
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
if (lastPressed + 200 < millis()) {
LOG_DEBUG("Rotary event Press");
lastPressed = millis();
e.inputEvent = this->eventPressed;
}
} else {
switch (rotary->process()) {
case RotaryEncoder::DIRECTION_CW:
LOG_DEBUG("Rotary event CW");
e.inputEvent = this->eventCw;
break;
case RotaryEncoder::DIRECTION_CCW:
LOG_DEBUG("Rotary event CCW");
e.inputEvent = this->eventCcw;
break;
default:
break;
}
}
if (e.inputEvent != INPUT_BROKER_NONE) {
this->notifyObservers(&e);
}
return 10;
}
#endif

View File

@@ -1,28 +0,0 @@
#pragma once
// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
#include "InputBroker.h"
#include "concurrency/OSThread.h"
#include "mesh/NodeDB.h"
class RotaryEncoder;
class RotaryEncoderImpl : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
RotaryEncoderImpl();
bool init(void);
protected:
virtual int32_t runOnce() override;
input_broker_event eventCw = INPUT_BROKER_NONE;
input_broker_event eventCcw = INPUT_BROKER_NONE;
input_broker_event eventPressed = INPUT_BROKER_NONE;
RotaryEncoder *rotary;
const char *originName;
};
extern RotaryEncoderImpl *rotaryEncoderImpl;

View File

@@ -18,23 +18,14 @@ void RotaryEncoderInterruptBase::init(
this->_eventCcw = eventCcw;
this->_eventPressed = eventPressed;
bool isRAK = false;
#ifdef RAK_4631
isRAK = true;
#endif
pinMode(pinPress, INPUT_PULLUP);
pinMode(this->_pinA, INPUT_PULLUP);
pinMode(this->_pinB, INPUT_PULLUP);
if (!isRAK || pinPress != 0) {
pinMode(pinPress, INPUT_PULLUP);
attachInterrupt(pinPress, onIntPress, RISING);
}
if (!isRAK || this->_pinA != 0) {
pinMode(this->_pinA, INPUT_PULLUP);
attachInterrupt(this->_pinA, onIntA, CHANGE);
}
if (!isRAK || this->_pinA != 0) {
pinMode(this->_pinB, INPUT_PULLUP);
attachInterrupt(this->_pinB, onIntB, CHANGE);
}
// attachInterrupt(pinPress, onIntPress, RISING);
attachInterrupt(pinPress, onIntPress, RISING);
attachInterrupt(this->_pinA, onIntA, CHANGE);
attachInterrupt(this->_pinB, onIntB, CHANGE);
this->rotaryLevelA = digitalRead(this->_pinA);
this->rotaryLevelB = digitalRead(this->_pinB);

View File

@@ -1,230 +0,0 @@
#if defined(T_LORA_PAGER)
#include "TLoraPagerKeyboard.h"
#include "main.h"
#ifndef LEDC_BACKLIGHT_CHANNEL
#define LEDC_BACKLIGHT_CHANNEL 4
#endif
#ifndef LEDC_BACKLIGHT_BIT_WIDTH
#define LEDC_BACKLIGHT_BIT_WIDTH 8
#endif
#ifndef LEDC_BACKLIGHT_FREQ
#define LEDC_BACKLIGHT_FREQ 1000 // Hz
#endif
#define _TCA8418_COLS 10
#define _TCA8418_ROWS 4
#define _TCA8418_NUM_KEYS 31
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
using Key = TCA8418KeyboardBase::TCA8418Key;
constexpr uint8_t modifierRightShiftKey = 29 - 1; // keynum -1
constexpr uint8_t modifierRightShift = 0b0001;
constexpr uint8_t modifierSymKey = 21 - 1;
constexpr uint8_t modifierSym = 0b0010;
// Num chars per key, Modulus for rotating through characters
static uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'},
{'w', 'W', '2'},
{'e', 'E', '3'},
{'r', 'R', '4'},
{'t', 'T', '5'},
{'y', 'Y', '6'},
{'u', 'U', '7'},
{'i', 'I', '8'},
{'o', 'O', '9'},
{'p', 'P', '0'},
{'a', 'A', '*'},
{'s', 'S', '/'},
{'d', 'D', '+'},
{'f', 'F', '-'},
{'g', 'G', '='},
{'h', 'H', ':'},
{'j', 'J', '\''},
{'k', 'K', '"'},
{'l', 'L', '@'},
{Key::SELECT, 0x00, Key::TAB},
{0x00, 0x00, 0x00},
{'z', 'Z', '_'},
{'x', 'X', '$'},
{'c', 'C', ';'},
{'v', 'V', '?'},
{'b', 'B', '!'},
{'n', 'N', ','},
{'m', 'M', '.'},
{0x00, 0x00, 0x00},
{Key::BSP, 0x00, Key::ESC},
{' ', 0x00, Key::BL_TOGGLE}};
TLoraPagerKeyboard::TLoraPagerKeyboard()
: 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)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
#else
ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
ledcAttachPin(KB_BL_PIN, LEDC_BACKLIGHT_CHANNEL);
#endif
reset();
}
void TLoraPagerKeyboard::reset(void)
{
TCA8418KeyboardBase::reset();
pinMode(KB_BL_PIN, OUTPUT);
digitalWrite(KB_BL_PIN, LOW);
setBacklight(false);
}
// handle multi-key presses (shift and alt)
void TLoraPagerKeyboard::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 TLoraPagerKeyboard::setBacklight(bool on)
{
toggleBacklight(!on);
}
void TLoraPagerKeyboard::pressed(uint8_t key)
{
if (state == Init || state == Busy) {
return;
}
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY) {
hapticFeedback();
}
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 TLoraPagerKeyboard::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 (TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]] == Key::BL_TOGGLE) {
toggleBacklight();
return;
}
queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]);
if (isModifierKey(last_key) == false)
modifierFlag = 0;
}
void TLoraPagerKeyboard::hapticFeedback()
{
drv.setWaveform(0, 14); // strong buzz 100%
drv.setWaveform(1, 0); // end waveform
drv.go();
}
// toggle brightness of the backlight in three steps
void TLoraPagerKeyboard::toggleBacklight(bool off)
{
static uint32_t brightness = 0;
if (off) {
brightness = 0;
} else {
if (brightness == 0) {
brightness = 40;
} else if (brightness == 40) {
brightness = 127;
} else if (brightness >= 127) {
brightness = 0;
}
}
LOG_DEBUG("Toggle backlight: %d", brightness);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
ledcWrite(KB_BL_PIN, brightness);
#else
ledcWrite(LEDC_BACKLIGHT_CHANNEL, brightness);
#endif
}
void TLoraPagerKeyboard::updateModifierFlag(uint8_t key)
{
if (key == modifierRightShiftKey) {
modifierFlag ^= modifierRightShift;
} else if (key == modifierSymKey) {
modifierFlag ^= modifierSym;
}
}
bool TLoraPagerKeyboard::isModifierKey(uint8_t key)
{
return (key == modifierRightShiftKey || key == modifierSymKey);
}
#endif

View File

@@ -4,26 +4,9 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase
{
public:
TLoraPagerKeyboard();
void reset(void);
void trigger(void) override;
void setBacklight(bool on) override;
virtual ~TLoraPagerKeyboard() {}
void setBacklight(bool on) override{};
protected:
void pressed(uint8_t key) override;
void released(void) override;
void hapticFeedback(void);
void updateModifierFlag(uint8_t key);
bool isModifierKey(uint8_t key);
void toggleBacklight(bool off = false);
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;
void pressed(uint8_t key) override{};
void released(void) override{};
};

View File

@@ -15,23 +15,14 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
this->_eventDown = eventDown;
this->_eventUp = eventUp;
this->_eventPressed = eventPressed;
bool isRAK = false;
#ifdef RAK_4631
isRAK = true;
#endif
if (!isRAK || pinPress != 0) {
pinMode(pinPress, INPUT_PULLUP);
attachInterrupt(pinPress, onIntPress, RISING);
}
if (!isRAK || this->_pinDown != 0) {
pinMode(this->_pinDown, INPUT_PULLUP);
attachInterrupt(this->_pinDown, onIntDown, RISING);
}
if (!isRAK || this->_pinUp != 0) {
pinMode(this->_pinUp, INPUT_PULLUP);
attachInterrupt(this->_pinUp, onIntUp, RISING);
}
pinMode(pinPress, INPUT_PULLUP);
pinMode(this->_pinDown, INPUT_PULLUP);
pinMode(this->_pinUp, INPUT_PULLUP);
attachInterrupt(pinPress, onIntPress, RISING);
attachInterrupt(this->_pinDown, onIntDown, RISING);
attachInterrupt(this->_pinUp, onIntUp, RISING);
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);

View File

@@ -12,12 +12,8 @@ void CardKbI2cImpl::init()
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN)
if (cardkb_found.address == 0x00) {
LOG_DEBUG("Rescan for I2C keyboard");
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, TCA8418_KB_ADDR};
#if defined(T_LORA_PAGER)
uint8_t i2caddr_asize = sizeof(i2caddr_scan) / sizeof(i2caddr_scan[0]);
#else
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, XPOWERS_AXP192_AXP2101_ADDRESS};
uint8_t i2caddr_asize = 5;
#endif
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
#if WIRE_INTERFACES_COUNT == 2

View File

@@ -135,9 +135,8 @@ AccelerometerThread *accelerometerThread = nullptr;
AudioThread *audioThread = nullptr;
#endif
#ifdef USE_XL9555
#include "ExtensionIOXL9555.hpp"
ExtensionIOXL9555 io;
#ifdef USE_PCA9557
PCA9557 IOEXP;
#endif
#if HAS_TFT
@@ -202,7 +201,7 @@ ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE,
/// The I2C address of our Air Quality Indicator (if found)
ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef T_WATCH_S3
Adafruit_DRV2605 drv;
#endif
@@ -360,30 +359,6 @@ void setup()
digitalWrite(SDCARD_CS, HIGH);
pinMode(PIN_EINK_CS, OUTPUT);
digitalWrite(PIN_EINK_CS, HIGH);
#elif defined(T_LORA_PAGER)
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH);
// io expander
io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
io.pinMode(EXPANDS_DRV_EN, OUTPUT);
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
io.pinMode(EXPANDS_AMP_EN, OUTPUT);
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
io.pinMode(EXPANDS_LORA_EN, OUTPUT);
io.digitalWrite(EXPANDS_LORA_EN, HIGH);
io.pinMode(EXPANDS_GPS_EN, OUTPUT);
io.digitalWrite(EXPANDS_GPS_EN, HIGH);
io.pinMode(EXPANDS_KB_EN, OUTPUT);
io.digitalWrite(EXPANDS_KB_EN, HIGH);
io.pinMode(EXPANDS_SD_EN, OUTPUT);
io.digitalWrite(EXPANDS_SD_EN, HIGH);
io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
#endif
concurrency::hasBeenSetup = true;
@@ -419,7 +394,7 @@ void setup()
struct timeval tv;
tv.tv_sec = time(NULL);
tv.tv_usec = 0;
perhapsSetRTC(RTCQualityDevice, &tv);
perhapsSetRTC(RTCQualityNTP, &tv);
#endif
powerMonInit();
@@ -429,16 +404,6 @@ void setup()
initDeepSleep();
#if defined(MODEM_POWER_EN)
pinMode(MODEM_POWER_EN, OUTPUT);
digitalWrite(MODEM_POWER_EN, LOW);
#endif
#if defined(MODEM_PWRKEY)
pinMode(MODEM_PWRKEY, OUTPUT);
digitalWrite(MODEM_PWRKEY, LOW);
#endif
#if defined(LORA_TCXO_GPIO)
pinMode(LORA_TCXO_GPIO, OUTPUT);
digitalWrite(LORA_TCXO_GPIO, HIGH);
@@ -741,7 +706,6 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2561, meshtastic_TelemetrySensorType_TSL2561);
i2cScanner.reset();
#endif
@@ -806,15 +770,18 @@ void setup()
if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO)
screen_model = config.display.oled;
#if defined(USE_SH1107)
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128
screen_geometry = GEOMETRY_128_128;
#endif
#if defined(USE_SH1107_128_64)
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
screen_geometry = GEOMETRY_128_64;
#endif
// if we have one of the fixed overrides in the settings, adjust display type accordingly.
if (screen_model == meshtastic_Config_DisplayConfig_OledType_OLED_SH1107) {
screen_geometry = GEOMETRY_128_64;
} else if (screen_model == meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128) {
screen_geometry = GEOMETRY_128_128;
}
#if !MESHTASTIC_EXCLUDE_I2C
#if !defined(ARCH_STM32WL)
if (acc_info.type != ScanI2C::DeviceType::NONE) {
@@ -831,7 +798,7 @@ void setup()
#endif
#endif
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef T_WATCH_S3
drv.begin();
drv.selectLibrary(1);
// I2C trigger by sending 'go' command
@@ -860,7 +827,7 @@ void setup()
#elif !defined(ARCH_ESP32) // ARCH_RP2040
SPI.begin();
#else
// ESP32
// ESP32
#if defined(HW_SPI1_DEVICE)
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
@@ -877,7 +844,7 @@ void setup()
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#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)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
@@ -1140,7 +1107,7 @@ void setup()
// Don't call screen setup until after nodedb is setup (because we need
// the current region name)
#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)
if (screen)
screen->setup();
#elif defined(ARCH_PORTDUINO)
@@ -1526,7 +1493,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
#if ((!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES) && !defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG;
#endif
#if NO_EXT_GPIO || MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
#if NO_EXT_GPIO
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_EXTNOTIF_CONFIG;
#endif
// Only edge case here is if we apply this a device with built in Accelerometer and want to detect interrupts
@@ -1598,13 +1565,7 @@ void loop()
#endif
service->loop();
#if defined(LGFX_SDL)
if (screen) {
auto dispdev = screen->getDisplayDevice();
if (dispdev)
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
}
#endif
long delayMsec = mainController.runOrDelay();
// We want to sleep as long as possible here - because it saves power

View File

@@ -41,7 +41,7 @@ extern bool eink_found;
extern bool pmu_found;
extern bool isUSBPowered;
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef T_WATCH_S3
#include <Adafruit_DRV2605.h>
extern Adafruit_DRV2605 drv;
#endif

View File

@@ -88,16 +88,4 @@ uint32_t MemGet::getPsramSize()
#else
return 0;
#endif
}
void displayPercentHeapFree()
{
uint32_t freeHeap = memGet.getFreeHeap();
uint32_t totalHeap = memGet.getHeapSize();
if (totalHeap == 0 || totalHeap == UINT32_MAX) {
LOG_INFO("Heap size unavailable");
return;
}
int percent = (int)((freeHeap * 100) / totalHeap);
LOG_INFO("Heap free: %d%% (%u/%u bytes)", percent, freeHeap, totalHeap);
}

View File

@@ -368,7 +368,7 @@ const char *Channels::getName(size_t chIndex)
// Per mesh.proto spec, if bandwidth is specified we must ignore modemPreset enum, we assume that in that case
// the app effed up and forgot to set channelSettings.name
if (config.lora.use_preset) {
channelName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
channelName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
} else {
channelName = "Custom";
}
@@ -382,8 +382,7 @@ bool Channels::isDefaultChannel(ChannelIndex chIndex)
const auto &ch = getByIndex(chIndex);
if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) {
const char *name = getName(chIndex);
const char *presetName =
DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
// Check if the name is the default derived from the modem preset
if (strcmp(name, presetName) == 0)
return true;

View File

@@ -6,7 +6,6 @@
#include <memory>
#include "PointerQueue.h"
#include "configuration.h" // For LOG_WARN, LOG_DEBUG, LOG_HEAP
template <class T> class Allocator
{
@@ -15,14 +14,13 @@ template <class T> class Allocator
Allocator() : deleter([this](T *p) { this->release(p); }) {}
virtual ~Allocator() {}
/// Return a queable object which has been prefilled with zeros. Return nullptr if no buffer is available
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
/// Note: this method is safe to call from regular OR ISR code
T *allocZeroed()
{
T *p = allocZeroed(0);
if (!p) {
LOG_WARN("Failed to allocate zeroed memory");
}
assert(p); // FIXME panic instead
return p;
}
@@ -41,12 +39,10 @@ template <class T> class Allocator
T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
{
T *p = alloc(maxWait);
if (!p) {
LOG_WARN("Failed to allocate memory for copy");
return nullptr;
}
assert(p);
*p = src;
if (p)
*p = src;
return p;
}
@@ -87,11 +83,7 @@ template <class T> class MemoryDynamic : public Allocator<T>
/// Return a buffer for use by others
virtual void release(T *p) override
{
if (p == nullptr)
return;
LOG_HEAP("Freeing 0x%x", p);
assert(p);
free(p);
}
@@ -104,58 +96,3 @@ template <class T> class MemoryDynamic : public Allocator<T>
return p;
}
};
/**
* A static memory pool that uses a fixed buffer instead of heap allocation
*/
template <class T, int MaxSize> class MemoryPool : public Allocator<T>
{
private:
T pool[MaxSize];
bool used[MaxSize];
public:
MemoryPool() : pool{}, used{}
{
// Arrays are now zero-initialized by member initializer list
// pool array: all elements are default-constructed (zero for POD types)
// used array: all elements are false (zero-initialized)
}
/// Return a buffer for use by others
virtual void release(T *p) override
{
if (!p) {
LOG_DEBUG("Failed to release memory, pointer is null");
return;
}
// Find the index of this pointer in our pool
int index = p - pool;
if (index >= 0 && index < MaxSize) {
assert(used[index]); // Should be marked as used
used[index] = false;
LOG_HEAP("Released static pool item %d at 0x%x", index, p);
} else {
LOG_WARN("Pointer 0x%x not from our pool!", p);
}
}
protected:
// Alloc some storage from our static pool
virtual T *alloc(TickType_t maxWait) override
{
// Find first free slot
for (int i = 0; i < MaxSize; i++) {
if (!used[i]) {
used[i] = true;
LOG_HEAP("Allocated static pool item %d at 0x%x", i, &pool[i]);
return &pool[i];
}
}
// No free slots available - return nullptr instead of asserting
LOG_WARN("No free slots available in static memory pool!");
return nullptr;
}
};

View File

@@ -85,8 +85,11 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e
return r;
}
void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char *specificModule)
{
if (specificModule) {
LOG_DEBUG("Calling specific module: %s", specificModule);
}
// LOG_DEBUG("In call modules");
bool moduleFound = false;
@@ -104,6 +107,11 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
for (auto i = modules->begin(); i != modules->end(); ++i) {
auto &pi = **i;
// If specificModule is provided, only call that specific module
if (specificModule && (!pi.name || strcmp(pi.name, specificModule) != 0)) {
continue;
}
pi.currentRequest = &mp;
/// We only call modules that are interested in the packet (and the message is destined to us or we are promiscious)

View File

@@ -73,7 +73,7 @@ class MeshModule
/** For use only by MeshService
*/
static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO);
static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO, const char *specificModule = nullptr);
static std::vector<MeshModule *> GetMeshModulesWithUIFrames(int startIndex);
static void observeUIEvents(Observer<const UIFrameEvent *> *observer);

View File

@@ -46,14 +46,11 @@ the new node can build its node db)
MeshService *service;
#define MAX_MQTT_PROXY_MESSAGES 16
static MemoryPool<meshtastic_MqttClientProxyMessage, MAX_MQTT_PROXY_MESSAGES> staticMqttClientProxyMessagePool;
static MemoryDynamic<meshtastic_MqttClientProxyMessage> staticMqttClientProxyMessagePool;
#define MAX_QUEUE_STATUS 4
static MemoryPool<meshtastic_QueueStatus, MAX_QUEUE_STATUS> staticQueueStatusPool;
static MemoryDynamic<meshtastic_QueueStatus> staticQueueStatusPool;
#define MAX_CLIENT_NOTIFICATIONS 4
static MemoryPool<meshtastic_ClientNotification, MAX_CLIENT_NOTIFICATIONS> staticClientNotificationPool;
static MemoryDynamic<meshtastic_ClientNotification> staticClientNotificationPool;
Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool;
@@ -64,10 +61,8 @@ Allocator<meshtastic_QueueStatus> &queueStatusPool = staticQueueStatusPool;
#include "Router.h"
MeshService::MeshService()
#ifdef ARCH_PORTDUINO
: toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_QUEUESTATUS_TOPHONE),
toPhoneMqttProxyQueue(MAX_RX_MQTTPROXY_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_NOTIFICATION_TOPHONE)
#endif
: toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE),
toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2)
{
lastQueueStatus = {0, 0, 16, 0};
}
@@ -196,10 +191,8 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
// (so we update our nodedb for the local node)
// Send the packet into the mesh
DEBUG_HEAP_BEFORE;
auto a = packetPool.allocCopy(p);
DEBUG_HEAP_AFTER("MeshService::handleToRadio", a);
sendToMesh(a, RX_SRC_USER);
sendToMesh(packetPool.allocCopy(p), RX_SRC_USER);
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback) {
@@ -255,11 +248,7 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
}
if ((res == ERRNO_OK || res == ERRNO_SHOULD_RELEASE) && ccToPhone) { // Check if p is not released in case it couldn't be sent
DEBUG_HEAP_BEFORE;
auto a = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("MeshService::sendToMesh", a);
sendToPhone(a);
sendToPhone(packetPool.allocCopy(*p));
}
// Router may ask us to release the packet if it wasn't sent

View File

@@ -9,12 +9,7 @@
#include "MeshRadio.h"
#include "MeshTypes.h"
#include "Observer.h"
#ifdef ARCH_PORTDUINO
#include "PointerQueue.h"
#else
#include "StaticPointerQueue.h"
#endif
#include "mesh-pb-constants.h"
#if defined(ARCH_PORTDUINO)
#include "../platform/portduino/SimRadio.h"
#endif
@@ -42,32 +37,16 @@ class MeshService
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
/// we never hang because android hasn't been there in a while
/// FIXME - save this to flash on deep sleep
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_MeshPacket> toPhoneQueue;
#else
StaticPointerQueue<meshtastic_MeshPacket, MAX_RX_TOPHONE> toPhoneQueue;
#endif
// keep list of QueueStatus packets to be send to the phone
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_QueueStatus> toPhoneQueueStatusQueue;
#else
StaticPointerQueue<meshtastic_QueueStatus, MAX_RX_QUEUESTATUS_TOPHONE> toPhoneQueueStatusQueue;
#endif
// keep list of MqttClientProxyMessages to be send to the client for delivery
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_MqttClientProxyMessage> toPhoneMqttProxyQueue;
#else
StaticPointerQueue<meshtastic_MqttClientProxyMessage, MAX_RX_MQTTPROXY_TOPHONE> toPhoneMqttProxyQueue;
#endif
// keep list of ClientNotifications to be send to the client (phone)
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_ClientNotification> toPhoneClientNotificationQueue;
#else
StaticPointerQueue<meshtastic_ClientNotification, MAX_RX_NOTIFICATION_TOPHONE> toPhoneClientNotificationQueue;
#endif
// This holds the last QueueStatus send
meshtastic_QueueStatus lastQueueStatus;

View File

@@ -34,11 +34,8 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
bool weWereNextHop = false;
if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
printPacket("Ignore dupe incoming msg", p);
if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
rxDupe++;
stopRetransmission(p->from, p->id);
}
rxDupe++;
stopRetransmission(p->from, p->id);
// If it was a fallback to flooding, try to relay again
if (wasFallback) {
@@ -74,11 +71,10 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
if (p->from != 0) {
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
if (origTx) {
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came directly
// from the destination
// Either relayer of ACK was also a relayer of the packet, or we were the relayer and the ACK came directly from
// the destination
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
(p->hop_start != 0 && p->hop_start == p->hop_limit &&
wasSoleRelayer(ourRelayID, p->decoded.request_id, p->to))) {
(wasRelayer(ourRelayID, p->decoded.request_id, p->to) && p->hop_start != 0 && p->hop_start == p->hop_limit)) {
if (origTx->next_hop != p->relay_node) { // Not already set
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
origTx->next_hop = p->relay_node;
@@ -175,18 +171,12 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id);
// now free the pooled copy for retransmission too
packetPool.release(p);
}
}
// Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
// get scheduled again. (This is the core of stopRetransmission.)
auto numErased = pending.erase(key);
assert(numErased == 1);
// When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
// to startRetransmission.
packetPool.release(p);
return true;
} else
return false;

View File

@@ -225,11 +225,7 @@ NodeDB::NodeDB()
memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end));
myNodeInfo.device_id.size = 16;
// Uncomment below to print the device id
#elif ARCH_PORTDUINO
if (portduino_config.has_device_id) {
memcpy(myNodeInfo.device_id.bytes, portduino_config.device_id, 16);
myNodeInfo.device_id.size = 16;
}
#else
// FIXME - implement for other platforms
#endif
@@ -663,7 +659,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.bluetooth.fixed_pin = defaultBLEPin;
#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(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
bool hasScreen = true;
#ifdef HELTEC_MESH_NODE_T114
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
@@ -830,15 +826,6 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.alert_message = true;
moduleConfig.external_notification.output_ms = 1000;
moduleConfig.external_notification.nag_timeout = 60;
#endif
#ifdef T_LORA_PAGER
moduleConfig.canned_message.updown1_enabled = true;
moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A;
moduleConfig.canned_message.inputbroker_pin_b = ROTARY_B;
moduleConfig.canned_message.inputbroker_pin_press = ROTARY_PRESS;
moduleConfig.canned_message.inputbroker_event_cw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(28);
moduleConfig.canned_message.inputbroker_event_ccw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(29);
moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
#endif
moduleConfig.has_canned_message = true;
#if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT
@@ -1711,10 +1698,10 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
{
if (mp.from == getNodeNum()) {
LOG_DEBUG("Ignore update from self");
return;
}
// if (mp.from == getNodeNum()) {
// LOG_DEBUG("Ignore update from self");
// return;
// }
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);

View File

@@ -294,7 +294,7 @@ void PacketHistory::insert(const PacketRecord &r)
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
* @return true if node was indeed a relayer, false if not */
bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole)
bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
if (!initOk()) {
LOG_ERROR("PacketHistory - wasRelayer: NOT INITIALIZED!");
@@ -322,42 +322,27 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const N
found->sender, found->id, found->next_hop, millis() - found->rxTimeMsec, found->relayed_by[0], found->relayed_by[1],
found->relayed_by[2], relayer);
#endif
return wasRelayer(relayer, *found, wasSole);
return wasRelayer(relayer, *found);
}
/* Check if a certain node was a relayer of a packet in the history given iterator
* @return true if node was indeed a relayer, false if not */
bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole)
bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r)
{
bool found = false;
bool other_present = false;
for (uint8_t i = 0; i < NUM_RELAYERS; ++i) {
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
if (r.relayed_by[i] == relayer) {
found = true;
} else if (r.relayed_by[i] != 0) {
other_present = true;
#if VERBOSE_PACKET_HISTORY
LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? YES", r.sender, r.id,
r.relayed_by[0], r.relayed_by[1], r.relayed_by[2], relayer);
#endif
return true;
}
}
if (wasSole) {
*wasSole = (found && !other_present);
}
#if VERBOSE_PACKET_HISTORY
LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? NO", r.sender, r.id, r.relayed_by[0],
r.relayed_by[1], r.relayed_by[2], relayer);
#endif
return found;
}
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
bool PacketHistory::wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
bool wasSole = false;
wasRelayer(relayer, id, sender, &wasSole);
return wasSole;
return false;
}
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender

View File

@@ -34,9 +34,8 @@ class PacketHistory
void insert(const PacketRecord &r); // Insert or replace a packet record in the history
/* Check if a certain node was a relayer of a packet in the history given iterator
* If wasSole is not nullptr, it will be set to true if the relayer was the only relayer of that packet
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr);
bool wasRelayer(const uint8_t relayer, const PacketRecord &r);
PacketHistory(const PacketHistory &); // non construction-copyable
PacketHistory &operator=(const PacketHistory &); // non copyable
@@ -55,12 +54,8 @@ class PacketHistory
bool *weWereNextHop = nullptr);
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
* If wasSole is not nullptr, it will be set to true if the relayer was the only relayer of that packet
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole = nullptr);
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
bool wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);

View File

@@ -100,7 +100,6 @@ void PhoneAPI::close()
config_nonce = 0;
config_state = 0;
pauseBluetoothLogging = false;
heartbeatReceived = false;
}
}

View File

@@ -586,8 +586,7 @@ void RadioInterface::applyModemConfig()
// Check if we use the default frequency slot
RadioInterface::uses_default_frequency_slot =
channel_num ==
hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels;
channel_num == hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false)) % numChannels;
// Old frequency selection formula
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);

View File

@@ -2,7 +2,6 @@
#include "Default.h"
#include "MeshTypes.h"
#include "configuration.h"
#include "memGet.h"
#include "mesh-pb-constants.h"
#include "modules/NodeInfoModule.h"
#include "modules/RoutingModule.h"
@@ -22,10 +21,8 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
if (p->hop_limit == 0) {
p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
}
DEBUG_HEAP_BEFORE;
auto copy = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("ReliableRouter::send", copy);
auto copy = packetPool.allocCopy(*p);
startRetransmission(copy, NUM_RELIABLE_RETX);
}
@@ -61,10 +58,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
// marked as wantAck
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, old->packet->channel);
// Only stop retransmissions if the rebroadcast came via LoRa
if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
stopRetransmission(key);
}
stopRetransmission(key);
} else {
LOG_DEBUG("Didn't find pending packet");
}

View File

@@ -5,7 +5,6 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "configuration.h"
#include "detect/LoRaRadioType.h"
#include "main.h"
@@ -28,24 +27,14 @@
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
// And every TX packet might have a retransmission packet or an ack alive at any moment
#ifdef ARCH_PORTDUINO
// Portduino (native) targets can use dynamic memory pools with runtime-configurable sizes
#define MAX_PACKETS \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
#else
// Embedded targets use static memory pools with compile-time constants
#define MAX_PACKETS_STATIC \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
// static MemoryPool<MeshPacket> staticPool(MAX_PACKETS);
static MemoryDynamic<meshtastic_MeshPacket> staticPool;
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
#endif
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
@@ -286,10 +275,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
// If the packet is not yet encrypted, do so now
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("Router::send", p_decoded);
auto encodeResult = perhapsEncode(p);
if (encodeResult != meshtastic_Routing_Error_NONE) {
@@ -543,9 +529,8 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
#endif
// Don't use PKC with Ham mode
!owner.is_licensed &&
// Don't use PKC on 'serial' or 'gpio' channels unless explicitly requested
!(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 ||
strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) &&
// Don't use PKC if it's not explicitly requested and a non-primary channel is requested
!(p->pki_encrypted != true && p->channel > 0) &&
// Check for valid keys and single node destination
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
// Check for a known public key for the destination
@@ -576,7 +561,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
// Now that we are encrypting the packet channel should be the hash (no longer the index)
p->channel = hash;
if (hash < 0) {
// No suitable channel could be found for
// No suitable channel could be found for sending
return meshtastic_Routing_Error_NO_CHANNEL;
}
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
@@ -592,7 +577,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
// Now that we are encrypting the packet channel should be the hash (no longer the index)
p->channel = hash;
if (hash < 0) {
// No suitable channel could be found for
// No suitable channel could be found for sending
return meshtastic_Routing_Error_NO_CHANNEL;
}
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
@@ -621,11 +606,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
bool skipHandle = false;
// Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
// Store a copy of encrypted packet for MQTT
DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted);
// Take those raw bytes and convert them back into a well structured protobuf we can understand
auto decodedState = perhapsDecode(p);
@@ -673,7 +655,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
// call modules here
// If this could be a spoofed packet, don't let the modules see it.
if (!skipHandle) {
if (!skipHandle && p->from != nodeDB->getNodeNum()) {
MeshModule::callModules(*p, src);
#if !MESHTASTIC_EXCLUDE_MQTT
@@ -687,6 +669,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
!isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel);
#endif
} else if (p->from == nodeDB->getNodeNum() && !skipHandle) {
MeshModule::callModules(*p, src, ROUTING_MODULE);
}
packetPool.release(p_encrypted); // Release the encrypted packet

View File

@@ -1,13 +1,13 @@
#include "configuration.h"
#ifdef ARCH_STM32WL
#include "STM32WLE5JCInterface.h"
#include "configuration.h"
#include "error.h"
#ifndef STM32WLx_MAX_POWER
#define STM32WLx_MAX_POWER 22
#endif
#ifdef ARCH_STM32WL
STM32WLE5JCInterface::STM32WLE5JCInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq,
RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy)
: SX126xInterface(hal, cs, irq, rst, busy)

View File

@@ -1,8 +1,8 @@
#pragma once
#ifdef ARCH_STM32WL
#include "SX126xInterface.h"
#include "rfswitch.h"
#ifdef ARCH_STM32WL
/**
* Our adapter for STM32WLE5JC radios
@@ -16,4 +16,13 @@ class STM32WLE5JCInterface : public SX126xInterface<STM32WLx>
virtual bool init() override;
};
/* https://wiki.seeedstudio.com/LoRa-E5_STM32WLE5JC_Module/
* Wio-E5 module ONLY transmits through RFO_HP
* Receive: PA4=1, PA5=0
* Transmit(high output power, SMPS mode): PA4=0, PA5=1 */
static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PA4, PA5, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[4] = {
{STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE};
#endif // ARCH_STM32WL

View File

@@ -1,77 +0,0 @@
#pragma once
#include "concurrency/OSThread.h"
#include "freertosinc.h"
#include <cassert>
/**
* A static circular buffer queue for pointers.
* This provides the same interface as PointerQueue but uses a statically allocated
* buffer instead of dynamic allocation.
*/
template <class T, int MaxElements> class StaticPointerQueue
{
static_assert(MaxElements > 0, "MaxElements must be greater than 0");
T *buffer[MaxElements];
int head = 0;
int tail = 0;
int count = 0;
concurrency::OSThread *reader = nullptr;
public:
StaticPointerQueue()
{
// Initialize all buffer elements to nullptr to silence warnings and ensure clean state
for (int i = 0; i < MaxElements; i++) {
buffer[i] = nullptr;
}
}
int numFree() const { return MaxElements - count; }
bool isEmpty() const { return count == 0; }
int numUsed() const { return count; }
bool enqueue(T *x, TickType_t maxWait = portMAX_DELAY)
{
if (count >= MaxElements) {
return false; // Queue is full
}
if (reader) {
reader->setInterval(0);
concurrency::mainDelay.interrupt();
}
buffer[tail] = x;
tail = (tail + 1) % MaxElements;
count++;
return true;
}
bool dequeue(T **p, TickType_t maxWait = portMAX_DELAY)
{
if (count == 0) {
return false; // Queue is empty
}
*p = buffer[head];
head = (head + 1) % MaxElements;
count--;
return true;
}
// returns a ptr or null if the queue was empty
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
{
T *p;
return dequeue(&p, maxWait) ? p : nullptr;
}
void setReader(concurrency::OSThread *t) { reader = t; }
// For compatibility with PointerQueue interface
int getMaxLen() const { return MaxElements; }
};

View File

@@ -16,95 +16,6 @@ int32_t StreamAPI::runOncePart()
return result;
}
int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen)
{
auto result = readStream(buf, bufLen);
writeStream();
checkConnectionTimeout();
return result;
}
/**
* Read any rx chars from the link and call handleRecStream
*/
int32_t StreamAPI::readStream(char *buf, uint16_t bufLen)
{
if (bufLen < 1) {
// Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time
bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000);
return recentRx ? 5 : 250;
} else {
handleRecStream(buf, bufLen);
// we had bytes available this time, so assume we might have them next time also
lastRxMsec = millis();
return 0;
}
}
/**
* call getFromRadio() and deliver encapsulated packets to the Stream
*/
void StreamAPI::writeStream()
{
if (canWrite) {
uint32_t len;
do {
// Send every packet we can
len = getFromRadio(txBuf + HEADER_LEN);
emitTxBuffer(len);
} while (len);
}
}
int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen)
{
uint16_t index = 0;
while (bufLen > index) { // Currently we never want to block
int cInt = buf[index++];
if (cInt < 0)
break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit
// arduino
uint8_t c = (uint8_t)cInt;
// Use the read pointer for a little state machine, first look for framing, then length bytes, then payload
size_t ptr = rxPtr;
rxPtr++; // assume we will probably advance the rxPtr
rxBuf[ptr] = c; // store all bytes (including framing)
// console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c);
if (ptr == 0) { // looking for START1
if (c != START1)
rxPtr = 0; // failed to find framing
} else if (ptr == 1) { // looking for START2
if (c != START2)
rxPtr = 0; // failed to find framing
} else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing
uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing
// console->printf("len %d\n", len);
if (ptr == HEADER_LEN - 1) {
// we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid
// protobuf also)
if (len > MAX_TO_FROM_RADIO_SIZE)
rxPtr = 0; // length is bogus, restart search for framing
}
if (rxPtr != 0) // Is packet still considered 'good'?
if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload?
rxPtr = 0; // start over again on the next packet
// If we didn't just fail the packet and we now have the right # of bytes, parse it
handleToRadio(rxBuf + HEADER_LEN, len);
}
}
}
return 0;
}
/**
* Read any rx chars from the link and call handleToRadio
*/
@@ -165,6 +76,21 @@ int32_t StreamAPI::readStream()
}
}
/**
* call getFromRadio() and deliver encapsulated packets to the Stream
*/
void StreamAPI::writeStream()
{
if (canWrite) {
uint32_t len;
do {
// Send every packet we can
len = getFromRadio(txBuf + HEADER_LEN);
emitTxBuffer(len);
} while (len);
}
}
/**
* Send the current txBuffer over our stream
*/

View File

@@ -50,15 +50,12 @@ class StreamAPI : public PhoneAPI
* phone.
*/
virtual int32_t runOncePart();
virtual int32_t runOncePart(char *buf,uint16_t bufLen);
private:
/**
* Read any rx chars from the link and call handleToRadio
*/
int32_t readStream();
int32_t readStream(char *buf,uint16_t bufLen);
int32_t handleRecStream(char *buf,uint16_t bufLen);
/**
* call getFromRadio() and deliver encapsulated packets to the Stream

View File

@@ -64,12 +64,7 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
in areas not already covered by other routers, or to bridge around problematic terrain,
but should not be given priority over other routers in order to avoid unnecessaraily
consuming hops. */
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11,
/* Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node. */
meshtastic_Config_DeviceConfig_Role_CLIENT_BASE = 12
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11
} meshtastic_Config_DeviceConfig_Role;
/* Defines the device's behavior for how messages are rebroadcast */
@@ -651,8 +646,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_Config_DeviceConfig_Role_MIN meshtastic_Config_DeviceConfig_Role_CLIENT
#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_CLIENT_BASE
#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_CLIENT_BASE+1))
#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_ROUTER_LATE
#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_ROUTER_LATE+1))
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY

View File

@@ -66,8 +66,6 @@ typedef enum _meshtastic_Language {
meshtastic_Language_UKRAINIAN = 16,
/* Bulgarian */
meshtastic_Language_BULGARIAN = 17,
/* Czech */
meshtastic_Language_CZECH = 18,
/* Simplified Chinese (experimental) */
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
/* Traditional Chinese (experimental) */

View File

@@ -360,7 +360,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
/* Maximum encoded size of messages (where known) */
/* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
#define meshtastic_BackupPreferences_size 2273
#define meshtastic_BackupPreferences_size 2271
#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1737
#define meshtastic_NodeInfoLite_size 196

View File

@@ -188,7 +188,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
#define meshtastic_LocalConfig_size 747
#define meshtastic_LocalModuleConfig_size 671
#define meshtastic_LocalModuleConfig_size 669
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -272,8 +272,6 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_HELTEC_MESH_SOLAR = 108,
/* Lilygo T-Echo Lite */
meshtastic_HardwareModel_T_ECHO_LITE = 109,
/* New Heltec LoRA32 with ESP32-S3 CPU */
meshtastic_HardwareModel_HELTEC_V4 = 110,
/* ------------------------------------------------------------------------------------------------------------------------------------------
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.
------------------------------------------------------------------------------------------------------------------------------------------ */

View File

@@ -317,9 +317,6 @@ typedef struct _meshtastic_ModuleConfig_RangeTestConfig {
/* Bool value indicating that this node should save a RangeTest.csv file.
ESP32 Only */
bool save;
/* Bool indicating that the node should cleanup / destroy it's RangeTest.csv file.
ESP32 Only */
bool clear_on_reboot;
} meshtastic_ModuleConfig_RangeTestConfig;
/* Configuration for both device and environment metrics */
@@ -522,7 +519,7 @@ extern "C" {
#define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
@@ -538,7 +535,7 @@ extern "C" {
#define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0}
@@ -613,7 +610,6 @@ extern "C" {
#define meshtastic_ModuleConfig_RangeTestConfig_enabled_tag 1
#define meshtastic_ModuleConfig_RangeTestConfig_sender_tag 2
#define meshtastic_ModuleConfig_RangeTestConfig_save_tag 3
#define meshtastic_ModuleConfig_RangeTestConfig_clear_on_reboot_tag 4
#define meshtastic_ModuleConfig_TelemetryConfig_device_update_interval_tag 1
#define meshtastic_ModuleConfig_TelemetryConfig_environment_update_interval_tag 2
#define meshtastic_ModuleConfig_TelemetryConfig_environment_measurement_enabled_tag 3
@@ -807,8 +803,7 @@ X(a, STATIC, SINGULAR, BOOL, is_server, 6)
#define meshtastic_ModuleConfig_RangeTestConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
X(a, STATIC, SINGULAR, UINT32, sender, 2) \
X(a, STATIC, SINGULAR, BOOL, save, 3) \
X(a, STATIC, SINGULAR, BOOL, clear_on_reboot, 4)
X(a, STATIC, SINGULAR, BOOL, save, 3)
#define meshtastic_ModuleConfig_RangeTestConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_RangeTestConfig_DEFAULT NULL
@@ -906,7 +901,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_MapReportSettings_size 14
#define meshtastic_ModuleConfig_NeighborInfoConfig_size 10
#define meshtastic_ModuleConfig_PaxcounterConfig_size 30
#define meshtastic_ModuleConfig_RangeTestConfig_size 12
#define meshtastic_ModuleConfig_RangeTestConfig_size 10
#define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
#define meshtastic_ModuleConfig_SerialConfig_size 28
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24

View File

@@ -292,14 +292,11 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels)
JSONObject thisFileMap;
thisFileMap["size"] = new JSONValue((int)file.size());
#ifdef ARCH_ESP32
String fileName = String(file.path()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str());
#else
String fileName = String(file.name()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str());
#endif
String tempName = String(file.name()).substring(1);
if (tempName.endsWith(".gz")) {
if (String(file.name()).substring(1).endsWith(".gz")) {
#ifdef ARCH_ESP32
String modifiedFile = String(file.path()).substring(1);
#else
@@ -342,15 +339,9 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
// Clean up the fileList to prevent memory leak
for (auto *val : fileList) {
delete val;
}
}
void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
@@ -371,8 +362,7 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
return;
} else {
@@ -381,8 +371,7 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("Error");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
return;
}
@@ -621,35 +610,33 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
res->println("<pre>");
}
// Helper lambda to create JSON array and clean up memory properly
auto createJSONArrayFromLog = [](uint32_t *logArray, int count) -> JSONValue * {
JSONArray tempArray;
for (int i = 0; i < count; i++) {
tempArray.push_back(new JSONValue((int)logArray[i]));
}
JSONValue *result = new JSONValue(tempArray);
// Note: Don't delete tempArray elements here - JSONValue now owns them
return result;
};
// data->airtime->tx_log
JSONArray txLogValues;
uint32_t *logArray;
logArray = airTime->airtimeReport(TX_LOG);
JSONValue *txLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
txLogValues.push_back(new JSONValue((int)logArray[i]));
}
// data->airtime->rx_log
JSONArray rxLogValues;
logArray = airTime->airtimeReport(RX_LOG);
JSONValue *rxLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
rxLogValues.push_back(new JSONValue((int)logArray[i]));
}
// data->airtime->rx_all_log
JSONArray rxAllLogValues;
logArray = airTime->airtimeReport(RX_ALL_LOG);
JSONValue *rxAllLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
rxAllLogValues.push_back(new JSONValue((int)logArray[i]));
}
// data->airtime
JSONObject jsonObjAirtime;
jsonObjAirtime["tx_log"] = txLogJsonValue;
jsonObjAirtime["rx_log"] = rxLogJsonValue;
jsonObjAirtime["rx_all_log"] = rxAllLogJsonValue;
jsonObjAirtime["tx_log"] = new JSONValue(txLogValues);
jsonObjAirtime["rx_log"] = new JSONValue(rxLogValues);
jsonObjAirtime["rx_all_log"] = new JSONValue(rxAllLogValues);
jsonObjAirtime["channel_utilization"] = new JSONValue(airTime->channelUtilizationPercent());
jsonObjAirtime["utilization_tx"] = new JSONValue(airTime->utilizationTXPercent());
jsonObjAirtime["seconds_since_boot"] = new JSONValue(int(airTime->getSecondsSinceBoot()));
@@ -659,9 +646,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
// data->wifi
JSONObject jsonObjWifi;
jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
String wifiIPString = WiFi.localIP().toString();
std::string wifiIP = wifiIPString.c_str();
jsonObjWifi["ip"] = new JSONValue(wifiIP.c_str());
jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str());
// data->memory
JSONObject jsonObjMemory;
@@ -707,8 +692,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
}
@@ -779,14 +763,8 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
// Clean up the nodesArray to prevent memory leak
for (auto *val : nodesArray) {
delete val;
}
}
/*
@@ -933,8 +911,7 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
}
@@ -976,13 +953,7 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
// Clean up the networkObjs to prevent memory leak
for (auto *val : networkObjs) {
delete val;
}
}
#endif

View File

@@ -15,27 +15,8 @@
// FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in
// RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
#ifndef MAX_RX_TOPHONE
#if defined(ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3))
#define MAX_RX_TOPHONE 8
#else
#define MAX_RX_TOPHONE 32
#endif
#endif
/// max number of QueueStatus packets which can be waiting for delivery to phone
#ifndef MAX_RX_QUEUESTATUS_TOPHONE
#define MAX_RX_QUEUESTATUS_TOPHONE 2
#endif
/// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone
#ifndef MAX_RX_MQTTPROXY_TOPHONE
#define MAX_RX_MQTTPROXY_TOPHONE 8
#endif
/// max number of ClientNotification packets which can be waiting for delivery to phone
#ifndef MAX_RX_NOTIFICATION_TOPHONE
#define MAX_RX_NOTIFICATION_TOPHONE 2
#endif
/// Verify baseline assumption of node size. If it increases, we need to reevaluate
/// the impact of its memory footprint, notably on MAX_NUM_NODES.

View File

@@ -505,9 +505,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
if (mp.decoded.want_response && !myReply) {
myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp);
}
if (mp.pki_encrypted && myReply) {
myReply->pki_encrypted = true;
}
return handled;
}
@@ -720,13 +718,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
requiresReboot = false;
}
#if defined(ARCH_PORTDUINO)
// If running on portduino and using SimRadio, do not require reboot
if (SimRadio::instance) {
requiresReboot = false;
}
#endif
#ifdef RF95_FAN_EN
// Turn PA off if disabled by config
if (c.payload_variant.lora.pa_fan_disabled) {
@@ -943,9 +934,6 @@ void AdminModule::handleGetOwner(const meshtastic_MeshPacket &req)
res.which_payload_variant = meshtastic_AdminMessage_get_owner_response_tag;
setPassKey(&res);
myReply = allocDataProtobuf(res);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
}
}
@@ -1017,9 +1005,6 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32
res.which_payload_variant = meshtastic_AdminMessage_get_config_response_tag;
setPassKey(&res);
myReply = allocDataProtobuf(res);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
}
}
@@ -1107,9 +1092,6 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
res.which_payload_variant = meshtastic_AdminMessage_get_module_config_response_tag;
setPassKey(&res);
myReply = allocDataProtobuf(res);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
}
}
@@ -1134,9 +1116,6 @@ void AdminModule::handleGetNodeRemoteHardwarePins(const meshtastic_MeshPacket &r
}
setPassKey(&r);
myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
}
void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req)
@@ -1146,9 +1125,6 @@ void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req)
r.which_payload_variant = meshtastic_AdminMessage_get_device_metadata_response_tag;
setPassKey(&r);
myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
}
void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &req)
@@ -1217,9 +1193,6 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
r.which_payload_variant = meshtastic_AdminMessage_get_device_connection_status_response_tag;
setPassKey(&r);
myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
}
void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t channelIndex)
@@ -1231,9 +1204,6 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch
r.which_payload_variant = meshtastic_AdminMessage_get_channel_response_tag;
setPassKey(&r);
myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
}
}
@@ -1243,9 +1213,6 @@ void AdminModule::handleGetDeviceUIConfig(const meshtastic_MeshPacket &req)
r.which_payload_variant = meshtastic_AdminMessage_get_ui_config_response_tag;
r.get_ui_config_response = uiconfig;
myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
}
void AdminModule::reboot(int32_t seconds)

View File

@@ -632,10 +632,10 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo
// Normal canned message selection
if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) {
} else {
#if CANNED_MESSAGE_ADD_CONFIRMATION
// Show confirmation dialog before sending canned message
NodeNum destNode = dest;
ChannelIndex chan = channel;
#if CANNED_MESSAGE_ADD_CONFIRMATION
graphics::menuHandler::showConfirmationBanner("Send message?", [this, destNode, chan, current]() {
this->sendText(destNode, chan, current, false);
payload = runState;
@@ -991,6 +991,7 @@ int32_t CannedMessageModule::runOnce()
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
}
}
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
this->currentMessageIndex = -1;
this->freetext = "";
this->cursor = 0;

View File

@@ -364,10 +364,9 @@ ExternalNotificationModule::ExternalNotificationModule()
// moduleConfig.external_notification.alert_message_buzzer = true;
if (moduleConfig.external_notification.enabled) {
#if !defined(MESHTASTIC_EXCLUDE_INPUTBROKER)
if (inputBroker) // put our callback in the inputObserver list
inputObserver.observe(inputBroker);
#endif
if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig),
&meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) {
memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone));

View File

@@ -3,15 +3,11 @@
#include "buzz/BuzzerFeedbackThread.h"
#include "input/ExpressLRSFiveWay.h"
#include "input/InputBroker.h"
#include "input/RotaryEncoderImpl.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/SerialKeyboardImpl.h"
#include "input/TrackballInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#include "modules/SystemCommandsModule.h"
#if HAS_TRACKBALL
#include "input/TrackballInterruptImpl1.h"
#endif
#if !MESHTASTIC_EXCLUDE_I2C
#include "input/cardKbI2cImpl.h"
#endif
@@ -91,7 +87,7 @@
#include "modules/StoreForwardModule.h"
#endif
#endif
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
#include "modules/ExternalNotificationModule.h"
#endif
@@ -101,6 +97,7 @@
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_SERIAL
#include "modules/SerialModule.h"
#endif
#endif
#if !MESHTASTIC_EXCLUDE_DROPZONE
#include "modules/DropzoneModule.h"
@@ -138,20 +135,13 @@ void setupModules()
traceRouteModule = new TraceRouteModule();
#endif
#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) {
neighborInfoModule = new NeighborInfoModule();
}
neighborInfoModule = new NeighborInfoModule();
#endif
#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
detectionSensorModule = new DetectionSensorModule();
}
detectionSensorModule = new DetectionSensorModule();
#endif
#if !MESHTASTIC_EXCLUDE_ATAK
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK,
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
atakPluginModule = new AtakPluginModule();
}
atakPluginModule = new AtakPluginModule();
#endif
#if !MESHTASTIC_EXCLUDE_PKI
keyVerificationModule = new KeyVerificationModule();
@@ -180,20 +170,11 @@ void setupModules()
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
#ifdef T_LORA_PAGER
// use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) {
delete rotaryEncoderImpl;
rotaryEncoderImpl = nullptr;
}
#else
upDownInterruptImpl1 = new UpDownInterruptImpl1();
if (!upDownInterruptImpl1->init()) {
delete upDownInterruptImpl1;
upDownInterruptImpl1 = nullptr;
}
#endif
cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init();
#ifdef INPUTBROKER_MATRIX_TYPE
@@ -217,7 +198,7 @@ void setupModules()
aLinuxInputImpl->init();
}
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
trackballInterruptImpl1 = new TrackballInterruptImpl1();
trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
@@ -237,14 +218,11 @@ void setupModules()
#if HAS_TELEMETRY
new DeviceTelemetryModule();
#endif
// TODO: How to improve this?
#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
new EnvironmentTelemetryModule();
}
new EnvironmentTelemetryModule();
#if __has_include("Adafruit_PM25AQI.h")
if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
new AirQualityTelemetryModule();
}
#endif
@@ -256,16 +234,12 @@ void setupModules()
#endif
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) {
new PowerTelemetryModule();
}
new PowerTelemetryModule();
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
!defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
!defined(CONFIG_IDF_TARGET_ESP32C3)
#if !MESHTASTIC_EXCLUDE_SERIAL
if (moduleConfig.has_serial && moduleConfig.serial.enabled &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
new SerialModule();
}
#endif
@@ -276,26 +250,21 @@ void setupModules()
audioModule = new AudioModule();
#endif
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) {
paxcounterModule = new PaxcounterModule();
}
paxcounterModule = new PaxcounterModule();
#endif
#endif
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) {
storeForwardModule = new StoreForwardModule();
}
storeForwardModule = new StoreForwardModule();
#endif
#endif
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
if (moduleConfig.has_external_notification && moduleConfig.external_notification.enabled) {
externalNotificationModule = new ExternalNotificationModule();
}
externalNotificationModule = new ExternalNotificationModule();
#endif
#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
new RangeTestModule();
new RangeTestModule();
#endif
#endif
} else {
#if !MESHTASTIC_EXCLUDE_ADMIN

View File

@@ -105,15 +105,14 @@ void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies)
{
meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
collectNeighborInfo(&neighborInfo);
// only send neighbours if we have some to send
if (neighborInfo.neighbors_count > 0) {
meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo);
p->to = dest;
p->decoded.want_response = wantReplies;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
printNeighborInfo("SENDING", &neighborInfo);
service->sendToMesh(p, RX_SRC_LOCAL, true);
}
meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo);
// send regardless of whether or not we have neighbors in our DB,
// because we want to get neighbors for the next cycle
p->to = dest;
p->decoded.want_response = wantReplies;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
printNeighborInfo("SENDING", &neighborInfo);
service->sendToMesh(p, RX_SRC_LOCAL, true);
}
/*
@@ -215,4 +214,4 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
neighbors.push_back(new_nbr);
}
return &neighbors.back();
}
}

View File

@@ -12,12 +12,12 @@ NodeInfoModule *nodeInfoModule;
bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *pptr)
{
auto p = *pptr;
if (mp.from == nodeDB->getNodeNum()) {
LOG_WARN("Ignoring packet supposed to be from our own node: %08x", mp.from);
return false;
}
auto p = *pptr;
if (p.is_licensed != owner.is_licensed) {
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
return true;
@@ -44,10 +44,7 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal)
service->cancelSending(prevPacketId);
shorterTimeout = _shorterTimeout;
DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p = allocReply();
DEBUG_HEAP_AFTER("NodeInfoModule::sendOurNodeInfo", p);
if (p) { // Check whether we didn't ignore it
p->to = dest;
p->decoded.want_response = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&

View File

@@ -31,7 +31,7 @@ uint32_t packetSequence = 0;
int32_t RangeTestModule::runOnce()
{
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_STM32WL) || defined(ARCH_PORTDUINO)
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_PORTDUINO)
/*
Uncomment the preferences below if you want to use the module
@@ -130,7 +130,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket &mp)
{
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_STM32WL) || defined(ARCH_PORTDUINO)
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_PORTDUINO)
if (moduleConfig.range_test.enabled) {

View File

@@ -73,7 +73,7 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit
return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit
}
RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
RoutingModule::RoutingModule() : ProtobufModule(ROUTING_MODULE, meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
{
isPromiscuous = true;

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