mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-04 03:18:10 +10:00
Compare commits
93
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f47ca1f32 | ||
|
|
6a27e62bcf | ||
|
|
2d5a6c1a20 | ||
|
|
c7839b469b | ||
|
|
95967a01b8 | ||
|
|
e16689a0d6 | ||
|
|
c80098f517 | ||
|
|
1f766a04aa | ||
|
|
1d31be939f | ||
|
|
4b4bd07d5c | ||
|
|
cf4753f7fd | ||
|
|
892223a297 | ||
|
|
658ed6fd28 | ||
|
|
3a8f623f8a | ||
|
|
f09e5c96fc | ||
|
|
a493ab526f | ||
|
|
b3ec3c20fb | ||
|
|
b65b9e5d65 | ||
|
|
766beefbc5 | ||
|
|
eb372c190e | ||
|
|
70df36b5db | ||
|
|
e33d014257 | ||
|
|
26691c0be7 | ||
|
|
09e08e0091 | ||
|
|
73c77b663c | ||
|
|
fb4faf790b | ||
|
|
cb7407e06b | ||
|
|
b45a912409 | ||
|
|
c7d5698dbc | ||
|
|
d1a25947e3 | ||
|
|
69dcc948b9 | ||
|
|
084b01715e | ||
|
|
af9d14c370 | ||
|
|
1032e16ea4 | ||
|
|
3da1b74a10 | ||
|
|
c0a3b20aa3 | ||
|
|
3daae24d29 | ||
|
|
dced888492 | ||
|
|
7167f1e04f | ||
|
|
dfbb4cd913 | ||
|
|
3da7c0dba7 | ||
|
|
7b70324435 | ||
|
|
94eb837ee8 | ||
|
|
a9c07a4c01 | ||
|
|
e232e3462c | ||
|
|
94794edd43 | ||
|
|
95b6f27d2a | ||
|
|
efd818fe90 | ||
|
|
576f582cd9 | ||
|
|
d5c11d1892 | ||
|
|
aaa5d61162 | ||
|
|
3efd606ea7 | ||
|
|
42286edc81 | ||
|
|
29335a18f5 | ||
|
|
51df4fc775 | ||
|
|
0f1bc98305 | ||
|
|
23926210d1 | ||
|
|
7275c21f6b | ||
|
|
ac89bb3387 | ||
|
|
07da130586 | ||
|
|
5d4d91f775 | ||
|
|
7da1153c2c | ||
|
|
585805c3b9 | ||
|
|
a4830e0ab1 | ||
|
|
763ae9f2e2 | ||
|
|
7f12505716 | ||
|
|
b4940b476d | ||
|
|
c860493e68 | ||
|
|
2dd751e339 | ||
|
|
bfce3938d2 | ||
|
|
46ad623785 | ||
|
|
e174328de3 | ||
|
|
9d37a8d17f | ||
|
|
f5ff77c2b9 | ||
|
|
72050530f1 | ||
|
|
e5bf07d4fb | ||
|
|
7ab9a94edb | ||
|
|
3c3d391044 | ||
|
|
e3063a2785 | ||
|
|
6dbb6583ef | ||
|
|
9b3e519487 | ||
|
|
495840c777 | ||
|
|
5865add857 | ||
|
|
c659292836 | ||
|
|
905718e2ac | ||
|
|
a58348369d | ||
|
|
d20fa6e927 | ||
|
|
bf88773b6b | ||
|
|
6acc63729b | ||
|
|
7aee014f5e | ||
|
|
2786db499d | ||
|
|
4ffb906fe8 | ||
|
|
f7758b4e44 |
@@ -35,6 +35,7 @@ jobs:
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
|
||||
|
||||
- name: Build ESP32
|
||||
run: bin/build-esp32.sh ${{ inputs.board }}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Build ESP32-C3
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
board:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-esp32-c3:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
|
||||
- name: Pull web ui
|
||||
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
|
||||
with:
|
||||
repo: meshtastic/web
|
||||
file: build.tar
|
||||
target: build.tar
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Unpack web ui
|
||||
run: |
|
||||
tar -xf build.tar -C data/static
|
||||
rm build.tar
|
||||
- name: Remove debug flags for release
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
run: |
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
|
||||
- name: Build ESP32
|
||||
run: bin/build-esp32.sh ${{ inputs.board }}
|
||||
|
||||
- name: Pull OTA Firmware
|
||||
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
|
||||
with:
|
||||
repo: meshtastic/firmware-ota
|
||||
file: firmware-c3.bin
|
||||
target: release/bleota-c3.bin
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
shell: bash
|
||||
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
|
||||
path: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
@@ -34,6 +34,7 @@ jobs:
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
|
||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
|
||||
- name: Build ESP32
|
||||
run: bin/build-esp32.sh ${{ inputs.board }}
|
||||
|
||||
|
||||
@@ -67,7 +67,6 @@ jobs:
|
||||
- board: tlora-v2-1-1_6-tcxo
|
||||
- board: tlora-v2-1-1_8
|
||||
- board: tbeam
|
||||
- board: heltec-ht62-esp32c3-sx1262
|
||||
- board: heltec-v2_0
|
||||
- board: heltec-v2_1
|
||||
- board: tbeam0_7
|
||||
@@ -93,7 +92,8 @@ jobs:
|
||||
- board: heltec-wsl-v3
|
||||
- board: heltec-wireless-tracker
|
||||
- board: heltec-wireless-tracker-V1-0
|
||||
- board: heltec-wireless-paper
|
||||
- board: heltec-wireless-paper-v1_0
|
||||
- board: heltec-wireless-paper #v1.1
|
||||
- board: tbeam-s3-core
|
||||
- board: tlora-t3s3-v1
|
||||
- board: t-watch-s3
|
||||
@@ -104,6 +104,16 @@ jobs:
|
||||
with:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
build-esp32-c3:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- board: heltec-ht62-esp32c3-sx1262
|
||||
uses: ./.github/workflows/build_esp32_c3.yml
|
||||
with:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
build-nrf52:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -225,6 +235,7 @@ jobs:
|
||||
[
|
||||
build-esp32,
|
||||
build-esp32-s3,
|
||||
build-esp32-c3,
|
||||
build-nrf52,
|
||||
build-raspbian,
|
||||
build-native,
|
||||
@@ -250,7 +261,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Move files up
|
||||
run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml
|
||||
run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -23,6 +23,14 @@ jobs:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Pull web ui
|
||||
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
|
||||
with:
|
||||
repo: meshtastic/web
|
||||
file: build.tar
|
||||
target: build.tar
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
@@ -37,9 +45,12 @@ jobs:
|
||||
|
||||
- name: build .debpkg
|
||||
run: |
|
||||
mkdir -p .debpkg/usr/share/doc/meshtasticd/web
|
||||
mkdir -p .debpkg/usr/sbin
|
||||
mkdir -p .debpkg/etc/meshtasticd
|
||||
mkdir -p .debpkg/usr/lib/systemd/system/
|
||||
tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
|
||||
gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
|
||||
cp release/meshtasticd_linux_aarch64 .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
|
||||
chmod +x .debpkg/usr/sbin/meshtasticd
|
||||
@@ -52,7 +63,7 @@ jobs:
|
||||
maintainer: Jonathan Bennett
|
||||
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
|
||||
arch: arm64
|
||||
depends: libyaml-cpp0.7
|
||||
depends: libyaml-cpp0.7, openssl
|
||||
desc: Native Linux Meshtastic binary.
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -31,3 +31,5 @@ venv/
|
||||
release/
|
||||
.vscode/extensions.json
|
||||
/compile_commands.json
|
||||
src/mesh/raspihttp/certificate.pem
|
||||
src/mesh/raspihttp/private_key.pem
|
||||
+5
-5
@@ -4,19 +4,19 @@ cli:
|
||||
plugins:
|
||||
sources:
|
||||
- id: trunk
|
||||
ref: v1.4.3
|
||||
ref: v1.4.4
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- trufflehog@3.68.2
|
||||
- trufflehog@3.68.5
|
||||
- yamllint@1.35.1
|
||||
- bandit@1.7.7
|
||||
- checkov@3.2.26
|
||||
- terrascan@1.18.11
|
||||
- checkov@3.2.32
|
||||
- terrascan@1.19.1
|
||||
- trivy@0.49.1
|
||||
#- trufflehog@3.63.2-rc0
|
||||
- taplo@0.8.1
|
||||
- ruff@0.2.2
|
||||
- ruff@0.3.1
|
||||
- isort@5.13.2
|
||||
- markdownlint@0.39.0
|
||||
- oxipng@9.0.0
|
||||
|
||||
Vendored
+3
-1
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "trunk.io",
|
||||
"trunk.enableWindows": true
|
||||
"trunk.enableWindows": true,
|
||||
"files.insertFinalNewline": false,
|
||||
"files.trimFinalNewlines": false
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ extends = arduino_base
|
||||
platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch.
|
||||
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2040> -<mesh/eth/>
|
||||
${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2040> -<mesh/eth/> -<mesh/raspihttp>
|
||||
|
||||
upload_speed = 921600
|
||||
debug_init_break = tbreak setup
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
extends = esp32_base
|
||||
|
||||
build_src_filter =
|
||||
${esp32_base.build_src_filter} -<nimble/>
|
||||
${esp32_base.build_src_filter} -<nimble/> -<mesh/raspihttp>
|
||||
|
||||
monitor_speed = 115200
|
||||
|
||||
@@ -12,5 +12,4 @@ build_flags =
|
||||
|
||||
lib_ignore =
|
||||
${esp32_base.lib_ignore}
|
||||
NimBLE-Arduino
|
||||
|
||||
NimBLE-Arduino
|
||||
@@ -11,7 +11,7 @@ build_flags =
|
||||
-Isrc/platform/nrf52
|
||||
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2040> -<mesh/eth/>
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2040> -<mesh/eth/> -<mesh/raspihttp>
|
||||
|
||||
lib_deps=
|
||||
${arduino_base.lib_deps}
|
||||
|
||||
@@ -12,6 +12,7 @@ build_src_filter =
|
||||
-<platform/rp2040>
|
||||
-<mesh/wifi/>
|
||||
-<mesh/http/>
|
||||
+<mesh/raspihttp/>
|
||||
-<mesh/eth/>
|
||||
-<modules/esp32>
|
||||
-<modules/Telemetry/EnvironmentTelemetry.cpp>
|
||||
|
||||
@@ -12,7 +12,7 @@ build_flags =
|
||||
-D__PLAT_RP2040__
|
||||
# -D _POSIX_THREADS
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<modules/esp32> -<platform/nrf52/> -<platform/stm32wl> -<mesh/eth/> -<mesh/wifi/> -<mesh/http/>
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<modules/esp32> -<platform/nrf52/> -<platform/stm32wl> -<mesh/eth/> -<mesh/wifi/> -<mesh/http/> -<mesh/raspihttp>
|
||||
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
|
||||
@@ -13,7 +13,7 @@ build_flags =
|
||||
-DVECT_TAB_OFFSET=0x08000000
|
||||
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/Telemetry> -<platform/nrf52> -<platform/portduino> -<platform/rp2040>
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/Telemetry> -<platform/nrf52> -<platform/portduino> -<platform/rp2040> -<mesh/raspihttp>
|
||||
|
||||
board_upload.offset_address = 0x08000000
|
||||
upload_protocol = stlink
|
||||
|
||||
@@ -117,3 +117,7 @@ Input:
|
||||
|
||||
Logging:
|
||||
LogLevel: info # debug, info, warn, error
|
||||
|
||||
Webserver:
|
||||
# Port: 443 # Port for Webserver & Webservices
|
||||
# RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer
|
||||
|
||||
@@ -31,9 +31,13 @@ IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
|
||||
%PYTHON% -m esptool --baud 115200 erase_flash
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME%
|
||||
|
||||
@REM Account for S3 board's different OTA partition
|
||||
@REM Account for S3 and C3 board's different OTA partition
|
||||
IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% (
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin
|
||||
IF x%FILENAME:esp32c3=%==x%FILENAME% (
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin
|
||||
) else (
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-c3.bin
|
||||
)
|
||||
) else (
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-s3.bin
|
||||
)
|
||||
|
||||
+36
-30
@@ -1,12 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
|
||||
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
|
||||
|
||||
set -e
|
||||
|
||||
# Usage info
|
||||
show_help() {
|
||||
cat << EOF
|
||||
cat <<EOF
|
||||
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME|FILENAME]
|
||||
Flash image file to device, but first erasing and writing system information"
|
||||
|
||||
@@ -18,44 +18,50 @@ Flash image file to device, but first erasing and writing system information"
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
while getopts ":hp:P:f:" opt; do
|
||||
case "${opt}" in
|
||||
h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
p) export ESPTOOL_PORT=${OPTARG}
|
||||
;;
|
||||
P) PYTHON=${OPTARG}
|
||||
;;
|
||||
f) FILENAME=${OPTARG}
|
||||
;;
|
||||
*)
|
||||
echo "Invalid flag."
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case "${opt}" in
|
||||
h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
p)
|
||||
export ESPTOOL_PORT=${OPTARG}
|
||||
;;
|
||||
P)
|
||||
PYTHON=${OPTARG}
|
||||
;;
|
||||
f)
|
||||
FILENAME=${OPTARG}
|
||||
;;
|
||||
*)
|
||||
echo "Invalid flag."
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift "$((OPTIND-1))"
|
||||
shift "$((OPTIND - 1))"
|
||||
|
||||
[ -z "$FILENAME" -a -n "$1" ] && {
|
||||
FILENAME=$1
|
||||
shift
|
||||
FILENAME=$1
|
||||
shift
|
||||
}
|
||||
|
||||
if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then
|
||||
if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||
"$PYTHON" -m esptool erase_flash
|
||||
"$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
|
||||
"$PYTHON" -m esptool erase_flash
|
||||
"$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
|
||||
# Account for S3 board's different OTA partition
|
||||
if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then
|
||||
"$PYTHON" -m esptool write_flash 0x260000 bleota.bin
|
||||
if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ]; then
|
||||
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
|
||||
"$PYTHON" -m esptool write_flash 0x260000 bleota.bin
|
||||
else
|
||||
"$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin
|
||||
fi
|
||||
else
|
||||
"$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin
|
||||
"$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin
|
||||
fi
|
||||
"$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin
|
||||
"$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin
|
||||
|
||||
else
|
||||
show_help
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_CANARY -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4405"]],
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x009F"]
|
||||
],
|
||||
"usb_product": "CanaryOne",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "canaryone",
|
||||
|
||||
@@ -116,6 +116,7 @@ lib_deps =
|
||||
adafruit/Adafruit BusIO@^1.11.4
|
||||
adafruit/Adafruit Unified Sensor@^1.1.11
|
||||
adafruit/Adafruit BMP280 Library@^2.6.8
|
||||
adafruit/Adafruit BMP085 Library@^1.2.4
|
||||
adafruit/Adafruit BME280 Library@^2.2.2
|
||||
https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.5.2400
|
||||
boschsensortec/BME68x Sensor Library@^1.1.40407
|
||||
|
||||
+1
-1
Submodule protobufs updated: 5241583565...00332412b2
@@ -103,12 +103,21 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
#endif
|
||||
|
||||
struct bma423_axes_remap remap_data;
|
||||
#ifdef T_WATCH_S3
|
||||
remap_data.x_axis = 1;
|
||||
remap_data.x_axis_sign = 0;
|
||||
remap_data.y_axis = 0;
|
||||
remap_data.y_axis_sign = 0;
|
||||
remap_data.z_axis = 2;
|
||||
remap_data.z_axis_sign = 1;
|
||||
#else
|
||||
remap_data.x_axis = 0;
|
||||
remap_data.x_axis_sign = 1;
|
||||
remap_data.y_axis = 1;
|
||||
remap_data.y_axis_sign = 0;
|
||||
remap_data.z_axis = 2;
|
||||
remap_data.z_axis_sign = 1;
|
||||
#endif
|
||||
// Need to raise the wrist function, need to set the correct axis
|
||||
bmaSensor.setRemapAxes(&remap_data);
|
||||
// sensor.enableFeature(BMA423_STEP_CNTR, true);
|
||||
@@ -171,4 +180,4 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
Adafruit_LIS3DH lis;
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -473,11 +473,6 @@ bool Power::setup()
|
||||
|
||||
void Power::shutdown()
|
||||
{
|
||||
screen->setOn(false);
|
||||
#if defined(USE_EINK) && defined(PIN_EINK_EN)
|
||||
digitalWrite(PIN_EINK_EN, LOW); // power off backlight first
|
||||
#endif
|
||||
|
||||
LOG_INFO("Shutting down\n");
|
||||
|
||||
#ifdef HAS_PMU
|
||||
|
||||
+21
-1
@@ -221,4 +221,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef HW_VENDOR
|
||||
#error HW_VENDOR must be defined
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// global switch to turn off all optional modules for a minimzed build
|
||||
#ifdef MESHTASTIC_EXCLUDE_MODULES
|
||||
#define MESHTASTIC_EXCLUDE_AUDIO 1
|
||||
#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
|
||||
#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1
|
||||
#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1
|
||||
#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1
|
||||
#define MESHTASTIC_EXCLUDE_POWER_TELEMETRY 1
|
||||
#define MESHTASTIC_EXCLUDE_RANGETEST 1
|
||||
#define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1
|
||||
#define MESHTASTIC_EXCLUDE_STOREFORWARD 1
|
||||
#define MESHTASTIC_EXCLUDE_ATAK 1
|
||||
#define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1
|
||||
#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1
|
||||
#define MESHTASTIC_EXCLUDE_TRACEROUTE 1
|
||||
#define MESHTASTIC_EXCLUDE_WAYPOINT 1
|
||||
#define MESHTASTIC_EXCLUDE_INPUTBROKER 1
|
||||
#define MESHTASTIC_EXCLUDE_SERIAL 1
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,7 @@ class ScanI2C
|
||||
BME_680,
|
||||
BME_280,
|
||||
BMP_280,
|
||||
BMP_085,
|
||||
INA260,
|
||||
INA219,
|
||||
INA3221,
|
||||
|
||||
@@ -242,6 +242,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
|
||||
LOG_INFO("BME-280 sensor found at address 0x%x\n", (uint8_t)addr.address);
|
||||
type = BME_280;
|
||||
break;
|
||||
case 0x55:
|
||||
LOG_INFO("BMP-085 or BMP-180 sensor found at address 0x%x\n", (uint8_t)addr.address);
|
||||
type = BMP_085;
|
||||
break;
|
||||
default:
|
||||
LOG_INFO("BMP-280 sensor found at address 0x%x\n", (uint8_t)addr.address);
|
||||
type = BMP_280;
|
||||
|
||||
+7
-20
@@ -255,19 +255,6 @@ bool GPS::setup()
|
||||
if (!didSerialInit) {
|
||||
#if !defined(GPS_UC6580)
|
||||
|
||||
#if defined(RAK4630) && defined(PIN_3V3_EN)
|
||||
// If we are using the RAK4630 and we have no other peripherals on the I2C bus or module interest in 3V3_S,
|
||||
// then we can safely set en_gpio turn off power to 3V3 (IO2) to hard sleep the GPS
|
||||
if (rtc_found.port == ScanI2C::DeviceType::NONE && rgb_found.type == ScanI2C::DeviceType::NONE &&
|
||||
accelerometer_found.port == ScanI2C::DeviceType::NONE && !moduleConfig.detection_sensor.enabled &&
|
||||
!moduleConfig.telemetry.air_quality_enabled && !moduleConfig.telemetry.environment_measurement_enabled &&
|
||||
config.power.device_battery_ina_address == 0 && en_gpio == 0) {
|
||||
LOG_DEBUG("Since no problematic peripherals or interested modules were found, setting power save GPS_EN to pin %i\n",
|
||||
PIN_3V3_EN);
|
||||
en_gpio = PIN_3V3_EN;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
LOG_DEBUG("Probing for GPS at %d \n", serialSpeeds[speedSelect]);
|
||||
gnssModel = probe(serialSpeeds[speedSelect]);
|
||||
@@ -489,13 +476,6 @@ bool GPS::setup()
|
||||
}
|
||||
}
|
||||
|
||||
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("Unable to save GNSS module configuration.\n");
|
||||
} else {
|
||||
LOG_INFO("GNSS module configuration saved!\n");
|
||||
}
|
||||
} else {
|
||||
// LOG_INFO("u-blox M10 hardware found.\n");
|
||||
delay(1000);
|
||||
@@ -588,6 +568,13 @@ bool GPS::setup()
|
||||
// BBR will survive a restart, and power off for a while, but modules with small backup
|
||||
// batteries or super caps will not retain the config for a long power off time.
|
||||
}
|
||||
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("Unable to save GNSS module configuration.\n");
|
||||
} else {
|
||||
LOG_INFO("GNSS module configuration saved!\n");
|
||||
}
|
||||
}
|
||||
didSerialInit = true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
#define PI 3.1415926535897932384626433832795
|
||||
#define OLC_CODE_LEN 11
|
||||
#define DEG_CONVERT 180 / PI
|
||||
#define DEG_CONVERT (180 / PI)
|
||||
|
||||
// Helper functions
|
||||
// Raises a number to an exponent, handling negative exponents.
|
||||
|
||||
+56
-378
@@ -2,127 +2,49 @@
|
||||
|
||||
#ifdef USE_EINK
|
||||
#include "EInkDisplay2.h"
|
||||
#include "GxEPD2_BW.h"
|
||||
#include "SPILock.h"
|
||||
#include "main.h"
|
||||
#include <SPI.h>
|
||||
|
||||
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
|
||||
SPIClass *hspi = NULL;
|
||||
#endif
|
||||
/*
|
||||
The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
|
||||
Previously, these macros were defined at the top of this file.
|
||||
|
||||
#define COLORED GxEPD_BLACK
|
||||
#define UNCOLORED GxEPD_WHITE
|
||||
For archival reasons, note that the following configurations had also been tested during this period:
|
||||
* ifdef RAK4631
|
||||
- 4.2 inch
|
||||
EINK_DISPLAY_MODEL: GxEPD2_420_M01
|
||||
EINK_WIDTH: 300
|
||||
EINK_WIDTH: 400
|
||||
|
||||
#if defined(TTGO_T_ECHO)
|
||||
#define TECHO_DISPLAY_MODEL GxEPD2_154_D67
|
||||
#elif defined(RAK4630)
|
||||
- 2.9 inch
|
||||
EINK_DISPLAY_MODEL: GxEPD2_290_T5D
|
||||
EINK_WIDTH: 296
|
||||
EINK_HEIGHT: 128
|
||||
|
||||
// GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122 - changed from GxEPD2_213_B74 - which was not going to give fast refresh
|
||||
// support
|
||||
#define TECHO_DISPLAY_MODEL GxEPD2_213_BN
|
||||
|
||||
// 4.2 inch 300x400 - GxEPD2_420_M01
|
||||
// #define TECHO_DISPLAY_MODEL GxEPD2_420_M01
|
||||
|
||||
// 2.9 inch 296x128 - GxEPD2_290_T5D
|
||||
// #define TECHO_DISPLAY_MODEL GxEPD2_290_T5D
|
||||
|
||||
// 1.54 inch 200x200 - GxEPD2_154_M09
|
||||
// #define TECHO_DISPLAY_MODEL GxEPD2_154_M09
|
||||
|
||||
#elif defined(MAKERPYTHON)
|
||||
// 2.9 inch 296x128 - GxEPD2_290_T5D
|
||||
#define TECHO_DISPLAY_MODEL GxEPD2_290_T5D
|
||||
|
||||
#elif defined(PCA10059)
|
||||
|
||||
// 4.2 inch 300x400 - GxEPD2_420_M01
|
||||
#define TECHO_DISPLAY_MODEL GxEPD2_420_M01
|
||||
|
||||
#elif defined(M5_COREINK)
|
||||
// M5Stack CoreInk
|
||||
// 1.54 inch 200x200 - GxEPD2_154_M09
|
||||
#define TECHO_DISPLAY_MODEL GxEPD2_154_M09
|
||||
|
||||
#elif defined(HELTEC_WIRELESS_PAPER)
|
||||
// #define TECHO_DISPLAY_MODEL GxEPD2_213_T5D
|
||||
#define TECHO_DISPLAY_MODEL GxEPD2_213_FC1
|
||||
|
||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
|
||||
// 2.13" 122x250 - DEPG0213BNS800
|
||||
#define TECHO_DISPLAY_MODEL GxEPD2_213_BN
|
||||
|
||||
#endif
|
||||
|
||||
GxEPD2_BW<TECHO_DISPLAY_MODEL, TECHO_DISPLAY_MODEL::HEIGHT> *adafruitDisplay;
|
||||
- 1.54 inch
|
||||
EINK_DISPLAY_MODEL: GxEPD2_154_M09
|
||||
EINK_WIDTH: 200
|
||||
EINK_HEIGHT: 200
|
||||
*/
|
||||
|
||||
// Constructor
|
||||
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
||||
{
|
||||
#if defined(TTGO_T_ECHO)
|
||||
setGeometry(GEOMETRY_RAWMODE, 200, 200);
|
||||
#elif defined(RAK4630)
|
||||
|
||||
// GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122
|
||||
setGeometry(GEOMETRY_RAWMODE, 250, 122);
|
||||
this->displayBufferSize = 250 * (128 / 8);
|
||||
// GxEPD2_420_M01
|
||||
// setGeometry(GEOMETRY_RAWMODE, 300, 400);
|
||||
|
||||
// GxEPD2_290_T5D
|
||||
// setGeometry(GEOMETRY_RAWMODE, 296, 128);
|
||||
|
||||
// GxEPD2_154_M09
|
||||
// setGeometry(GEOMETRY_RAWMODE, 200, 200);
|
||||
|
||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
|
||||
|
||||
// The display's memory is actually 128px x 250px
|
||||
// Setting the buffersize manually prevents 122/8 truncating to a 15 byte width
|
||||
// (Or something like that..)
|
||||
|
||||
// Set dimensions in OLEDDisplay base class
|
||||
this->geometry = GEOMETRY_RAWMODE;
|
||||
this->displayWidth = 250;
|
||||
this->displayHeight = 122;
|
||||
this->displayBufferSize = 250 * (128 / 8);
|
||||
this->displayWidth = EINK_WIDTH;
|
||||
this->displayHeight = EINK_HEIGHT;
|
||||
|
||||
#elif defined(HELTEC_WIRELESS_PAPER)
|
||||
// GxEPD2_213_BN - 2.13 inch b/w 250x122
|
||||
setGeometry(GEOMETRY_RAWMODE, 250, 122);
|
||||
#elif defined(MAKERPYTHON)
|
||||
// GxEPD2_290_T5D
|
||||
setGeometry(GEOMETRY_RAWMODE, 296, 128);
|
||||
// Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer
|
||||
uint16_t shortSide = min(EINK_WIDTH, EINK_HEIGHT);
|
||||
uint16_t longSide = max(EINK_WIDTH, EINK_HEIGHT);
|
||||
if (shortSide % 8 != 0)
|
||||
shortSide = (shortSide | 7) + 1;
|
||||
|
||||
#elif defined(PCA10059)
|
||||
|
||||
// GxEPD2_420_M01
|
||||
setGeometry(GEOMETRY_RAWMODE, 300, 400);
|
||||
|
||||
#elif defined(M5_COREINK)
|
||||
|
||||
// M5Stack_CoreInk 200x200
|
||||
// 1.54 inch 200x200 - GxEPD2_154_M09
|
||||
setGeometry(GEOMETRY_RAWMODE, EPD_HEIGHT, EPD_WIDTH);
|
||||
#elif defined(my)
|
||||
|
||||
// GxEPD2_290_T5D
|
||||
setGeometry(GEOMETRY_RAWMODE, 296, 128);
|
||||
LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n");
|
||||
|
||||
#elif defined(ESP32_S3_PICO)
|
||||
|
||||
// GxEPD2_290_T94_V2
|
||||
setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT);
|
||||
LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n");
|
||||
|
||||
#endif
|
||||
// setGeometry(GEOMETRY_RAWMODE, 128, 64); // old resolution
|
||||
// setGeometry(GEOMETRY_128_64); // We originally used this because I wasn't sure if rawmode worked - it does
|
||||
this->displayBufferSize = longSide * (shortSide / 8);
|
||||
}
|
||||
|
||||
// FIXME quick hack to limit drawing to a very slow rate
|
||||
uint32_t lastDrawMsec;
|
||||
|
||||
/**
|
||||
* Force a display update if we haven't drawn within the specified msecLimit
|
||||
*/
|
||||
@@ -131,13 +53,6 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
||||
// No need to grab this lock because we are on our own SPI bus
|
||||
// concurrency::LockGuard g(spiLock);
|
||||
|
||||
#if defined(USE_EINK_DYNAMIC_REFRESH)
|
||||
// Decide between full refresh, fast refresh, or skipping the update
|
||||
bool continueUpdate = determineRefreshMode();
|
||||
if (!continueUpdate)
|
||||
return false;
|
||||
#else
|
||||
|
||||
uint32_t now = millis();
|
||||
uint32_t sinceLast = now - lastDrawMsec;
|
||||
|
||||
@@ -146,56 +61,34 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
||||
else
|
||||
return false;
|
||||
|
||||
#endif
|
||||
|
||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
||||
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));
|
||||
adafruitDisplay->drawPixel(x, y, isset ? COLORED : UNCOLORED);
|
||||
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the refresh in GxEPD2
|
||||
LOG_DEBUG("Updating E-Paper... ");
|
||||
|
||||
#if defined(TTGO_T_ECHO)
|
||||
adafruitDisplay->nextPage();
|
||||
#elif defined(RAK4630) || defined(MAKERPYTHON)
|
||||
|
||||
// RAK14000 2.13 inch b/w 250x122 actually now does support fast refresh
|
||||
|
||||
// Full update mode (slow)
|
||||
// adafruitDisplay->display(false); // FIXME, use fast refresh mode
|
||||
|
||||
// Only enable for e-Paper with support for fast updates and comment out above adafruitDisplay->display(false);
|
||||
// 1.54 inch 200x200 - GxEPD2_154_M09
|
||||
// 2.13 inch 250x122 - GxEPD2_213_BN
|
||||
// 2.9 inch 296x128 - GxEPD2_290_T5D
|
||||
// 4.2 inch 300x400 - GxEPD2_420_M01
|
||||
adafruitDisplay->nextPage();
|
||||
|
||||
#elif defined(PCA10059) || defined(M5_COREINK)
|
||||
adafruitDisplay->nextPage();
|
||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
|
||||
adafruitDisplay->nextPage();
|
||||
#elif defined(HELTEC_WIRELESS_PAPER)
|
||||
adafruitDisplay->nextPage();
|
||||
#elif defined(ESP32_S3_PICO)
|
||||
adafruitDisplay->nextPage();
|
||||
#elif defined(PRIVATE_HW) || defined(my)
|
||||
adafruitDisplay->nextPage();
|
||||
#endif
|
||||
// End the update process
|
||||
endUpdate();
|
||||
|
||||
// Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display)
|
||||
adafruitDisplay->hibernate();
|
||||
LOG_DEBUG("done\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// End the update process - virtual method, overriden in derived class
|
||||
void EInkDisplay::endUpdate()
|
||||
{
|
||||
// Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
|
||||
adafruitDisplay->hibernate();
|
||||
}
|
||||
|
||||
// Write the buffer to the display memory
|
||||
void EInkDisplay::display(void)
|
||||
{
|
||||
@@ -203,15 +96,9 @@ void EInkDisplay::display(void)
|
||||
// at least one forceDisplay() keyframe. This prevents flashing when we should the critical
|
||||
// bootscreen (that we want to look nice)
|
||||
|
||||
#ifdef USE_EINK_DYNAMIC_REFRESH
|
||||
lowPriority();
|
||||
forceDisplay();
|
||||
highPriority();
|
||||
#else
|
||||
if (lastDrawMsec) {
|
||||
forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Send a command to the display (low level function)
|
||||
@@ -226,16 +113,11 @@ void EInkDisplay::setDetected(uint8_t detected)
|
||||
(void)detected;
|
||||
}
|
||||
|
||||
// Connect to the display
|
||||
// Connect to the display - variant specific
|
||||
bool EInkDisplay::connect()
|
||||
{
|
||||
LOG_INFO("Doing EInk init\n");
|
||||
|
||||
#ifdef PIN_EINK_PWR_ON
|
||||
pinMode(PIN_EINK_PWR_ON, OUTPUT);
|
||||
digitalWrite(PIN_EINK_PWR_ON, HIGH); // If we need to assert a pin to power external peripherals
|
||||
#endif
|
||||
|
||||
#ifdef PIN_EINK_EN
|
||||
// backlight power, HIGH is backlight on, LOW is off
|
||||
pinMode(PIN_EINK_EN, OUTPUT);
|
||||
@@ -244,9 +126,9 @@ bool EInkDisplay::connect()
|
||||
|
||||
#if defined(TTGO_T_ECHO)
|
||||
{
|
||||
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
||||
|
||||
adafruitDisplay = new GxEPD2_BW<TECHO_DISPLAY_MODEL, TECHO_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
||||
@@ -254,8 +136,8 @@ bool EInkDisplay::connect()
|
||||
#elif defined(RAK4630) || defined(MAKERPYTHON)
|
||||
{
|
||||
if (eink_found) {
|
||||
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<TECHO_DISPLAY_MODEL, TECHO_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
// RAK14000 2.13 inch b/w 250x122 does actually now support fast refresh
|
||||
adafruitDisplay->setRotation(3);
|
||||
@@ -267,7 +149,7 @@ bool EInkDisplay::connect()
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
|
||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER)
|
||||
{
|
||||
// Is this a normal boot, or a wake from deep sleep?
|
||||
esp_sleep_wakeup_cause_t wakeReason = esp_sleep_get_wakeup_cause();
|
||||
@@ -296,243 +178,39 @@ bool EInkDisplay::connect()
|
||||
delay(100);
|
||||
|
||||
// Create GxEPD2 objects
|
||||
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
|
||||
adafruitDisplay = new GxEPD2_BW<TECHO_DISPLAY_MODEL, TECHO_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
|
||||
// Init GxEPD2
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(3);
|
||||
}
|
||||
#elif defined(HELTEC_WIRELESS_PAPER)
|
||||
{
|
||||
hspi = new SPIClass(HSPI);
|
||||
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
|
||||
delay(100);
|
||||
pinMode(Vext, OUTPUT);
|
||||
digitalWrite(Vext, LOW);
|
||||
delay(100);
|
||||
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
|
||||
adafruitDisplay = new GxEPD2_BW<TECHO_DISPLAY_MODEL, TECHO_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh)
|
||||
}
|
||||
#elif defined(PCA10059)
|
||||
{
|
||||
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<TECHO_DISPLAY_MODEL, TECHO_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
||||
}
|
||||
#elif defined(M5_COREINK)
|
||||
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<TECHO_DISPLAY_MODEL, TECHO_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
adafruitDisplay->setRotation(0);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||
#elif defined(my) || defined(ESP32_S3_PICO)
|
||||
{
|
||||
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<TECHO_DISPLAY_MODEL, TECHO_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
adafruitDisplay->setRotation(1);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||
}
|
||||
#endif
|
||||
|
||||
// adafruitDisplay->setFullWindow();
|
||||
// adafruitDisplay->fillScreen(UNCOLORED);
|
||||
// adafruitDisplay->drawCircle(100, 100, 20, COLORED);
|
||||
// adafruitDisplay->display(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use a mix of full refresh, fast refresh, and update skipping, to balance urgency and display health
|
||||
#if defined(USE_EINK_DYNAMIC_REFRESH)
|
||||
|
||||
// Suggest that subsequent updates should use fast-refresh
|
||||
void EInkDisplay::highPriority()
|
||||
{
|
||||
isHighPriority = true;
|
||||
}
|
||||
|
||||
// Suggest that subsequent updates should use full-refresh
|
||||
void EInkDisplay::lowPriority()
|
||||
{
|
||||
isHighPriority = false;
|
||||
}
|
||||
|
||||
// Full-refresh is explicitly requested for next one update - no skipping please
|
||||
void EInkDisplay::demandFullRefresh()
|
||||
{
|
||||
demandingFull = true;
|
||||
}
|
||||
|
||||
// configure display for fast-refresh
|
||||
void EInkDisplay::configForFastRefresh()
|
||||
{
|
||||
// Display-specific code can go here
|
||||
#if defined(PRIVATE_HW)
|
||||
#else
|
||||
// Otherwise:
|
||||
adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Configure display for full-refresh
|
||||
void EInkDisplay::configForFullRefresh()
|
||||
{
|
||||
// Display-specific code can go here
|
||||
#if defined(PRIVATE_HW)
|
||||
#else
|
||||
// Otherwise:
|
||||
adafruitDisplay->setFullWindow();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef EINK_FASTREFRESH_ERASURE_LIMIT
|
||||
// Count black pixels in an image. Used for "erasure tracking"
|
||||
int32_t EInkDisplay::countBlackPixels()
|
||||
{
|
||||
int32_t blackCount = 0; // Signed, to avoid underflow when comparing
|
||||
for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
|
||||
for (uint8_t i = 0; i < 7; i++) {
|
||||
// Check if each bit is black or white
|
||||
blackCount += (buffer[b] >> i) & 1;
|
||||
}
|
||||
}
|
||||
return blackCount;
|
||||
}
|
||||
|
||||
// Evaluate the (rough) amount of black->white pixel change since last full refresh
|
||||
bool EInkDisplay::tooManyErasures()
|
||||
{
|
||||
// Ideally, we would compare the new and old buffers, to count *actual* white-to-black pixel changes
|
||||
// but that would require substantially more "code tampering"
|
||||
|
||||
// Get the black pixel stats for this image
|
||||
int32_t blackCount = countBlackPixels();
|
||||
int32_t blackDifference = blackCount - prevBlackCount;
|
||||
|
||||
// Update the running total of "erasures" - black pixels which have become white, since last full-refresh
|
||||
if (blackDifference < 0)
|
||||
erasedSinceFull -= blackDifference;
|
||||
|
||||
// Store black pixel count for next time
|
||||
prevBlackCount = blackCount;
|
||||
|
||||
// Log the running total - help devs setup new boards
|
||||
LOG_DEBUG("Dynamic Refresh: erasedSinceFull=%hu, EINK_FASTREFRESH_ERASURE_LIMIT=%hu\n", erasedSinceFull,
|
||||
EINK_FASTREFRESH_ERASURE_LIMIT);
|
||||
|
||||
// Check if too many pixels have been erased
|
||||
if (erasedSinceFull > EINK_FASTREFRESH_ERASURE_LIMIT)
|
||||
return true; // Too many
|
||||
else
|
||||
return false; // Still okay
|
||||
}
|
||||
#endif // ifdef EINK_FASTREFRESH_ERASURE_LIMIT
|
||||
|
||||
bool EInkDisplay::newImageMatchesOld()
|
||||
{
|
||||
uint32_t newImageHash = 0;
|
||||
|
||||
// Generate hash: sum all bytes in the image buffer
|
||||
for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
|
||||
newImageHash += buffer[b];
|
||||
}
|
||||
|
||||
// Compare hashes
|
||||
bool hashMatches = (newImageHash == prevImageHash);
|
||||
|
||||
// Update the cached hash
|
||||
prevImageHash = newImageHash;
|
||||
|
||||
// Return the comparison result
|
||||
return hashMatches;
|
||||
}
|
||||
|
||||
// Choose between, full-refresh, fast refresh, and update skipping, to balance urgency and display health.
|
||||
bool EInkDisplay::determineRefreshMode()
|
||||
{
|
||||
uint32_t now = millis();
|
||||
uint32_t sinceLast = now - lastUpdateMsec;
|
||||
|
||||
// If rate-limiting dropped a high-priority update:
|
||||
// promote this update, so it runs ASAP
|
||||
if (missedHighPriorityUpdate) {
|
||||
isHighPriority = true;
|
||||
missedHighPriorityUpdate = false;
|
||||
}
|
||||
|
||||
// Abort: if too soon for a new frame (unless demanding full)
|
||||
if (!demandingFull && isHighPriority && fastRefreshCount > 0 && sinceLast < highPriorityLimitMsec) {
|
||||
LOG_DEBUG("Dynamic Refresh: update skipped. Exceeded EINK_HIGHPRIORITY_LIMIT_SECONDS\n");
|
||||
missedHighPriorityUpdate = true;
|
||||
return false;
|
||||
}
|
||||
if (!demandingFull && !isHighPriority && !demandingFull && sinceLast < lowPriorityLimitMsec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If demanded full refresh: give it to them
|
||||
if (demandingFull)
|
||||
needsFull = true;
|
||||
|
||||
// Check if old image (fast-refresh) should be redrawn (as full), for image quality
|
||||
if (fastRefreshCount > 0 && !isHighPriority)
|
||||
needsFull = true;
|
||||
|
||||
// If too many fast updates, require a full-refresh (display health)
|
||||
if (fastRefreshCount >= fastRefreshLimit)
|
||||
needsFull = true;
|
||||
|
||||
#ifdef EINK_FASTREFRESH_ERASURE_LIMIT
|
||||
// Some displays struggle with erasing black pixels to white, during fast-refresh
|
||||
if (tooManyErasures())
|
||||
needsFull = true;
|
||||
#endif
|
||||
|
||||
// If image matches
|
||||
// (Block must run, even if full already selected, to store hash for next time)
|
||||
if (newImageMatchesOld()) {
|
||||
// If low priority: limit rate
|
||||
// otherwise, every loop() will run the hash method
|
||||
if (!isHighPriority)
|
||||
lastUpdateMsec = now;
|
||||
|
||||
// If update is *not* for display health or image quality, skip it
|
||||
if (!needsFull)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Conditions assessed - not skipping - load the appropriate config
|
||||
|
||||
// If options require a full refresh
|
||||
if (!isHighPriority || needsFull) {
|
||||
if (fastRefreshCount > 0)
|
||||
configForFullRefresh();
|
||||
|
||||
LOG_DEBUG("Dynamic Refresh: conditions met for full-refresh\n");
|
||||
fastRefreshCount = 0;
|
||||
needsFull = false;
|
||||
demandingFull = false;
|
||||
erasedSinceFull = 0; // Reset the count for EINK_FASTREFRESH_ERASURE_LIMIT - tracks ghosting buildup
|
||||
}
|
||||
|
||||
// If options allow a fast-refresh
|
||||
else {
|
||||
if (fastRefreshCount == 0)
|
||||
configForFastRefresh();
|
||||
|
||||
LOG_DEBUG("Dynamic Refresh: conditions met for fast-refresh\n");
|
||||
fastRefreshCount++;
|
||||
}
|
||||
|
||||
lastUpdateMsec = now; // Mark time for rate limiting
|
||||
return true; // Instruct calling method to continue with update
|
||||
}
|
||||
|
||||
#endif // End USE_EINK_DYNAMIC_REFRESH
|
||||
|
||||
#endif
|
||||
|
||||
+25
-75
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_EINK
|
||||
|
||||
#include "GxEPD2_BW.h"
|
||||
#include <OLEDDisplay.h>
|
||||
|
||||
#if defined(HELTEC_WIRELESS_PAPER_V1_0)
|
||||
#if defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER)
|
||||
// Re-enable SPI after deep sleep: rtc_gpio_hold_dis()
|
||||
#include "driver/rtc_io.h"
|
||||
#endif
|
||||
@@ -10,12 +13,15 @@
|
||||
/**
|
||||
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
|
||||
*
|
||||
* Note: EInkDynamicDisplay derives from this class.
|
||||
*
|
||||
* Remaining TODO:
|
||||
* optimize display() to only draw changed pixels (see other OLED subclasses for examples)
|
||||
* implement displayOn/displayOff to turn off the TFT device (and backlight)
|
||||
* Use the fast NRF52 SPI API rather than the slow standard arduino version
|
||||
*
|
||||
* turn radio back on - currently with both on spi bus is fucked? or are we leaving chip select asserted?
|
||||
* Suggestion: perhaps similar to HELTEC_WIRELESS_PAPER issue, which resolved with rtc_gpio_hold_dis()
|
||||
*/
|
||||
class EInkDisplay : public OLEDDisplay
|
||||
{
|
||||
@@ -37,7 +43,14 @@ class EInkDisplay : public OLEDDisplay
|
||||
*
|
||||
* @return true if we did draw the screen
|
||||
*/
|
||||
bool forceDisplay(uint32_t msecLimit = 1000);
|
||||
virtual bool forceDisplay(uint32_t msecLimit = 1000);
|
||||
|
||||
/**
|
||||
* Run any code needed to complete an update, after the physical refresh has completed.
|
||||
* Split from forceDisplay(), to enable async refresh in derived EInkDynamicDisplay class.
|
||||
*
|
||||
*/
|
||||
virtual void endUpdate();
|
||||
|
||||
/**
|
||||
* shim to make the abstraction happy
|
||||
@@ -55,80 +68,17 @@ class EInkDisplay : public OLEDDisplay
|
||||
// Connect to the display
|
||||
virtual bool connect() override;
|
||||
|
||||
#if defined(USE_EINK_DYNAMIC_REFRESH)
|
||||
// Full, fast, or skip: balance urgency with display health
|
||||
// AdafruitGFX display object - instantiated in connect(), variant specific
|
||||
GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> *adafruitDisplay = NULL;
|
||||
|
||||
// Use fast refresh if EITHER:
|
||||
// * highPriority() was set
|
||||
// * a highPriority() update was previously skipped, for rate-limiting - (EINK_HIGHPRIORITY_LIMIT_SECONDS)
|
||||
|
||||
// Use full refresh if EITHER:
|
||||
// * lowPriority() was set
|
||||
// * demandFullRefresh() was called - (single shot)
|
||||
// * too many fast updates in a row: protect display - (EINK_FASTREFRESH_REPEAT_LIMIT)
|
||||
// * no recent updates, and last update was fast: redraw for image quality (EINK_LOWPRIORITY_LIMIT_SECONDS)
|
||||
// * (optional) too many "erasures" since full-refresh (black pixels cleared to white)
|
||||
|
||||
// Rate limit if:
|
||||
// * lowPriority() - (EINK_LOWPRIORITY_LIMIT_SECONDS)
|
||||
// * highPriority(), if multiple fast updates have run back-to-back - (EINK_HIGHPRIORITY_LIMIT_SECONDS)
|
||||
|
||||
// Skip update entirely if ALL criteria met:
|
||||
// * new image matches old image
|
||||
// * lowPriority()
|
||||
// * no call to demandFullRefresh()
|
||||
// * not redrawing for image quality
|
||||
// * not refreshing for display health
|
||||
|
||||
// ------------------------------------
|
||||
|
||||
// To implement for your E-Ink display:
|
||||
// * edit configForFastRefresh()
|
||||
// * edit configForFullRefresh()
|
||||
// * add macros to variant.h, and adjust to taste:
|
||||
|
||||
/*
|
||||
#define USE_EINK_DYNAMIC_REFRESH
|
||||
#define EINK_LOWPRIORITY_LIMIT_SECONDS 30
|
||||
#define EINK_HIGHPRIORITY_LIMIT_SECONDS 1
|
||||
#define EINK_FASTREFRESH_REPEAT_LIMIT 5
|
||||
#define EINK_FASTREFRESH_ERASURE_LIMIT 300 // optional
|
||||
*/
|
||||
|
||||
public:
|
||||
void highPriority(); // Suggest fast refresh
|
||||
void lowPriority(); // Suggest full refresh
|
||||
void demandFullRefresh(); // For next update: explicitly request full refresh
|
||||
|
||||
protected:
|
||||
void configForFastRefresh(); // Display specific code to select fast refresh mode
|
||||
void configForFullRefresh(); // Display specific code to return to full refresh mode
|
||||
bool newImageMatchesOld(); // Is the new update actually different to the last image?
|
||||
bool determineRefreshMode(); // Called immediately before data written to display - choose refresh mode, or abort update
|
||||
#ifdef EINK_FASTREFRESH_ERASURE_LIMIT
|
||||
int32_t countBlackPixels(); // Calculate the number of black pixels in the new image
|
||||
bool tooManyErasures(); // Has too much "ghosting" (black pixels erased to white) accumulated since last full-refresh?
|
||||
// If display uses HSPI
|
||||
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
|
||||
SPIClass *hspi = NULL;
|
||||
#endif
|
||||
|
||||
bool isHighPriority = true; // Does the method calling update believe that this is urgent?
|
||||
bool needsFull = false; // Is a full refresh forced? (display health)
|
||||
bool demandingFull = false; // Was full refresh specifically requested? (splash screens, etc)
|
||||
bool missedHighPriorityUpdate = false; // Was a high priority update skipped for rate-limiting?
|
||||
uint16_t fastRefreshCount = 0; // How many fast updates have occurred since last full refresh?
|
||||
uint32_t lastUpdateMsec = 0; // When did the last update occur?
|
||||
uint32_t prevImageHash = 0; // Used to check if update will change screen image (skippable or not)
|
||||
int32_t prevBlackCount = 0; // How many black pixels were in the previous image
|
||||
uint32_t erasedSinceFull = 0; // How many black pixels have been set back to white since last full-refresh? (roughly)
|
||||
|
||||
// Set in variant.h
|
||||
const uint32_t lowPriorityLimitMsec = (uint32_t)1000 * EINK_LOWPRIORITY_LIMIT_SECONDS; // Max rate for fast refreshes
|
||||
const uint32_t highPriorityLimitMsec = (uint32_t)1000 * EINK_HIGHPRIORITY_LIMIT_SECONDS; // Max rate for full refreshes
|
||||
const uint32_t fastRefreshLimit = EINK_FASTREFRESH_REPEAT_LIMIT; // Max consecutive fast updates, before full is triggered
|
||||
|
||||
#else // !USE_EINK_DYNAMIC_REFRESH
|
||||
// Tolerate calls to these methods anywhere, just to be safe
|
||||
void highPriority() {}
|
||||
void lowPriority() {}
|
||||
void demandFullRefresh() {}
|
||||
#endif
|
||||
private:
|
||||
// FIXME quick hack to limit drawing to a very slow rate
|
||||
uint32_t lastDrawMsec = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,500 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
|
||||
#include "EInkDynamicDisplay.h"
|
||||
|
||||
// Constructor
|
||||
EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
||||
: EInkDisplay(address, sda, scl, geometry, i2cBus)
|
||||
{
|
||||
// If tracking ghost pixels, grab memory
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
dirtyPixels = new uint8_t[EInkDisplay::displayBufferSize](); // Init with zeros
|
||||
#endif
|
||||
}
|
||||
|
||||
// Destructor
|
||||
EInkDynamicDisplay::~EInkDynamicDisplay()
|
||||
{
|
||||
// If we were tracking ghost pixels, free the memory
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
delete[] dirtyPixels;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Screen requests a BACKGROUND frame
|
||||
void EInkDynamicDisplay::display()
|
||||
{
|
||||
addFrameFlag(BACKGROUND);
|
||||
update();
|
||||
}
|
||||
|
||||
// Screen requests a RESPONSIVE frame
|
||||
bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit)
|
||||
{
|
||||
addFrameFlag(RESPONSIVE);
|
||||
return update(); // (Unutilized) Base class promises to return true if update ran
|
||||
}
|
||||
|
||||
// Add flag for the next frame
|
||||
void EInkDynamicDisplay::addFrameFlag(frameFlagTypes flag)
|
||||
{
|
||||
// OR the new flag into the existing flags
|
||||
this->frameFlags = (frameFlagTypes)(this->frameFlags | flag);
|
||||
}
|
||||
|
||||
// GxEPD2 code to set fast refresh
|
||||
void EInkDynamicDisplay::configForFastRefresh()
|
||||
{
|
||||
// Variant-specific code can go here
|
||||
#if defined(PRIVATE_HW)
|
||||
#else
|
||||
// Otherwise:
|
||||
adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height());
|
||||
#endif
|
||||
}
|
||||
|
||||
// GxEPD2 code to set full refresh
|
||||
void EInkDynamicDisplay::configForFullRefresh()
|
||||
{
|
||||
// Variant-specific code can go here
|
||||
#if defined(PRIVATE_HW)
|
||||
#else
|
||||
// Otherwise:
|
||||
adafruitDisplay->setFullWindow();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Run any relevant GxEPD2 code, so next update will use correct refresh type
|
||||
void EInkDynamicDisplay::applyRefreshMode()
|
||||
{
|
||||
// Change from FULL to FAST
|
||||
if (currentConfig == FULL && refresh == FAST) {
|
||||
configForFastRefresh();
|
||||
currentConfig = FAST;
|
||||
}
|
||||
|
||||
// Change from FAST back to FULL
|
||||
else if (currentConfig == FAST && refresh == FULL) {
|
||||
configForFullRefresh();
|
||||
currentConfig = FULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Update fastRefreshCount
|
||||
void EInkDynamicDisplay::adjustRefreshCounters()
|
||||
{
|
||||
if (refresh == FAST)
|
||||
fastRefreshCount++;
|
||||
|
||||
else if (refresh == FULL)
|
||||
fastRefreshCount = 0;
|
||||
}
|
||||
|
||||
// Trigger the display update by calling base class
|
||||
bool EInkDynamicDisplay::update()
|
||||
{
|
||||
// Detemine the refresh mode to use, and start the update
|
||||
bool refreshApproved = determineMode();
|
||||
if (refreshApproved) {
|
||||
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
|
||||
storeAndReset(); // Store the result of this loop for next time. Note: call *before* endOrDetach()
|
||||
endOrDetach(); // endUpdate() right now, or set the async refresh flag (if FULL and HAS_EINK_ASYNCFULL)
|
||||
} else
|
||||
storeAndReset(); // No update, no post-update code, just store the results
|
||||
|
||||
return refreshApproved; // (Unutilized) Base class promises to return true if update ran
|
||||
}
|
||||
|
||||
// Figure out who runs the post-update code
|
||||
void EInkDynamicDisplay::endOrDetach()
|
||||
{
|
||||
// If the GxEPD2 version reports that it has the async modifications
|
||||
#ifdef HAS_EINK_ASYNCFULL
|
||||
if (previousRefresh == FULL) {
|
||||
asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop.
|
||||
|
||||
if (previousFrameFlags & BLOCKING)
|
||||
awaitRefresh();
|
||||
else
|
||||
LOG_DEBUG("Async full-refresh begins\n");
|
||||
}
|
||||
|
||||
// Fast Refresh
|
||||
else if (previousRefresh == FAST)
|
||||
EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves.
|
||||
|
||||
// Fallback - If using an unmodified version of GxEPD2 for some reason
|
||||
#else
|
||||
if (previousRefresh == FULL || previousRefresh == FAST) { // If refresh wasn't skipped (on unspecified..)
|
||||
LOG_WARN(
|
||||
"GxEPD2 version has not been modified to support async refresh; using fallback behavior. Please update lib_deps in "
|
||||
"variant's platformio.ini file\n");
|
||||
EInkDisplay::endUpdate();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Assess situation, pick a refresh type
|
||||
bool EInkDynamicDisplay::determineMode()
|
||||
{
|
||||
checkInitialized();
|
||||
checkForPromotion();
|
||||
#if defined(HAS_EINK_ASYNCFULL)
|
||||
checkAsyncFullRefresh();
|
||||
#endif
|
||||
checkRateLimiting();
|
||||
|
||||
// If too soon for a new frame, or display busy, abort early
|
||||
if (refresh == SKIPPED)
|
||||
return false; // No refresh
|
||||
|
||||
// -- New frame is due --
|
||||
|
||||
resetRateLimiting(); // Once determineMode() ends, will have to wait again
|
||||
hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check
|
||||
LOG_DEBUG("determineMode(): "); // Begin log entry
|
||||
|
||||
// Once mode determined, any remaining checks will bypass
|
||||
checkCosmetic();
|
||||
checkDemandingFast();
|
||||
checkFrameMatchesPrevious();
|
||||
checkConsecutiveFastRefreshes();
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
checkExcessiveGhosting();
|
||||
#endif
|
||||
checkFastRequested();
|
||||
|
||||
if (refresh == UNSPECIFIED)
|
||||
LOG_WARN("There was a flaw in the determineMode() logic.\n");
|
||||
|
||||
// -- Decision has been reached --
|
||||
applyRefreshMode();
|
||||
adjustRefreshCounters();
|
||||
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
// Full refresh clears any ghosting
|
||||
if (refresh == FULL)
|
||||
resetGhostPixelTracking();
|
||||
#endif
|
||||
|
||||
// Return - call a refresh or not?
|
||||
if (refresh == SKIPPED)
|
||||
return false; // Don't trigger a refresh
|
||||
else
|
||||
return true; // Do trigger a refresh
|
||||
}
|
||||
|
||||
// Is this the very first frame?
|
||||
void EInkDynamicDisplay::checkInitialized()
|
||||
{
|
||||
if (!initialized) {
|
||||
// Undo GxEPD2_BW::partialWindow(), if set by developer in EInkDisplay::connect()
|
||||
configForFullRefresh();
|
||||
|
||||
// Clear any existing image, so we can draw logo with fast-refresh, but also to set GxEPD2_EPD::_initial_write
|
||||
adafruitDisplay->clearScreen();
|
||||
|
||||
LOG_DEBUG("initialized, ");
|
||||
initialized = true;
|
||||
|
||||
// Use a fast-refresh for the next frame; no skipping or else blank screen when waking from deep sleep
|
||||
addFrameFlag(DEMAND_FAST);
|
||||
}
|
||||
}
|
||||
|
||||
// Was a frame skipped (rate, display busy) that should have been a FAST refresh?
|
||||
void EInkDynamicDisplay::checkForPromotion()
|
||||
{
|
||||
// If a frame was skipped (rate, display busy), then promote a BACKGROUND frame
|
||||
// Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it
|
||||
|
||||
switch (previousReason) {
|
||||
case ASYNC_REFRESH_BLOCKED_DEMANDFAST:
|
||||
addFrameFlag(DEMAND_FAST);
|
||||
break;
|
||||
case ASYNC_REFRESH_BLOCKED_COSMETIC:
|
||||
addFrameFlag(COSMETIC);
|
||||
break;
|
||||
case ASYNC_REFRESH_BLOCKED_RESPONSIVE:
|
||||
case EXCEEDED_RATELIMIT_FAST:
|
||||
addFrameFlag(RESPONSIVE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Is it too soon for another frame of this type?
|
||||
void EInkDynamicDisplay::checkRateLimiting()
|
||||
{
|
||||
uint32_t now = millis();
|
||||
|
||||
// Sanity check: millis() overflow - just let the update run..
|
||||
if (previousRunMs > now)
|
||||
return;
|
||||
|
||||
// Skip update: too soon for BACKGROUND
|
||||
if (frameFlags == BACKGROUND) {
|
||||
if (now - previousRunMs < EINK_LIMIT_RATE_BACKGROUND_SEC * 1000) {
|
||||
refresh = SKIPPED;
|
||||
reason = EXCEEDED_RATELIMIT_FULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No rate-limit for these special cases
|
||||
if (frameFlags & COSMETIC || frameFlags & DEMAND_FAST)
|
||||
return;
|
||||
|
||||
// Skip update: too soon for RESPONSIVE
|
||||
if (frameFlags & RESPONSIVE) {
|
||||
if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
|
||||
refresh = SKIPPED;
|
||||
reason = EXCEEDED_RATELIMIT_FAST;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is this frame COSMETIC (splash screens?)
|
||||
void EInkDynamicDisplay::checkCosmetic()
|
||||
{
|
||||
// If a decision was already reached, don't run the check
|
||||
if (refresh != UNSPECIFIED)
|
||||
return;
|
||||
|
||||
// A full refresh is requested for cosmetic purposes: we have a decision
|
||||
if (frameFlags & COSMETIC) {
|
||||
refresh = FULL;
|
||||
reason = FLAGGED_COSMETIC;
|
||||
LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x\n", frameFlags);
|
||||
}
|
||||
}
|
||||
|
||||
// Is this a one-off special circumstance, where we REALLY want a fast refresh?
|
||||
void EInkDynamicDisplay::checkDemandingFast()
|
||||
{
|
||||
// If a decision was already reached, don't run the check
|
||||
if (refresh != UNSPECIFIED)
|
||||
return;
|
||||
|
||||
// A fast refresh is demanded: we have a decision
|
||||
if (frameFlags & DEMAND_FAST) {
|
||||
refresh = FAST;
|
||||
reason = FLAGGED_DEMAND_FAST;
|
||||
LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x\n", frameFlags);
|
||||
}
|
||||
}
|
||||
|
||||
// Does the new frame match the currently displayed image?
|
||||
void EInkDynamicDisplay::checkFrameMatchesPrevious()
|
||||
{
|
||||
// If a decision was already reached, don't run the check
|
||||
if (refresh != UNSPECIFIED)
|
||||
return;
|
||||
|
||||
// If frame is *not* a duplicate, abort the check
|
||||
if (imageHash != previousImageHash)
|
||||
return;
|
||||
|
||||
#if !defined(EINK_BACKGROUND_USES_FAST)
|
||||
// If BACKGROUND, and last update was FAST: redraw the same image in FULL (for display health + image quality)
|
||||
if (frameFlags == BACKGROUND && fastRefreshCount > 0) {
|
||||
refresh = FULL;
|
||||
reason = REDRAW_WITH_FULL;
|
||||
LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x\n", frameFlags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Not redrawn, not COSMETIC, not DEMAND_FAST
|
||||
refresh = SKIPPED;
|
||||
reason = FRAME_MATCHED_PREVIOUS;
|
||||
LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x\n", frameFlags);
|
||||
}
|
||||
|
||||
// Have too many fast-refreshes occured consecutively, since last full refresh?
|
||||
void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
|
||||
{
|
||||
// If a decision was already reached, don't run the check
|
||||
if (refresh != UNSPECIFIED)
|
||||
return;
|
||||
|
||||
// If too many FAST refreshes consecutively - force a FULL refresh
|
||||
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
|
||||
refresh = FULL;
|
||||
reason = EXCEEDED_LIMIT_FASTREFRESH;
|
||||
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x\n", frameFlags);
|
||||
}
|
||||
}
|
||||
|
||||
// No objections, we can perform fast-refresh, if desired
|
||||
void EInkDynamicDisplay::checkFastRequested()
|
||||
{
|
||||
if (refresh != UNSPECIFIED)
|
||||
return;
|
||||
|
||||
if (frameFlags == BACKGROUND) {
|
||||
#ifdef EINK_BACKGROUND_USES_FAST
|
||||
// If we want BACKGROUND to use fast. (FULL only when a limit is hit)
|
||||
refresh = FAST;
|
||||
reason = BACKGROUND_USES_FAST;
|
||||
LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount,
|
||||
frameFlags);
|
||||
#else
|
||||
// If we do want to use FULL for BACKGROUND updates
|
||||
refresh = FULL;
|
||||
reason = FLAGGED_BACKGROUND;
|
||||
LOG_DEBUG("refresh=FULL, reason=FLAGGED_BACKGROUND\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Sanity: confirm that we did ask for a RESPONSIVE frame.
|
||||
if (frameFlags & RESPONSIVE) {
|
||||
refresh = FAST;
|
||||
reason = NO_OBJECTIONS;
|
||||
LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount, frameFlags);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the timer used for rate-limiting
|
||||
void EInkDynamicDisplay::resetRateLimiting()
|
||||
{
|
||||
previousRunMs = millis();
|
||||
}
|
||||
|
||||
// Generate a hash of this frame, to compare against previous update
|
||||
void EInkDynamicDisplay::hashImage()
|
||||
{
|
||||
imageHash = 0;
|
||||
|
||||
// Sum all bytes of the image buffer together
|
||||
for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
|
||||
imageHash += buffer[b];
|
||||
}
|
||||
}
|
||||
|
||||
// Store the results of determineMode() for future use, and reset for next call
|
||||
void EInkDynamicDisplay::storeAndReset()
|
||||
{
|
||||
previousFrameFlags = frameFlags;
|
||||
previousRefresh = refresh;
|
||||
previousReason = reason;
|
||||
|
||||
// Only store image hash if the display will update
|
||||
if (refresh != SKIPPED) {
|
||||
previousImageHash = imageHash;
|
||||
}
|
||||
|
||||
frameFlags = BACKGROUND;
|
||||
refresh = UNSPECIFIED;
|
||||
}
|
||||
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
// Count how many ghost pixels the new image will display
|
||||
void EInkDynamicDisplay::countGhostPixels()
|
||||
{
|
||||
// If a decision was already reached, don't run the check
|
||||
if (refresh != UNSPECIFIED)
|
||||
return;
|
||||
|
||||
// Start a new count
|
||||
ghostPixelCount = 0;
|
||||
|
||||
// Check new image, bit by bit, for any white pixels at locations marked "dirty"
|
||||
for (uint16_t i = 0; i < displayBufferSize; i++) {
|
||||
for (uint8_t bit = 0; bit < 7; bit++) {
|
||||
|
||||
const bool dirty = (dirtyPixels[i] >> bit) & 1; // Has pixel location been drawn to since full-refresh?
|
||||
const bool shouldBeBlank = !((buffer[i] >> bit) & 1); // Is pixel location white in the new image?
|
||||
|
||||
// If pixel is (or has been) black since last full-refresh, and now is white: ghosting
|
||||
if (dirty && shouldBeBlank)
|
||||
ghostPixelCount++;
|
||||
|
||||
// Update the dirty status for this pixel - will this location become a ghost if set white in future?
|
||||
if (!dirty && !shouldBeBlank)
|
||||
dirtyPixels[i] |= (1 << bit);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG("ghostPixels=%hu, ", ghostPixelCount);
|
||||
}
|
||||
|
||||
// Check if ghost pixel count exceeds the defined limit
|
||||
void EInkDynamicDisplay::checkExcessiveGhosting()
|
||||
{
|
||||
// If a decision was already reached, don't run the check
|
||||
if (refresh != UNSPECIFIED)
|
||||
return;
|
||||
|
||||
countGhostPixels();
|
||||
|
||||
// If too many ghost pixels, select full refresh
|
||||
if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) {
|
||||
refresh = FULL;
|
||||
reason = EXCEEDED_GHOSTINGLIMIT;
|
||||
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x\n", frameFlags);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the dirty pixels array. Call when full-refresh cleans the display.
|
||||
void EInkDynamicDisplay::resetGhostPixelTracking()
|
||||
{
|
||||
// Copy the current frame into dirtyPixels[] from the display buffer
|
||||
memcpy(dirtyPixels, EInkDisplay::buffer, EInkDisplay::displayBufferSize);
|
||||
}
|
||||
#endif // EINK_LIMIT_GHOSTING_PX
|
||||
|
||||
#ifdef HAS_EINK_ASYNCFULL
|
||||
// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready
|
||||
void EInkDynamicDisplay::checkAsyncFullRefresh()
|
||||
{
|
||||
// No refresh taking place, continue with determineMode()
|
||||
if (!asyncRefreshRunning)
|
||||
return;
|
||||
|
||||
// Full refresh still running
|
||||
if (adafruitDisplay->epd2.isBusy()) {
|
||||
// No refresh
|
||||
refresh = SKIPPED;
|
||||
|
||||
// Set the reason, marking what type of frame we're skipping
|
||||
if (frameFlags & DEMAND_FAST)
|
||||
reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST;
|
||||
else if (frameFlags & COSMETIC)
|
||||
reason = ASYNC_REFRESH_BLOCKED_COSMETIC;
|
||||
else if (frameFlags & RESPONSIVE)
|
||||
reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE;
|
||||
else
|
||||
reason = ASYNC_REFRESH_BLOCKED_BACKGROUND;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If we asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
|
||||
adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
|
||||
EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
|
||||
asyncRefreshRunning = false; // Unset the flag
|
||||
LOG_DEBUG("Async full-refresh complete\n");
|
||||
|
||||
// Note: this code only works because of a modification to meshtastic/GxEPD2.
|
||||
// It is only equipped to intercept calls to nextPage()
|
||||
}
|
||||
|
||||
// Hold control while an async refresh runs
|
||||
void EInkDynamicDisplay::awaitRefresh()
|
||||
{
|
||||
// Continually poll the BUSY pin
|
||||
while (adafruitDisplay->epd2.isBusy())
|
||||
yield();
|
||||
|
||||
// End the full-refresh process
|
||||
adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
|
||||
EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
|
||||
asyncRefreshRunning = false; // Unset the flag
|
||||
}
|
||||
#endif // HAS_EINK_ASYNCFULL
|
||||
|
||||
#endif // USE_EINK_DYNAMICDISPLAY
|
||||
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
|
||||
|
||||
#include "EInkDisplay2.h"
|
||||
#include "GxEPD2_BW.h"
|
||||
|
||||
/*
|
||||
Derives from the EInkDisplay adapter class.
|
||||
Accepts suggestions from Screen class about frame type.
|
||||
Determines which refresh type is most suitable.
|
||||
(Full, Fast, Skip)
|
||||
*/
|
||||
|
||||
class EInkDynamicDisplay : public EInkDisplay
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
// ( Parameters unused, passed to EInkDisplay. Maintains compatibility OLEDDisplay class )
|
||||
EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus);
|
||||
~EInkDynamicDisplay();
|
||||
|
||||
// What kind of frame is this
|
||||
enum frameFlagTypes : uint8_t {
|
||||
BACKGROUND = (1 << 0), // For frames via display()
|
||||
RESPONSIVE = (1 << 1), // For frames via forceDisplay()
|
||||
COSMETIC = (1 << 2), // For splashes
|
||||
DEMAND_FAST = (1 << 3), // Special case only
|
||||
BLOCKING = (1 << 4), // Modifier - block while refresh runs
|
||||
};
|
||||
void addFrameFlag(frameFlagTypes flag);
|
||||
|
||||
// Set the correct frame flag, then call universal "update()" method
|
||||
void display() override;
|
||||
bool forceDisplay(uint32_t msecLimit) override; // Shadows base class. Parameter and return val unused.
|
||||
|
||||
protected:
|
||||
enum refreshTypes : uint8_t { // Which refresh operation will be used
|
||||
UNSPECIFIED,
|
||||
FULL,
|
||||
FAST,
|
||||
SKIPPED,
|
||||
};
|
||||
enum reasonTypes : uint8_t { // How was the decision reached
|
||||
NO_OBJECTIONS,
|
||||
ASYNC_REFRESH_BLOCKED_DEMANDFAST,
|
||||
ASYNC_REFRESH_BLOCKED_COSMETIC,
|
||||
ASYNC_REFRESH_BLOCKED_RESPONSIVE,
|
||||
ASYNC_REFRESH_BLOCKED_BACKGROUND,
|
||||
EXCEEDED_RATELIMIT_FAST,
|
||||
EXCEEDED_RATELIMIT_FULL,
|
||||
FLAGGED_COSMETIC,
|
||||
FLAGGED_DEMAND_FAST,
|
||||
EXCEEDED_LIMIT_FASTREFRESH,
|
||||
EXCEEDED_GHOSTINGLIMIT,
|
||||
FRAME_MATCHED_PREVIOUS,
|
||||
BACKGROUND_USES_FAST,
|
||||
FLAGGED_BACKGROUND,
|
||||
REDRAW_WITH_FULL,
|
||||
};
|
||||
|
||||
void configForFastRefresh(); // GxEPD2 code to set fast-refresh
|
||||
void configForFullRefresh(); // GxEPD2 code to set full-refresh
|
||||
bool determineMode(); // Assess situation, pick a refresh type
|
||||
void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type
|
||||
void adjustRefreshCounters(); // Update fastRefreshCount
|
||||
bool update(); // Trigger the display update - determine mode, then call base class
|
||||
void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh()
|
||||
|
||||
// Checks as part of determineMode()
|
||||
void checkInitialized(); // Is this the very first frame?
|
||||
void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh?
|
||||
void checkRateLimiting(); // Is this frame too soon?
|
||||
void checkCosmetic(); // Was the COSMETIC flag set?
|
||||
void checkDemandingFast(); // Was the DEMAND_FAST flag set?
|
||||
void checkFrameMatchesPrevious(); // Does the new frame match the existing display image?
|
||||
void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively?
|
||||
void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND?
|
||||
|
||||
void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting
|
||||
void hashImage(); // Generate a hashed version of this frame, to compare against previous update
|
||||
void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call
|
||||
|
||||
// What we are determining for this frame
|
||||
frameFlagTypes frameFlags = BACKGROUND; // Frame characteristics - determineMode() input
|
||||
refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output
|
||||
reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used
|
||||
|
||||
// What happened last time determineMode() ran
|
||||
frameFlagTypes previousFrameFlags = BACKGROUND; // (Previous) Frame flags
|
||||
refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome
|
||||
reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason
|
||||
|
||||
bool initialized = false; // Have we drawn at least one frame yet?
|
||||
uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting)
|
||||
uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed!
|
||||
uint32_t previousImageHash = 0; // Hash of the previous update's frame
|
||||
uint32_t fastRefreshCount = 0; // How many fast-refreshes consecutively since last full refresh?
|
||||
refreshTypes currentConfig = FULL; // Which refresh type is GxEPD2 currently configured for
|
||||
|
||||
// Optional - track ghosting, pixel by pixel
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh
|
||||
void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit
|
||||
void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display.
|
||||
uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem)
|
||||
uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use
|
||||
#endif
|
||||
|
||||
// Conditional - async full refresh - only with modified meshtastic/GxEPD2
|
||||
#if defined(HAS_EINK_ASYNCFULL)
|
||||
void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready
|
||||
void awaitRefresh(); // Hold control while an async refresh runs
|
||||
void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay()
|
||||
bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh()
|
||||
#endif
|
||||
};
|
||||
|
||||
// Tidier calls to addFrameFlag() from outside class
|
||||
#define EINK_ADD_FRAMEFLAG(display, flag) static_cast<EInkDynamicDisplay *>(display)->addFrameFlag(EInkDynamicDisplay::flag)
|
||||
|
||||
#else // !USE_EINK_DYNAMICDISPLAY
|
||||
// Dummy-macro, removes the need for include guards
|
||||
#define EINK_ADD_FRAMEFLAG(display, flag)
|
||||
#endif
|
||||
+18
-34
@@ -31,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "error.h"
|
||||
#include "gps/GeoCoord.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/images.h"
|
||||
#include "input/TouchScreenImpl1.h"
|
||||
#include "main.h"
|
||||
@@ -56,14 +57,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
#ifdef OLED_RU
|
||||
#include "fonts/OLEDDisplayFontsRU.h"
|
||||
#endif
|
||||
|
||||
#ifdef OLED_UA
|
||||
#include "fonts/OLEDDisplayFontsUA.h"
|
||||
#endif
|
||||
|
||||
using namespace meshtastic; /** @todo remove */
|
||||
|
||||
namespace graphics
|
||||
@@ -111,31 +104,7 @@ static uint16_t displayWidth, displayHeight;
|
||||
#define SCREEN_WIDTH displayWidth
|
||||
#define SCREEN_HEIGHT displayHeight
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16 // Height: 19
|
||||
#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_SMALL ArialMT_Plain_10_RU
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_SMALL ArialMT_Plain_10_UA
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10 // Height: 13
|
||||
#endif
|
||||
#endif
|
||||
#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#endif
|
||||
|
||||
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
|
||||
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
|
||||
#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
|
||||
#include "graphics/ScreenFonts.h"
|
||||
|
||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||
|
||||
@@ -291,6 +260,10 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
/// Used on eink displays while in deep sleep
|
||||
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Next frame should use full-refresh, and block while running, else device will sleep before async callback
|
||||
EINK_ADD_FRAMEFLAG(display, COSMETIC);
|
||||
EINK_ADD_FRAMEFLAG(display, BLOCKING);
|
||||
|
||||
drawIconScreen("Sleeping...", display, state, x, y);
|
||||
}
|
||||
#endif
|
||||
@@ -929,9 +902,12 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
||||
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014)
|
||||
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)
|
||||
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
|
||||
dispdev = new EInkDisplay(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)
|
||||
dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry,
|
||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||
#elif defined(USE_ST7567)
|
||||
dispdev = new ST7567Wire(address.address, -1, -1, geometry,
|
||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||
@@ -966,6 +942,9 @@ void Screen::doDeepSleep()
|
||||
static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
|
||||
ui->setFrames(sleepFrames, sleepFrameCount);
|
||||
ui->update();
|
||||
#ifdef PIN_EINK_EN
|
||||
digitalWrite(PIN_EINK_EN, LOW); // power off backlight
|
||||
#endif
|
||||
#endif
|
||||
setOn(false);
|
||||
}
|
||||
@@ -1195,6 +1174,7 @@ int32_t Screen::runOnce()
|
||||
break;
|
||||
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
|
||||
case Cmd::STOP_BOOT_SCREEN:
|
||||
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
||||
setFrames();
|
||||
break;
|
||||
case Cmd::PRINT:
|
||||
@@ -1375,6 +1355,7 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
||||
{
|
||||
LOG_DEBUG("showing bluetooth screen\n");
|
||||
showingNormalScreen = false;
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
||||
|
||||
static FrameCallback frames[] = {drawFrameBluetooth};
|
||||
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
|
||||
@@ -1392,6 +1373,7 @@ void Screen::handleShutdownScreen()
|
||||
{
|
||||
LOG_DEBUG("showing shutdown screen\n");
|
||||
showingNormalScreen = false;
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
||||
|
||||
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||
drawFrameText(display, state, x, y, "Shutting down...");
|
||||
@@ -1405,6 +1387,7 @@ void Screen::handleRebootScreen()
|
||||
{
|
||||
LOG_DEBUG("showing reboot screen\n");
|
||||
showingNormalScreen = false;
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
||||
|
||||
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||
drawFrameText(display, state, x, y, "Rebooting...");
|
||||
@@ -1417,6 +1400,7 @@ void Screen::handleStartFirmwareUpdateScreen()
|
||||
{
|
||||
LOG_DEBUG("showing firmware screen\n");
|
||||
showingNormalScreen = false;
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
||||
|
||||
static FrameCallback frames[] = {drawFrameFirmware};
|
||||
setFrameImmediateDraw(frames);
|
||||
|
||||
@@ -47,6 +47,7 @@ class Screen
|
||||
#endif
|
||||
|
||||
#include "EInkDisplay2.h"
|
||||
#include "EInkDynamicDisplay.h"
|
||||
#include "TFTDisplay.h"
|
||||
#include "TypedQueue.h"
|
||||
#include "commands.h"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef OLED_RU
|
||||
#include "graphics/fonts/OLEDDisplayFontsRU.h"
|
||||
#endif
|
||||
|
||||
#ifdef OLED_UA
|
||||
#include "graphics/fonts/OLEDDisplayFontsUA.h"
|
||||
#endif
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16 // Height: 19
|
||||
#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_SMALL ArialMT_Plain_10_RU
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_SMALL ArialMT_Plain_10_UA
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10 // Height: 13
|
||||
#endif
|
||||
#endif
|
||||
#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#endif
|
||||
|
||||
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
|
||||
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
|
||||
#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
|
||||
+17
-4
@@ -68,6 +68,7 @@ NRF52Bluetooth *nrf52Bluetooth;
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "linux/LinuxHardwareI2C.h"
|
||||
#include "mesh/raspihttp/PiWebServer.h"
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -218,10 +219,11 @@ void setup()
|
||||
|
||||
initDeepSleep();
|
||||
|
||||
// Testing this fix für erratic T-Echo boot behaviour
|
||||
#if defined(TTGO_T_ECHO) && defined(PIN_EINK_PWR_ON)
|
||||
pinMode(PIN_EINK_PWR_ON, OUTPUT);
|
||||
digitalWrite(PIN_EINK_PWR_ON, HIGH);
|
||||
// power on peripherals
|
||||
#if defined(TTGO_T_ECHO) && defined(PIN_POWER_EN)
|
||||
pinMode(PIN_POWER_EN, OUTPUT);
|
||||
digitalWrite(PIN_POWER_EN, HIGH);
|
||||
digitalWrite(PIN_POWER_EN1, INPUT);
|
||||
#endif
|
||||
|
||||
#if defined(VEXT_ENABLE_V03)
|
||||
@@ -499,6 +501,7 @@ void setup()
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221)
|
||||
@@ -679,6 +682,11 @@ void setup()
|
||||
digitalWrite(SX126X_ANT_SW, 1);
|
||||
#endif
|
||||
|
||||
#ifdef PIN_PWR_DELAY_MS
|
||||
// This may be required to give the peripherals time to power up.
|
||||
delay(PIN_PWR_DELAY_MS);
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
if (settingsMap[use_sx1262]) {
|
||||
if (!rIf) {
|
||||
@@ -857,6 +865,11 @@ void setup()
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#if __has_include(<ulfius.h>)
|
||||
if (settingsMap[webserverport] != -1) {
|
||||
piwebServerThread = new PiWebServerThread();
|
||||
}
|
||||
#endif
|
||||
initApiServer(TCPPort);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "CryptoEngine.h"
|
||||
#include "DisplayFormatters.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#include <assert.h>
|
||||
@@ -254,6 +255,25 @@ const char *Channels::getName(size_t chIndex)
|
||||
return channelName;
|
||||
}
|
||||
|
||||
bool Channels::hasDefaultChannel()
|
||||
{
|
||||
// If we don't use a preset or the default frequency slot, or we override the frequency, we don't have a default channel
|
||||
if (!config.lora.use_preset || !RadioInterface::uses_default_frequency_slot || config.lora.override_frequency)
|
||||
return false;
|
||||
// Check if any of the channels are using the default name and PSK
|
||||
for (size_t i = 0; i < getNumChannels(); i++) {
|
||||
const auto &ch = getByIndex(i);
|
||||
if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) {
|
||||
const char *name = getName(i);
|
||||
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;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs.
|
||||
* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they
|
||||
|
||||
@@ -102,6 +102,9 @@ class Channels
|
||||
*/
|
||||
int16_t setActiveByIndex(ChannelIndex channelIndex);
|
||||
|
||||
// Returns true if we can be reached via a channel with the default settings given a region and modem preset
|
||||
bool hasDefaultChannel();
|
||||
|
||||
private:
|
||||
/** Given a channel index, change to use the crypto key specified by that index
|
||||
*
|
||||
|
||||
@@ -32,7 +32,8 @@ MeshModule::~MeshModule()
|
||||
assert(0); // FIXME - remove from list of modules once someone needs this feature
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex)
|
||||
meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
|
||||
uint8_t hopStart, uint8_t hopLimit)
|
||||
{
|
||||
meshtastic_Routing c = meshtastic_Routing_init_default;
|
||||
|
||||
@@ -49,7 +50,7 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod
|
||||
|
||||
p->priority = meshtastic_MeshPacket_Priority_ACK;
|
||||
|
||||
p->hop_limit = config.lora.hop_limit; // Flood ACK back to original sender
|
||||
p->hop_limit = routingModule->getHopLimitForResponse(hopStart, hopLimit); // Flood ACK back to original sender
|
||||
p->to = to;
|
||||
p->decoded.request_id = idFrom;
|
||||
p->channel = chIndex;
|
||||
@@ -176,7 +177,8 @@ void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src)
|
||||
// SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded)
|
||||
// but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs
|
||||
// bad.
|
||||
routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel);
|
||||
routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel, mp.hop_start,
|
||||
mp.hop_limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +219,7 @@ void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to)
|
||||
assert(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // Should already be set by now
|
||||
p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0
|
||||
p->channel = to.channel; // Use the same channel that the request came in on
|
||||
p->hop_limit = routingModule->getHopLimitForResponse(to.hop_start, to.hop_limit);
|
||||
|
||||
// No need for an ack if we are just delivering locally (it just generates an ignored ack)
|
||||
p->want_ack = (to.from != 0) ? to.want_ack : false;
|
||||
|
||||
@@ -153,7 +153,8 @@ class MeshModule
|
||||
virtual bool wantUIFrame() { return false; }
|
||||
virtual Observable<const UIFrameEvent *> *getUIFrameObservable() { return NULL; }
|
||||
|
||||
meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
|
||||
meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
|
||||
uint8_t hopStart = 0, uint8_t hopLimit = 0);
|
||||
|
||||
/// Send an error response for the specified packet.
|
||||
meshtastic_MeshPacket *allocErrorResponse(meshtastic_Routing_Error err, const meshtastic_MeshPacket *p);
|
||||
|
||||
+14
-2
@@ -130,6 +130,9 @@ bool NodeDB::factoryReset()
|
||||
LOG_INFO("Performing factory reset!\n");
|
||||
// first, remove the "/prefs" (this removes most prefs)
|
||||
rmDir("/prefs");
|
||||
if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) {
|
||||
LOG_WARN("Could not remove rangetest.csv file\n");
|
||||
}
|
||||
// second, install default state (this will deal with the duplicate mac address issue)
|
||||
installDefaultDeviceState();
|
||||
installDefaultConfig();
|
||||
@@ -741,14 +744,17 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
|
||||
|
||||
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
|
||||
|
||||
size_t NodeDB::getNumOnlineMeshNodes()
|
||||
size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
||||
{
|
||||
size_t numseen = 0;
|
||||
|
||||
// FIXME this implementation is kinda expensive
|
||||
for (int i = 0; i < *numMeshNodes; i++)
|
||||
for (int i = 0; i < *numMeshNodes; i++) {
|
||||
if (localOnly && meshNodes[i].via_mqtt)
|
||||
continue;
|
||||
if (sinceLastSeen(&meshNodes[i]) < NUM_ONLINE_SECS)
|
||||
numseen++;
|
||||
}
|
||||
|
||||
return numseen;
|
||||
}
|
||||
@@ -872,6 +878,12 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
||||
|
||||
if (mp.rx_snr)
|
||||
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
||||
|
||||
info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
|
||||
|
||||
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
|
||||
if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start)
|
||||
info->hops_away = mp.hop_start - mp.hop_limit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -108,8 +108,10 @@ class NodeDB
|
||||
// get channel channel index we heard a nodeNum on, defaults to 0 if not found
|
||||
uint8_t getMeshNodeChannel(NodeNum n);
|
||||
|
||||
/// Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
||||
size_t getNumOnlineMeshNodes();
|
||||
/* Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
||||
* @param localOnly if true, ignore nodes heard via MQTT
|
||||
*/
|
||||
size_t getNumOnlineMeshNodes(bool localOnly = false);
|
||||
|
||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "RadioInterface.h"
|
||||
#include "Channels.h"
|
||||
#include "DisplayFormatters.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
@@ -143,6 +144,7 @@ const RegionInfo regions[] = {
|
||||
};
|
||||
|
||||
const RegionInfo *myRegion;
|
||||
bool RadioInterface::uses_default_frequency_slot = true;
|
||||
|
||||
static uint8_t bytes[MAX_RHPACKETLEN];
|
||||
|
||||
@@ -302,6 +304,8 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
||||
out += DEBUG_PORT.mt_sprintf(" rxRSSI=%i", p->rx_rssi);
|
||||
if (p->via_mqtt != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" via MQTT");
|
||||
if (p->hop_start != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" hopStart=%d", p->hop_start);
|
||||
if (p->priority != 0)
|
||||
out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority);
|
||||
|
||||
@@ -484,6 +488,10 @@ void RadioInterface::applyModemConfig()
|
||||
// channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1)
|
||||
int channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
|
||||
|
||||
// Check if we use the default frequency slot
|
||||
RadioInterface::uses_default_frequency_slot =
|
||||
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);
|
||||
|
||||
@@ -561,6 +569,7 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
|
||||
p->hop_limit = HOP_RELIABLE;
|
||||
}
|
||||
h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0);
|
||||
h->flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK;
|
||||
|
||||
// if the sender nodenum is zero, that means uninitialized
|
||||
assert(h->from);
|
||||
@@ -569,4 +578,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
|
||||
|
||||
sendingPacket = p;
|
||||
return p->encrypted.size + sizeof(PacketHeader);
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,11 @@
|
||||
|
||||
#define MAX_RHPACKETLEN 256
|
||||
|
||||
#define PACKET_FLAGS_HOP_MASK 0x07
|
||||
#define PACKET_FLAGS_HOP_LIMIT_MASK 0x07
|
||||
#define PACKET_FLAGS_WANT_ACK_MASK 0x08
|
||||
#define PACKET_FLAGS_VIA_MQTT_MASK 0x10
|
||||
#define PACKET_FLAGS_HOP_START_MASK 0xE0
|
||||
#define PACKET_FLAGS_HOP_START_SHIFT 5
|
||||
|
||||
/**
|
||||
* This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility
|
||||
@@ -173,6 +175,9 @@ class RadioInterface
|
||||
/// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers
|
||||
virtual bool isIRQPending() { return false; }
|
||||
|
||||
// Whether we use the default frequency slot given our LoRa config (region and modem preset)
|
||||
static bool uses_default_frequency_slot;
|
||||
|
||||
protected:
|
||||
int8_t power = 17; // Set by applyModemConfig()
|
||||
|
||||
@@ -224,4 +229,4 @@ class RadioInterface
|
||||
};
|
||||
|
||||
/// Debug printing for packets
|
||||
void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
|
||||
void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
|
||||
@@ -359,8 +359,9 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
mp->to = h->to;
|
||||
mp->id = h->id;
|
||||
mp->channel = h->channel;
|
||||
assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code
|
||||
mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK;
|
||||
assert(HOP_MAX <= PACKET_FLAGS_HOP_LIMIT_MASK); // If hopmax changes, carefully check this code
|
||||
mp->hop_limit = h->flags & PACKET_FLAGS_HOP_LIMIT_MASK;
|
||||
mp->hop_start = (h->flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
|
||||
mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK);
|
||||
mp->via_mqtt = !!(h->flags & PACKET_FLAGS_VIA_MQTT_MASK);
|
||||
|
||||
@@ -407,4 +408,4 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
|
||||
// bits
|
||||
enableInterrupt(isrTxLevel0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,12 +71,12 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
i->second.nextTxMsec += iface->getPacketTime(p);
|
||||
}
|
||||
|
||||
/* Resend implicit ACKs for repeated packets (assuming the original packet was sent with HOP_RELIABLE)
|
||||
/* Resend implicit ACKs for repeated packets (hopStart equals hopLimit);
|
||||
* this way if an implicit ACK is dropped and a packet is resent we'll rebroadcast again.
|
||||
* Resending real ACKs is omitted, as you might receive a packet multiple times due to flooding and
|
||||
* flooding this ACK back to the original sender already adds redundancy. */
|
||||
if (wasSeenRecently(p, false) && p->hop_limit == HOP_RELIABLE && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) {
|
||||
// retransmission on broadcast has hop_limit still equal to HOP_RELIABLE
|
||||
bool isRepeated = p->hop_start == 0 ? (p->hop_limit == HOP_RELIABLE) : (p->hop_start == p->hop_limit);
|
||||
if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) {
|
||||
LOG_DEBUG("Resending implicit ack for a repeated floodmsg\n");
|
||||
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p);
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
@@ -107,10 +107,11 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
||||
if (MeshModule::currentReply) {
|
||||
LOG_DEBUG("Some other module has replied to this message, no need for a 2nd ack\n");
|
||||
} else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel);
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit);
|
||||
} else {
|
||||
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
|
||||
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex());
|
||||
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(), p->hop_start,
|
||||
p->hop_limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,8 +167,6 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key)
|
||||
auto old = findPendingPacket(key);
|
||||
if (old) {
|
||||
auto p = old->packet;
|
||||
auto numErased = pending.erase(key);
|
||||
assert(numErased == 1);
|
||||
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
|
||||
to avoid canceling a transmission if it was ACKed super fast via MQTT */
|
||||
if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) {
|
||||
@@ -176,6 +175,8 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key)
|
||||
// now free the pooled copy for retransmission too
|
||||
packetPool.release(p);
|
||||
}
|
||||
auto numErased = pending.erase(key);
|
||||
assert(numErased == 1);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
@@ -255,4 +256,4 @@ void ReliableRouter::setNextTx(PendingPacket *pending)
|
||||
LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
|
||||
printPacket("", pending->packet);
|
||||
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
|
||||
}
|
||||
}
|
||||
+21
-5
@@ -132,9 +132,10 @@ meshtastic_MeshPacket *Router::allocForSending()
|
||||
/**
|
||||
* Send an ack or a nak packet back towards whoever sent idFrom
|
||||
*/
|
||||
void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex)
|
||||
void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart,
|
||||
uint8_t hopLimit)
|
||||
{
|
||||
routingModule->sendAckNak(err, to, idFrom, chIndex);
|
||||
routingModule->sendAckNak(err, to, idFrom, chIndex, hopStart, hopLimit);
|
||||
}
|
||||
|
||||
void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p)
|
||||
@@ -240,6 +241,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
// the lora we need to make sure we have replaced it with our local address
|
||||
p->from = getFrom(p);
|
||||
|
||||
// If we are the original transmitter, set the hop limit with which we start
|
||||
if (p->from == getNodeNum())
|
||||
p->hop_start = p->hop_limit;
|
||||
|
||||
// If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it)
|
||||
|
||||
assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag ||
|
||||
@@ -292,7 +297,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
|
||||
return false;
|
||||
|
||||
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
|
||||
!nodeDB.getMeshNode(p->from)->has_user) {
|
||||
(nodeDB.getMeshNode(p->from) == NULL || !nodeDB.getMeshNode(p->from)->has_user)) {
|
||||
LOG_DEBUG("Node 0x%x not in NodeDB. Rebroadcast mode KNOWN_ONLY will ignore packet\n", p->from);
|
||||
return false;
|
||||
}
|
||||
@@ -435,6 +440,7 @@ NodeNum Router::getNodeNum()
|
||||
*/
|
||||
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
|
||||
@@ -451,8 +457,17 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
||||
else
|
||||
printPacket("handleReceived(REMOTE)", p);
|
||||
|
||||
// Neighbor info module is disabled, ignore expensive neighbor info packets
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p->decoded.portnum == meshtastic_PortNum_NEIGHBORINFO_APP &&
|
||||
(!moduleConfig.has_neighbor_info || !moduleConfig.neighbor_info.enabled)) {
|
||||
LOG_DEBUG("Neighbor info module is disabled, ignoring neighbor packet\n");
|
||||
cancelSending(p->from, p->id);
|
||||
skipHandle = true;
|
||||
}
|
||||
|
||||
// Publish received message to MQTT if we're not the original transmitter of the packet
|
||||
if (moduleConfig.mqtt.enabled && getFrom(p) != nodeDB.getNodeNum() && mqtt)
|
||||
if (!skipHandle && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB.getNodeNum() && mqtt)
|
||||
mqtt->onSend(*p_encrypted, *p, p->channel);
|
||||
} else {
|
||||
printPacket("packet decoding failed or skipped (no PSK?)", p);
|
||||
@@ -461,7 +476,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
||||
packetPool.release(p_encrypted); // Release the encrypted packet
|
||||
|
||||
// call modules here
|
||||
MeshModule::callPlugins(*p, src);
|
||||
if (!skipHandle)
|
||||
MeshModule::callPlugins(*p, src);
|
||||
}
|
||||
|
||||
void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
|
||||
|
||||
+2
-1
@@ -104,7 +104,8 @@ class Router : protected concurrency::OSThread
|
||||
/**
|
||||
* Send an ack or a nak packet back towards whoever sent idFrom
|
||||
*/
|
||||
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
|
||||
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart = 0,
|
||||
uint8_t hopLimit = 0);
|
||||
|
||||
private:
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,8 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo
|
||||
info.snr = lite->snr;
|
||||
info.last_heard = lite->last_heard;
|
||||
info.channel = lite->channel;
|
||||
info.via_mqtt = lite->via_mqtt;
|
||||
info.hops_away = lite->hops_away;
|
||||
|
||||
if (lite->has_position) {
|
||||
info.has_position = true;
|
||||
|
||||
@@ -65,6 +65,10 @@ typedef struct _meshtastic_NodeInfoLite {
|
||||
meshtastic_DeviceMetrics device_metrics;
|
||||
/* local channel index we heard that node on. Only populated if its not the default channel. */
|
||||
uint8_t channel;
|
||||
/* True if we witnessed the node over MQTT instead of LoRA transport */
|
||||
bool via_mqtt;
|
||||
/* Number of hops away from us this node is (0 if adjacent) */
|
||||
uint8_t hops_away;
|
||||
} meshtastic_NodeInfoLite;
|
||||
|
||||
/* The on-disk saved channels */
|
||||
@@ -175,13 +179,13 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, 0, {meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default}}
|
||||
#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0}
|
||||
#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0}
|
||||
#define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
|
||||
#define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
|
||||
#define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default}
|
||||
#define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default}
|
||||
#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, 0, {meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero}}
|
||||
#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0}
|
||||
#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0}
|
||||
#define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
|
||||
#define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
|
||||
#define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero}
|
||||
@@ -200,6 +204,8 @@ extern "C" {
|
||||
#define meshtastic_NodeInfoLite_last_heard_tag 5
|
||||
#define meshtastic_NodeInfoLite_device_metrics_tag 6
|
||||
#define meshtastic_NodeInfoLite_channel_tag 7
|
||||
#define meshtastic_NodeInfoLite_via_mqtt_tag 8
|
||||
#define meshtastic_NodeInfoLite_hops_away_tag 9
|
||||
#define meshtastic_ChannelFile_channels_tag 1
|
||||
#define meshtastic_ChannelFile_version_tag 2
|
||||
#define meshtastic_OEMStore_oem_icon_width_tag 1
|
||||
@@ -252,7 +258,9 @@ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \
|
||||
X(a, STATIC, SINGULAR, FLOAT, snr, 4) \
|
||||
X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \
|
||||
X(a, STATIC, SINGULAR, UINT32, channel, 7)
|
||||
X(a, STATIC, SINGULAR, UINT32, channel, 7) \
|
||||
X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \
|
||||
X(a, STATIC, SINGULAR, UINT32, hops_away, 9)
|
||||
#define meshtastic_NodeInfoLite_CALLBACK NULL
|
||||
#define meshtastic_NodeInfoLite_DEFAULT NULL
|
||||
#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User
|
||||
@@ -313,10 +321,10 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define meshtastic_ChannelFile_size 702
|
||||
#define meshtastic_DeviceState_size 17062
|
||||
#define meshtastic_NodeInfoLite_size 153
|
||||
#define meshtastic_DeviceState_size 17571
|
||||
#define meshtastic_NodeInfoLite_size 158
|
||||
#define meshtastic_NodeRemoteHardwarePin_size 29
|
||||
#define meshtastic_OEMStore_size 3246
|
||||
#define meshtastic_OEMStore_size 3262
|
||||
#define meshtastic_PositionLite_size 28
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define meshtastic_LocalConfig_size 469
|
||||
#define meshtastic_LocalModuleConfig_size 631
|
||||
#define meshtastic_LocalModuleConfig_size 647
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -534,8 +534,7 @@ typedef struct _meshtastic_MeshPacket {
|
||||
Note: Our crypto implementation uses this field as well.
|
||||
See [crypto](/docs/overview/encryption) for details. */
|
||||
uint32_t from;
|
||||
/* The (immediatSee Priority description for more details.y should be fixed32 instead, this encoding only
|
||||
hurts the ble link though. */
|
||||
/* The (immediate) destination for this packet */
|
||||
uint32_t to;
|
||||
/* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on.
|
||||
If unset, packet was on the primary channel.
|
||||
@@ -594,6 +593,9 @@ typedef struct _meshtastic_MeshPacket {
|
||||
meshtastic_MeshPacket_Delayed delayed;
|
||||
/* Describes whether this packet passed via MQTT somewhere along the path it currently took. */
|
||||
bool via_mqtt;
|
||||
/* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header.
|
||||
When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. */
|
||||
uint8_t hop_start;
|
||||
} meshtastic_MeshPacket;
|
||||
|
||||
/* The bluetooth to device link:
|
||||
@@ -632,6 +634,10 @@ typedef struct _meshtastic_NodeInfo {
|
||||
meshtastic_DeviceMetrics device_metrics;
|
||||
/* local channel index we heard that node on. Only populated if its not the default channel. */
|
||||
uint8_t channel;
|
||||
/* True if we witnessed the node over MQTT instead of LoRA transport */
|
||||
bool via_mqtt;
|
||||
/* Number of hops away from us this node is (0 if adjacent) */
|
||||
uint8_t hops_away;
|
||||
} meshtastic_NodeInfo;
|
||||
|
||||
/* Unique local debugging info for this node
|
||||
@@ -890,8 +896,8 @@ extern "C" {
|
||||
#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_Waypoint_init_default {0, 0, 0, 0, 0, "", "", 0}
|
||||
#define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0}
|
||||
#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0}
|
||||
#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0}
|
||||
#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0}
|
||||
#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0}
|
||||
#define meshtastic_MyNodeInfo_init_default {0, 0, 0}
|
||||
#define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN}
|
||||
#define meshtastic_QueueStatus_init_default {0, 0, 0, 0}
|
||||
@@ -908,8 +914,8 @@ extern "C" {
|
||||
#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_Waypoint_init_zero {0, 0, 0, 0, 0, "", "", 0}
|
||||
#define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0}
|
||||
#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0}
|
||||
#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0}
|
||||
#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0}
|
||||
#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0}
|
||||
#define meshtastic_MyNodeInfo_init_zero {0, 0, 0}
|
||||
#define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN}
|
||||
#define meshtastic_QueueStatus_init_zero {0, 0, 0, 0}
|
||||
@@ -989,6 +995,7 @@ extern "C" {
|
||||
#define meshtastic_MeshPacket_rx_rssi_tag 12
|
||||
#define meshtastic_MeshPacket_delayed_tag 13
|
||||
#define meshtastic_MeshPacket_via_mqtt_tag 14
|
||||
#define meshtastic_MeshPacket_hop_start_tag 15
|
||||
#define meshtastic_NodeInfo_num_tag 1
|
||||
#define meshtastic_NodeInfo_user_tag 2
|
||||
#define meshtastic_NodeInfo_position_tag 3
|
||||
@@ -996,6 +1003,8 @@ extern "C" {
|
||||
#define meshtastic_NodeInfo_last_heard_tag 5
|
||||
#define meshtastic_NodeInfo_device_metrics_tag 6
|
||||
#define meshtastic_NodeInfo_channel_tag 7
|
||||
#define meshtastic_NodeInfo_via_mqtt_tag 8
|
||||
#define meshtastic_NodeInfo_hops_away_tag 9
|
||||
#define meshtastic_MyNodeInfo_my_node_num_tag 1
|
||||
#define meshtastic_MyNodeInfo_reboot_count_tag 8
|
||||
#define meshtastic_MyNodeInfo_min_app_version_tag 11
|
||||
@@ -1146,7 +1155,8 @@ X(a, STATIC, SINGULAR, BOOL, want_ack, 10) \
|
||||
X(a, STATIC, SINGULAR, UENUM, priority, 11) \
|
||||
X(a, STATIC, SINGULAR, INT32, rx_rssi, 12) \
|
||||
X(a, STATIC, SINGULAR, UENUM, delayed, 13) \
|
||||
X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14)
|
||||
X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14) \
|
||||
X(a, STATIC, SINGULAR, UINT32, hop_start, 15)
|
||||
#define meshtastic_MeshPacket_CALLBACK NULL
|
||||
#define meshtastic_MeshPacket_DEFAULT NULL
|
||||
#define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data
|
||||
@@ -1158,7 +1168,9 @@ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \
|
||||
X(a, STATIC, SINGULAR, FLOAT, snr, 4) \
|
||||
X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \
|
||||
X(a, STATIC, SINGULAR, UINT32, channel, 7)
|
||||
X(a, STATIC, SINGULAR, UINT32, channel, 7) \
|
||||
X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \
|
||||
X(a, STATIC, SINGULAR, UINT32, hops_away, 9)
|
||||
#define meshtastic_NodeInfo_CALLBACK NULL
|
||||
#define meshtastic_NodeInfo_DEFAULT NULL
|
||||
#define meshtastic_NodeInfo_user_MSGTYPE meshtastic_User
|
||||
@@ -1311,12 +1323,12 @@ extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg;
|
||||
#define meshtastic_DeviceMetadata_size 46
|
||||
#define meshtastic_FromRadio_size 510
|
||||
#define meshtastic_LogRecord_size 81
|
||||
#define meshtastic_MeshPacket_size 323
|
||||
#define meshtastic_MeshPacket_size 326
|
||||
#define meshtastic_MqttClientProxyMessage_size 501
|
||||
#define meshtastic_MyNodeInfo_size 18
|
||||
#define meshtastic_NeighborInfo_size 258
|
||||
#define meshtastic_Neighbor_size 22
|
||||
#define meshtastic_NodeInfo_size 270
|
||||
#define meshtastic_NodeInfo_size 275
|
||||
#define meshtastic_Position_size 144
|
||||
#define meshtastic_QueueStatus_size 23
|
||||
#define meshtastic_RouteDiscovery_size 40
|
||||
|
||||
@@ -6,12 +6,15 @@
|
||||
#error Regenerate this file with the current version of nanopb generator.
|
||||
#endif
|
||||
|
||||
PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, AUTO)
|
||||
PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, 2)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_ModuleConfig_MQTTConfig, meshtastic_ModuleConfig_MQTTConfig, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_ModuleConfig_MapReportSettings, meshtastic_ModuleConfig_MapReportSettings, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_ModuleConfig_RemoteHardwareConfig, meshtastic_ModuleConfig_RemoteHardwareConfig, AUTO)
|
||||
|
||||
|
||||
|
||||
@@ -84,6 +84,14 @@ typedef enum _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar {
|
||||
} meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar;
|
||||
|
||||
/* Struct definitions */
|
||||
/* Settings for reporting unencrypted information about our node to a map via MQTT */
|
||||
typedef struct _meshtastic_ModuleConfig_MapReportSettings {
|
||||
/* How often we should report our info to the map (in seconds) */
|
||||
uint32_t publish_interval_secs;
|
||||
/* Bits of precision for the location sent (default of 32 is full precision). */
|
||||
uint32_t position_precision;
|
||||
} meshtastic_ModuleConfig_MapReportSettings;
|
||||
|
||||
/* MQTT Client Config */
|
||||
typedef struct _meshtastic_ModuleConfig_MQTTConfig {
|
||||
/* If a meshtastic node is able to reach the internet it will normally attempt to gateway any channels that are marked as
|
||||
@@ -114,6 +122,11 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig {
|
||||
char root[16];
|
||||
/* If true, we can use the connected phone / client to proxy messages to MQTT instead of a direct connection */
|
||||
bool proxy_to_client_enabled;
|
||||
/* If true, we will periodically report unencrypted information about our node to a map via MQTT */
|
||||
bool map_reporting_enabled;
|
||||
/* Settings for reporting information about our node to a map via MQTT */
|
||||
bool has_map_report_settings;
|
||||
meshtastic_ModuleConfig_MapReportSettings map_report_settings;
|
||||
} meshtastic_ModuleConfig_MQTTConfig;
|
||||
|
||||
/* NeighborInfoModule Config */
|
||||
@@ -427,6 +440,7 @@ extern "C" {
|
||||
|
||||
|
||||
|
||||
|
||||
#define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud
|
||||
|
||||
|
||||
@@ -447,7 +461,8 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_ModuleConfig_init_default {0, {meshtastic_ModuleConfig_MQTTConfig_init_default}}
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0}
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_default}
|
||||
#define meshtastic_ModuleConfig_MapReportSettings_init_default {0, 0}
|
||||
#define meshtastic_ModuleConfig_RemoteHardwareConfig_init_default {0, 0, 0, {meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default}}
|
||||
#define meshtastic_ModuleConfig_NeighborInfoConfig_init_default {0, 0}
|
||||
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, 0, 0}
|
||||
@@ -462,7 +477,8 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
|
||||
#define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
|
||||
#define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}}
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0}
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero}
|
||||
#define meshtastic_ModuleConfig_MapReportSettings_init_zero {0, 0}
|
||||
#define meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero {0, 0, 0, {meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero}}
|
||||
#define meshtastic_ModuleConfig_NeighborInfoConfig_init_zero {0, 0}
|
||||
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, 0, 0}
|
||||
@@ -478,6 +494,8 @@ extern "C" {
|
||||
#define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_ModuleConfig_MapReportSettings_publish_interval_secs_tag 1
|
||||
#define meshtastic_ModuleConfig_MapReportSettings_position_precision_tag 2
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_enabled_tag 1
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_address_tag 2
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_username_tag 3
|
||||
@@ -487,6 +505,8 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_tls_enabled_tag 7
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_root_tag 8
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_proxy_to_client_enabled_tag 9
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_map_reporting_enabled_tag 10
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_tag 11
|
||||
#define meshtastic_ModuleConfig_NeighborInfoConfig_enabled_tag 1
|
||||
#define meshtastic_ModuleConfig_NeighborInfoConfig_update_interval_tag 2
|
||||
#define meshtastic_ModuleConfig_DetectionSensorConfig_enabled_tag 1
|
||||
@@ -623,9 +643,18 @@ X(a, STATIC, SINGULAR, BOOL, encryption_enabled, 5) \
|
||||
X(a, STATIC, SINGULAR, BOOL, json_enabled, 6) \
|
||||
X(a, STATIC, SINGULAR, BOOL, tls_enabled, 7) \
|
||||
X(a, STATIC, SINGULAR, STRING, root, 8) \
|
||||
X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9)
|
||||
X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9) \
|
||||
X(a, STATIC, SINGULAR, BOOL, map_reporting_enabled, 10) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, map_report_settings, 11)
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_DEFAULT NULL
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_MSGTYPE meshtastic_ModuleConfig_MapReportSettings
|
||||
|
||||
#define meshtastic_ModuleConfig_MapReportSettings_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UINT32, publish_interval_secs, 1) \
|
||||
X(a, STATIC, SINGULAR, UINT32, position_precision, 2)
|
||||
#define meshtastic_ModuleConfig_MapReportSettings_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_MapReportSettings_DEFAULT NULL
|
||||
|
||||
#define meshtastic_ModuleConfig_RemoteHardwareConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
|
||||
@@ -764,6 +793,7 @@ X(a, STATIC, SINGULAR, UENUM, type, 3)
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_MQTTConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_MapReportSettings_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_RemoteHardwareConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_NeighborInfoConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_DetectionSensorConfig_msg;
|
||||
@@ -781,6 +811,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define meshtastic_ModuleConfig_fields &meshtastic_ModuleConfig_msg
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_fields &meshtastic_ModuleConfig_MQTTConfig_msg
|
||||
#define meshtastic_ModuleConfig_MapReportSettings_fields &meshtastic_ModuleConfig_MapReportSettings_msg
|
||||
#define meshtastic_ModuleConfig_RemoteHardwareConfig_fields &meshtastic_ModuleConfig_RemoteHardwareConfig_msg
|
||||
#define meshtastic_ModuleConfig_NeighborInfoConfig_fields &meshtastic_ModuleConfig_NeighborInfoConfig_msg
|
||||
#define meshtastic_ModuleConfig_DetectionSensorConfig_fields &meshtastic_ModuleConfig_DetectionSensorConfig_msg
|
||||
@@ -801,7 +832,8 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_size 49
|
||||
#define meshtastic_ModuleConfig_DetectionSensorConfig_size 44
|
||||
#define meshtastic_ModuleConfig_ExternalNotificationConfig_size 42
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_size 222
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_size 238
|
||||
#define meshtastic_ModuleConfig_MapReportSettings_size 12
|
||||
#define meshtastic_ModuleConfig_NeighborInfoConfig_size 8
|
||||
#define meshtastic_ModuleConfig_PaxcounterConfig_size 8
|
||||
#define meshtastic_ModuleConfig_RangeTestConfig_size 10
|
||||
@@ -809,7 +841,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
#define meshtastic_ModuleConfig_SerialConfig_size 28
|
||||
#define meshtastic_ModuleConfig_StoreForwardConfig_size 22
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_size 36
|
||||
#define meshtastic_ModuleConfig_size 225
|
||||
#define meshtastic_ModuleConfig_size 241
|
||||
#define meshtastic_RemoteHardwarePin_size 21
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -9,4 +9,7 @@
|
||||
PB_BIND(meshtastic_ServiceEnvelope, meshtastic_ServiceEnvelope, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_MapReport, meshtastic_MapReport, AUTO)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#define PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED
|
||||
#include <pb.h>
|
||||
#include "meshtastic/mesh.pb.h"
|
||||
#include "meshtastic/config.pb.h"
|
||||
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
#error Regenerate this file with the current version of nanopb generator.
|
||||
@@ -23,6 +24,38 @@ typedef struct _meshtastic_ServiceEnvelope {
|
||||
char *gateway_id;
|
||||
} meshtastic_ServiceEnvelope;
|
||||
|
||||
/* Information about a node intended to be reported unencrypted to a map using MQTT. */
|
||||
typedef struct _meshtastic_MapReport {
|
||||
/* A full name for this user, i.e. "Kevin Hester" */
|
||||
char long_name[40];
|
||||
/* A VERY short name, ideally two characters.
|
||||
Suitable for a tiny OLED screen */
|
||||
char short_name[5];
|
||||
/* Role of the node that applies specific settings for a particular use-case */
|
||||
meshtastic_Config_DeviceConfig_Role role;
|
||||
/* Hardware model of the node, i.e. T-Beam, Heltec V3, etc... */
|
||||
meshtastic_HardwareModel hw_model;
|
||||
/* Device firmware version string */
|
||||
char firmware_version[18];
|
||||
/* The region code for the radio (US, CN, EU433, etc...) */
|
||||
meshtastic_Config_LoRaConfig_RegionCode region;
|
||||
/* Modem preset used by the radio (LongFast, MediumSlow, etc...) */
|
||||
meshtastic_Config_LoRaConfig_ModemPreset modem_preset;
|
||||
/* Whether the node has a channel with default PSK and name (LongFast, MediumSlow, etc...)
|
||||
and it uses the default frequency slot given the region and modem preset. */
|
||||
bool has_default_channel;
|
||||
/* Latitude: multiply by 1e-7 to get degrees in floating point */
|
||||
int32_t latitude_i;
|
||||
/* Longitude: multiply by 1e-7 to get degrees in floating point */
|
||||
int32_t longitude_i;
|
||||
/* Altitude in meters above MSL */
|
||||
int32_t altitude;
|
||||
/* Indicates the bits of precision for latitude and longitude set by the sending node */
|
||||
uint32_t position_precision;
|
||||
/* Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT) */
|
||||
uint16_t num_online_local_nodes;
|
||||
} meshtastic_MapReport;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -30,12 +63,27 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_ServiceEnvelope_init_default {NULL, NULL, NULL}
|
||||
#define meshtastic_MapReport_init_default {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ServiceEnvelope_init_zero {NULL, NULL, NULL}
|
||||
#define meshtastic_MapReport_init_zero {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_ServiceEnvelope_packet_tag 1
|
||||
#define meshtastic_ServiceEnvelope_channel_id_tag 2
|
||||
#define meshtastic_ServiceEnvelope_gateway_id_tag 3
|
||||
#define meshtastic_MapReport_long_name_tag 1
|
||||
#define meshtastic_MapReport_short_name_tag 2
|
||||
#define meshtastic_MapReport_role_tag 3
|
||||
#define meshtastic_MapReport_hw_model_tag 4
|
||||
#define meshtastic_MapReport_firmware_version_tag 5
|
||||
#define meshtastic_MapReport_region_tag 6
|
||||
#define meshtastic_MapReport_modem_preset_tag 7
|
||||
#define meshtastic_MapReport_has_default_channel_tag 8
|
||||
#define meshtastic_MapReport_latitude_i_tag 9
|
||||
#define meshtastic_MapReport_longitude_i_tag 10
|
||||
#define meshtastic_MapReport_altitude_tag 11
|
||||
#define meshtastic_MapReport_position_precision_tag 12
|
||||
#define meshtastic_MapReport_num_online_local_nodes_tag 13
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_ServiceEnvelope_FIELDLIST(X, a) \
|
||||
@@ -46,13 +94,33 @@ X(a, POINTER, SINGULAR, STRING, gateway_id, 3)
|
||||
#define meshtastic_ServiceEnvelope_DEFAULT NULL
|
||||
#define meshtastic_ServiceEnvelope_packet_MSGTYPE meshtastic_MeshPacket
|
||||
|
||||
#define meshtastic_MapReport_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, STRING, long_name, 1) \
|
||||
X(a, STATIC, SINGULAR, STRING, short_name, 2) \
|
||||
X(a, STATIC, SINGULAR, UENUM, role, 3) \
|
||||
X(a, STATIC, SINGULAR, UENUM, hw_model, 4) \
|
||||
X(a, STATIC, SINGULAR, STRING, firmware_version, 5) \
|
||||
X(a, STATIC, SINGULAR, UENUM, region, 6) \
|
||||
X(a, STATIC, SINGULAR, UENUM, modem_preset, 7) \
|
||||
X(a, STATIC, SINGULAR, BOOL, has_default_channel, 8) \
|
||||
X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 9) \
|
||||
X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 10) \
|
||||
X(a, STATIC, SINGULAR, INT32, altitude, 11) \
|
||||
X(a, STATIC, SINGULAR, UINT32, position_precision, 12) \
|
||||
X(a, STATIC, SINGULAR, UINT32, num_online_local_nodes, 13)
|
||||
#define meshtastic_MapReport_CALLBACK NULL
|
||||
#define meshtastic_MapReport_DEFAULT NULL
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_ServiceEnvelope_msg;
|
||||
extern const pb_msgdesc_t meshtastic_MapReport_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define meshtastic_ServiceEnvelope_fields &meshtastic_ServiceEnvelope_msg
|
||||
#define meshtastic_MapReport_fields &meshtastic_MapReport_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* meshtastic_ServiceEnvelope_size depends on runtime parameters */
|
||||
#define meshtastic_MapReport_size 108
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -38,19 +38,19 @@ typedef enum _meshtastic_PortNum {
|
||||
ENCODING: Protobuf */
|
||||
meshtastic_PortNum_REMOTE_HARDWARE_APP = 2,
|
||||
/* The built-in position messaging app.
|
||||
Payload is a [Position](/docs/developers/protobufs/api#position) message
|
||||
Payload is a Position message.
|
||||
ENCODING: Protobuf */
|
||||
meshtastic_PortNum_POSITION_APP = 3,
|
||||
/* The built-in user info app.
|
||||
Payload is a [User](/docs/developers/protobufs/api#user) message
|
||||
Payload is a User message.
|
||||
ENCODING: Protobuf */
|
||||
meshtastic_PortNum_NODEINFO_APP = 4,
|
||||
/* Protocol control packets for mesh protocol use.
|
||||
Payload is a [Routing](/docs/developers/protobufs/api#routing) message
|
||||
Payload is a Routing message.
|
||||
ENCODING: Protobuf */
|
||||
meshtastic_PortNum_ROUTING_APP = 5,
|
||||
/* Admin control packets.
|
||||
Payload is a [AdminMessage](/docs/developers/protobufs/api#adminmessage) message
|
||||
Payload is a AdminMessage message.
|
||||
ENCODING: Protobuf */
|
||||
meshtastic_PortNum_ADMIN_APP = 6,
|
||||
/* Compressed TEXT_MESSAGE payloads.
|
||||
@@ -60,7 +60,7 @@ typedef enum _meshtastic_PortNum {
|
||||
any incoming TEXT_MESSAGE_COMPRESSED_APP payload and convert to TEXT_MESSAGE_APP. */
|
||||
meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP = 7,
|
||||
/* Waypoint payloads.
|
||||
Payload is a [Waypoint](/docs/developers/protobufs/api#waypoint) message
|
||||
Payload is a Waypoint message.
|
||||
ENCODING: Protobuf */
|
||||
meshtastic_PortNum_WAYPOINT_APP = 8,
|
||||
/* Audio Payloads.
|
||||
@@ -122,6 +122,8 @@ typedef enum _meshtastic_PortNum {
|
||||
/* ATAK Plugin
|
||||
Portnum for payloads from the official Meshtastic ATAK plugin */
|
||||
meshtastic_PortNum_ATAK_PLUGIN = 72,
|
||||
/* Provides unencrypted information about a node for consumption by a map via MQTT */
|
||||
meshtastic_PortNum_MAP_REPORT_APP = 73,
|
||||
/* Private applications should use portnums >= 256.
|
||||
To simplify initial development and testing you can use "PRIVATE_APP"
|
||||
in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */
|
||||
|
||||
@@ -41,7 +41,9 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
/* PM2.5 air quality sensor */
|
||||
meshtastic_TelemetrySensorType_PMSA003I = 13,
|
||||
/* INA3221 3 Channel Voltage / Current Sensor */
|
||||
meshtastic_TelemetrySensorType_INA3221 = 14
|
||||
meshtastic_TelemetrySensorType_INA3221 = 14,
|
||||
/* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280) */
|
||||
meshtastic_TelemetrySensorType_BMP085 = 15
|
||||
} meshtastic_TelemetrySensorType;
|
||||
|
||||
/* Struct definitions */
|
||||
@@ -141,8 +143,8 @@ extern "C" {
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_INA3221
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_INA3221+1))
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BMP085
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BMP085+1))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,530 @@
|
||||
/*
|
||||
Adds a WebServer and WebService callbacks to meshtastic as Linux Version. The WebServer & Webservices
|
||||
runs in a real linux thread beside the portdunio threading emulation. It replaces the complete ESP32
|
||||
Webserver libs including generation of SSL certifcicates, because the use ESP specific details in
|
||||
the lib that can't be emulated.
|
||||
|
||||
The WebServices adapt to the two major phoneapi functions "handleAPIv1FromRadio,handleAPIv1ToRadio"
|
||||
The WebServer just adds basaic support to deliver WebContent, so it can be used to
|
||||
deliver the WebGui definded by the WebClient Project.
|
||||
|
||||
Steps to get it running:
|
||||
1.) Add these Linux Libs to the compile and target machine:
|
||||
|
||||
sudo apt update && \
|
||||
apt -y install openssl libssl-dev libopenssl libsdl2-dev \
|
||||
libulfius-dev liborcania-dev
|
||||
|
||||
2.) Configure the root directory of the web Content in the config.yaml file.
|
||||
The followinng tags should be included and set at your needs
|
||||
|
||||
Example entry in the config.yaml
|
||||
Webserver:
|
||||
Port: 9001 # Port for Webserver & Webservices
|
||||
RootPath: /home/marc/web # Root Dir of WebServer
|
||||
|
||||
3.) Checkout the web project
|
||||
https://github.com/meshtastic/web.git
|
||||
|
||||
Build it and copy the content of the folder web/dist/* to the folder you did set as "RootPath"
|
||||
|
||||
!!!The WebServer should not be used as production system or exposed to the Internet. Its a raw basic version!!!
|
||||
|
||||
Author: Marc Philipp Hammermann
|
||||
mail: marchammermann@googlemail.com
|
||||
|
||||
*/
|
||||
#ifdef PORTDUINO_LINUX_HARDWARE
|
||||
#if __has_include(<ulfius.h>)
|
||||
#include "PiWebServer.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PhoneAPI.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "airtime.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
#include "sleep.h"
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <orcania.h>
|
||||
#include <string.h>
|
||||
#include <ulfius.h>
|
||||
#include <yder.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "PortduinoFS.h"
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
|
||||
#define DEFAULT_REALM "default_realm"
|
||||
#define PREFIX ""
|
||||
|
||||
struct _file_config configWeb;
|
||||
|
||||
// We need to specify some content-type mapping, so the resources get delivered with the
|
||||
// right content type and are displayed correctly in the browser
|
||||
char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"},
|
||||
{".js", "text/javascript"}, {".png", "image/png"},
|
||||
{".jpg", "image/jpg"}, {".gz", "application/gzip"},
|
||||
{".gif", "image/gif"}, {".json", "application/json"},
|
||||
{".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
|
||||
{".svg", "image/svg+xml"}, {".ts", "text/javascript"},
|
||||
{".tsx", "text/javascript"}, {"", ""}};
|
||||
|
||||
#undef str
|
||||
|
||||
volatile bool isWebServerReady;
|
||||
volatile bool isCertReady;
|
||||
|
||||
HttpAPI webAPI;
|
||||
|
||||
PiWebServerThread *piwebServerThread;
|
||||
|
||||
/**
|
||||
* Return the filename extension
|
||||
*/
|
||||
const char *get_filename_ext(const char *path)
|
||||
{
|
||||
const char *dot = strrchr(path, '.');
|
||||
if (!dot || dot == path)
|
||||
return "*";
|
||||
if (strchr(dot, '?') != NULL) {
|
||||
//*strchr(dot, '?') = '\0';
|
||||
const char *empty = "\0";
|
||||
return empty;
|
||||
}
|
||||
return dot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming callback function to ease sending large files
|
||||
*/
|
||||
static ssize_t callback_static_file_stream(void *cls, uint64_t pos, char *buf, size_t max)
|
||||
{
|
||||
(void)(pos);
|
||||
if (cls != NULL) {
|
||||
return fread(buf, 1, max, (FILE *)cls);
|
||||
} else {
|
||||
return U_STREAM_END;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup FILE* structure when streaming is complete
|
||||
*/
|
||||
static void callback_static_file_stream_free(void *cls)
|
||||
{
|
||||
if (cls != NULL) {
|
||||
fclose((FILE *)cls);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* static file callback endpoint that delivers the content for WebServer calls
|
||||
*/
|
||||
int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data)
|
||||
{
|
||||
size_t length;
|
||||
FILE *f;
|
||||
char *file_requested, *file_path, *url_dup_save, *real_path = NULL;
|
||||
const char *content_type;
|
||||
|
||||
/*
|
||||
* Comment this if statement if you don't access static files url from root dir, like /app
|
||||
*/
|
||||
if (request->callback_position > 0) {
|
||||
return U_CALLBACK_CONTINUE;
|
||||
} else if (user_data != NULL && (configWeb.files_path != NULL)) {
|
||||
file_requested = o_strdup(request->http_url);
|
||||
url_dup_save = file_requested;
|
||||
|
||||
while (file_requested[0] == '/') {
|
||||
file_requested++;
|
||||
}
|
||||
file_requested += o_strlen(configWeb.url_prefix);
|
||||
while (file_requested[0] == '/') {
|
||||
file_requested++;
|
||||
}
|
||||
|
||||
if (strchr(file_requested, '#') != NULL) {
|
||||
*strchr(file_requested, '#') = '\0';
|
||||
}
|
||||
|
||||
if (strchr(file_requested, '?') != NULL) {
|
||||
*strchr(file_requested, '?') = '\0';
|
||||
}
|
||||
|
||||
if (file_requested == NULL || o_strlen(file_requested) == 0 || 0 == o_strcmp("/", file_requested)) {
|
||||
o_free(url_dup_save);
|
||||
url_dup_save = file_requested = o_strdup("index.html");
|
||||
}
|
||||
|
||||
file_path = msprintf("%s/%s", configWeb.files_path, file_requested);
|
||||
real_path = realpath(file_path, NULL);
|
||||
if (0 == o_strncmp(configWeb.files_path, real_path, o_strlen(configWeb.files_path))) {
|
||||
if (access(file_path, F_OK) != -1) {
|
||||
f = fopen(file_path, "rb");
|
||||
if (f) {
|
||||
fseek(f, 0, SEEK_END);
|
||||
length = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
content_type = u_map_get_case(&configWeb.mime_types, get_filename_ext(file_requested));
|
||||
if (content_type == NULL) {
|
||||
content_type = u_map_get(&configWeb.mime_types, "*");
|
||||
LOG_DEBUG("Static File Server - Unknown mime type for extension %s \n", get_filename_ext(file_requested));
|
||||
}
|
||||
u_map_put(response->map_header, "Content-Type", content_type);
|
||||
u_map_copy_into(response->map_header, &configWeb.map_header);
|
||||
|
||||
if (ulfius_set_stream_response(response, 200, callback_static_file_stream, callback_static_file_stream_free,
|
||||
length, STATIC_FILE_CHUNK, f) != U_OK) {
|
||||
LOG_DEBUG("callback_static_file - Error ulfius_set_stream_response\n ");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (configWeb.redirect_on_404 == NULL) {
|
||||
ulfius_set_string_body_response(response, 404, "File not found");
|
||||
} else {
|
||||
ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
|
||||
response->status = 302;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (configWeb.redirect_on_404 == NULL) {
|
||||
ulfius_set_string_body_response(response, 404, "File not found");
|
||||
} else {
|
||||
ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
|
||||
response->status = 302;
|
||||
}
|
||||
}
|
||||
|
||||
o_free(file_path);
|
||||
o_free(url_dup_save);
|
||||
free(real_path); // realpath uses malloc
|
||||
return U_CALLBACK_CONTINUE;
|
||||
} else {
|
||||
LOG_DEBUG("Static File Server - Error, user_data is NULL or inconsistent\n");
|
||||
return U_CALLBACK_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static void handleWebResponse() {}
|
||||
|
||||
/*
|
||||
* Adapt the radioapi to the Webservice handleAPIv1ToRadio
|
||||
* Trigger : WebGui(SAVE)->WebServcice->phoneApi
|
||||
*/
|
||||
int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
|
||||
{
|
||||
LOG_DEBUG("handleAPIv1ToRadio web -> radio \n");
|
||||
|
||||
ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
|
||||
ulfius_add_header_to_response(res, "Access-Control-Allow-Headers", "Content-Type");
|
||||
ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
|
||||
ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "PUT, OPTIONS");
|
||||
ulfius_add_header_to_response(res, "X-Protobuf-Schema",
|
||||
"https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
|
||||
|
||||
if (req->http_verb == "OPTIONS") {
|
||||
ulfius_set_response_properties(res, U_OPT_STATUS, 204);
|
||||
return U_CALLBACK_CONTINUE;
|
||||
}
|
||||
|
||||
byte buffer[MAX_TO_FROM_RADIO_SIZE];
|
||||
size_t s = req->binary_body_length;
|
||||
|
||||
memcpy(buffer, req->binary_body, MAX_TO_FROM_RADIO_SIZE);
|
||||
|
||||
// FIXME* Problem with portdunio loosing mountpoint maybe because of running in a real sep. thread
|
||||
|
||||
portduinoVFS->mountpoint("/home/marc/.portduino/default");
|
||||
|
||||
LOG_DEBUG("Received %d bytes from PUT request\n", s);
|
||||
webAPI.handleToRadio(buffer, s);
|
||||
LOG_DEBUG("end web->radio \n");
|
||||
return U_CALLBACK_COMPLETE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adapt the radioapi to the Webservice handleAPIv1FromRadio
|
||||
* Trigger : WebGui(POLL)->handleAPIv1FromRadio->phoneapi->Meshtastic(Radio) events
|
||||
*/
|
||||
int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
|
||||
{
|
||||
|
||||
// LOG_DEBUG("handleAPIv1FromRadio radio -> web\n");
|
||||
std::string valueAll;
|
||||
|
||||
// Status code is 200 OK by default.
|
||||
ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
|
||||
ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
|
||||
ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "GET");
|
||||
ulfius_add_header_to_response(res, "X-Protobuf-Schema",
|
||||
"https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
|
||||
|
||||
uint8_t txBuf[MAX_STREAM_BUF_SIZE];
|
||||
uint32_t len = 1;
|
||||
|
||||
if (valueAll == "true") {
|
||||
while (len) {
|
||||
len = webAPI.getFromRadio(txBuf);
|
||||
ulfius_set_response_properties(res, U_OPT_STATUS, 200, U_OPT_BINARY_BODY, txBuf, len);
|
||||
const char *tmpa = (const char *)txBuf;
|
||||
ulfius_set_string_body_response(res, 200, tmpa);
|
||||
// LOG_DEBUG("\n----webAPI response all:----\n");
|
||||
LOG_DEBUG(tmpa);
|
||||
LOG_DEBUG("\n");
|
||||
}
|
||||
// Otherwise, just return one protobuf
|
||||
} else {
|
||||
len = webAPI.getFromRadio(txBuf);
|
||||
const char *tmpa = (const char *)txBuf;
|
||||
ulfius_set_binary_body_response(res, 200, tmpa, len);
|
||||
// LOG_DEBUG("\n----webAPI response:\n");
|
||||
LOG_DEBUG(tmpa);
|
||||
LOG_DEBUG("\n");
|
||||
}
|
||||
|
||||
// LOG_DEBUG("end radio->web\n", len);
|
||||
return U_CALLBACK_COMPLETE;
|
||||
}
|
||||
|
||||
/*
|
||||
OpenSSL RSA Key Gen
|
||||
*/
|
||||
int generate_rsa_key(EVP_PKEY **pkey)
|
||||
{
|
||||
EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
|
||||
if (!pkey_ctx)
|
||||
return -1;
|
||||
if (EVP_PKEY_keygen_init(pkey_ctx) <= 0)
|
||||
return -1;
|
||||
if (EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_ctx, 2048) <= 0)
|
||||
return -1;
|
||||
if (EVP_PKEY_keygen(pkey_ctx, pkey) <= 0)
|
||||
return -1;
|
||||
EVP_PKEY_CTX_free(pkey_ctx);
|
||||
return 0; // SUCCESS
|
||||
}
|
||||
|
||||
int generate_self_signed_x509(EVP_PKEY *pkey, X509 **x509)
|
||||
{
|
||||
*x509 = X509_new();
|
||||
if (!*x509)
|
||||
return -1;
|
||||
if (X509_set_version(*x509, 2) != 1)
|
||||
return -1;
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(*x509), 1);
|
||||
X509_gmtime_adj(X509_get_notBefore(*x509), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(*x509), 31536000L); // 1 YEAR ACCESS
|
||||
|
||||
X509_set_pubkey(*x509, pkey);
|
||||
|
||||
// SET Subject Name
|
||||
X509_NAME *name = X509_get_subject_name(*x509);
|
||||
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"DE", -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Meshtastic", -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"meshtastic.local", -1, -1, 0);
|
||||
// Selfsigned, Issuer = Subject
|
||||
X509_set_issuer_name(*x509, name);
|
||||
|
||||
// Certificate signed with our privte key
|
||||
if (X509_sign(*x509, pkey, EVP_sha256()) <= 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *read_file_into_string(const char *filename)
|
||||
{
|
||||
FILE *file = fopen(filename, "rb");
|
||||
if (file == NULL) {
|
||||
LOG_ERROR("Error reading File : %s \n", filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Size of file
|
||||
fseek(file, 0, SEEK_END);
|
||||
long filesize = ftell(file);
|
||||
rewind(file);
|
||||
|
||||
// reserve mem for file + 1 byte
|
||||
char *buffer = (char *)malloc(filesize + 1);
|
||||
if (buffer == NULL) {
|
||||
LOG_ERROR("Malloc of mem failed for file : %s \n", filename);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// read content
|
||||
size_t readSize = fread(buffer, 1, filesize, file);
|
||||
if (readSize != filesize) {
|
||||
LOG_ERROR("Error reading file into buffer\n");
|
||||
free(buffer);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// add terminator sign at the end
|
||||
buffer[filesize] = '\0';
|
||||
fclose(file);
|
||||
return buffer; // return pointer
|
||||
}
|
||||
|
||||
int PiWebServerThread::CheckSSLandLoad()
|
||||
{
|
||||
// read certificate
|
||||
cert_pem = read_file_into_string("certificate.pem");
|
||||
if (cert_pem == NULL) {
|
||||
LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing\n");
|
||||
return 1;
|
||||
}
|
||||
// read private key
|
||||
key_pem = read_file_into_string("private_key.pem");
|
||||
if (key_pem == NULL) {
|
||||
LOG_ERROR("ERROR file private_key can't be loaded or is missing\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PiWebServerThread::CreateSSLCertificate()
|
||||
{
|
||||
|
||||
EVP_PKEY *pkey = NULL;
|
||||
X509 *x509 = NULL;
|
||||
|
||||
if (generate_rsa_key(&pkey) != 0) {
|
||||
LOG_ERROR("Error generating RSA-Key.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (generate_self_signed_x509(pkey, &x509) != 0) {
|
||||
LOG_ERROR("Error generating of X509-Certificat.\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Ope file to write private key file
|
||||
FILE *pkey_file = fopen("private_key.pem", "wb");
|
||||
if (!pkey_file) {
|
||||
LOG_ERROR("Error opening private key file.\n");
|
||||
return 3;
|
||||
}
|
||||
// write private key file
|
||||
PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL);
|
||||
fclose(pkey_file);
|
||||
|
||||
// open Certificate file
|
||||
FILE *x509_file = fopen("certificate.pem", "wb");
|
||||
if (!x509_file) {
|
||||
LOG_ERROR("Error opening certificate.\n");
|
||||
return 4;
|
||||
}
|
||||
// write cirtificate
|
||||
PEM_write_X509(x509_file, x509);
|
||||
fclose(x509_file);
|
||||
|
||||
EVP_PKEY_free(pkey);
|
||||
X509_free(x509);
|
||||
LOG_INFO("Create SSL Certifictate -certificate.pem- succesfull \n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void initWebServer() {}
|
||||
|
||||
PiWebServerThread::PiWebServerThread()
|
||||
{
|
||||
int ret, retssl, webservport;
|
||||
|
||||
if (CheckSSLandLoad() != 0) {
|
||||
CreateSSLCertificate();
|
||||
if (CheckSSLandLoad() != 0) {
|
||||
LOG_ERROR("Major Error Gen & Read SSL Certificate\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsMap[webserverport] != 0) {
|
||||
webservport = settingsMap[webserverport];
|
||||
LOG_INFO("Using webserver port from yaml config. %i \n", webservport);
|
||||
} else {
|
||||
LOG_INFO("Webserver port in yaml config set to 0, so defaulting to port 443.\n");
|
||||
webservport = 443;
|
||||
}
|
||||
|
||||
// Web Content Service Instance
|
||||
if (ulfius_init_instance(&instanceWeb, webservport, NULL, DEFAULT_REALM) != U_OK) {
|
||||
LOG_ERROR("Webserver couldn't be started, abort execution\n");
|
||||
} else {
|
||||
|
||||
LOG_INFO("Webserver started ....\n");
|
||||
u_map_init(&configWeb.mime_types);
|
||||
u_map_put(&configWeb.mime_types, "*", "application/octet-stream");
|
||||
u_map_put(&configWeb.mime_types, ".html", "text/html");
|
||||
u_map_put(&configWeb.mime_types, ".htm", "text/html");
|
||||
u_map_put(&configWeb.mime_types, ".tsx", "application/javascript");
|
||||
u_map_put(&configWeb.mime_types, ".ts", "application/javascript");
|
||||
u_map_put(&configWeb.mime_types, ".css", "text/css");
|
||||
u_map_put(&configWeb.mime_types, ".js", "application/javascript");
|
||||
u_map_put(&configWeb.mime_types, ".json", "application/json");
|
||||
u_map_put(&configWeb.mime_types, ".png", "image/png");
|
||||
u_map_put(&configWeb.mime_types, ".gif", "image/gif");
|
||||
u_map_put(&configWeb.mime_types, ".jpeg", "image/jpeg");
|
||||
u_map_put(&configWeb.mime_types, ".jpg", "image/jpeg");
|
||||
u_map_put(&configWeb.mime_types, ".ttf", "font/ttf");
|
||||
u_map_put(&configWeb.mime_types, ".woff", "font/woff");
|
||||
u_map_put(&configWeb.mime_types, ".ico", "image/x-icon");
|
||||
u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml");
|
||||
|
||||
webrootpath = settingsStrings[webserverrootpath];
|
||||
|
||||
configWeb.files_path = (char *)webrootpath.c_str();
|
||||
configWeb.url_prefix = "";
|
||||
configWeb.rootPath = strdup(portduinoVFS->mountpoint());
|
||||
|
||||
u_map_put(instanceWeb.default_headers, "Access-Control-Allow-Origin", "*");
|
||||
// Maximum body size sent by the client is 1 Kb
|
||||
instanceWeb.max_post_body_size = 1024;
|
||||
ulfius_add_endpoint_by_val(&instanceWeb, "GET", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, NULL);
|
||||
ulfius_add_endpoint_by_val(&instanceWeb, "PUT", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, configWeb.rootPath);
|
||||
|
||||
// Add callback function to all endpoints for the Web Server
|
||||
ulfius_add_endpoint_by_val(&instanceWeb, "GET", NULL, "/*", 2, &callback_static_file, &configWeb);
|
||||
|
||||
// thats for serving without SSL
|
||||
// retssl = ulfius_start_framework(&instanceWeb);
|
||||
|
||||
// thats for serving with SSL
|
||||
retssl = ulfius_start_secure_framework(&instanceWeb, key_pem, cert_pem);
|
||||
|
||||
if (retssl == U_OK) {
|
||||
LOG_INFO("Web Server framework started on port: %i \n", webservport);
|
||||
LOG_INFO("Web Server root %s\n", (char *)webrootpath.c_str());
|
||||
} else {
|
||||
LOG_ERROR("Error starting Web Server framework\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PiWebServerThread::~PiWebServerThread()
|
||||
{
|
||||
u_map_clean(&configWeb.mime_types);
|
||||
|
||||
ulfius_stop_framework(&instanceWeb);
|
||||
ulfius_stop_framework(&instanceWeb);
|
||||
free(configWeb.rootPath);
|
||||
ulfius_clean_instance(&instanceService);
|
||||
ulfius_clean_instance(&instanceService);
|
||||
free(cert_pem);
|
||||
LOG_INFO("End framework");
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#ifdef PORTDUINO_LINUX_HARDWARE
|
||||
#if __has_include(<ulfius.h>)
|
||||
#include "PhoneAPI.h"
|
||||
#include "ulfius-cfg.h"
|
||||
#include "ulfius.h"
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
#define STATIC_FILE_CHUNK 256
|
||||
|
||||
void initWebServer();
|
||||
void createSSLCert();
|
||||
int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data);
|
||||
const char *get_filename_ext(const char *path);
|
||||
|
||||
struct _file_config {
|
||||
char *files_path;
|
||||
char *url_prefix;
|
||||
struct _u_map mime_types;
|
||||
struct _u_map map_header;
|
||||
char *redirect_on_404;
|
||||
char *rootPath;
|
||||
};
|
||||
|
||||
class PiWebServerThread
|
||||
{
|
||||
private:
|
||||
char *key_pem = NULL;
|
||||
char *cert_pem = NULL;
|
||||
// struct _u_map mime_types;
|
||||
std::string webrootpath;
|
||||
|
||||
public:
|
||||
PiWebServerThread();
|
||||
~PiWebServerThread();
|
||||
int CreateSSLCertificate();
|
||||
int CheckSSLandLoad();
|
||||
uint32_t requestRestart = 0;
|
||||
struct _u_instance instanceWeb;
|
||||
struct _u_instance instanceService;
|
||||
};
|
||||
|
||||
class HttpAPI : public PhoneAPI
|
||||
{
|
||||
|
||||
public:
|
||||
// Nothing here yet
|
||||
|
||||
private:
|
||||
// Nothing here yet
|
||||
|
||||
protected:
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this
|
||||
};
|
||||
|
||||
extern PiWebServerThread *piwebServerThread;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -18,40 +18,7 @@
|
||||
#define INPUTBROKER_MATRIX_TYPE 0
|
||||
#endif
|
||||
|
||||
#ifdef OLED_RU
|
||||
#include "graphics/fonts/OLEDDisplayFontsRU.h"
|
||||
#endif
|
||||
|
||||
#ifdef OLED_UA
|
||||
#include "graphics/fonts/OLEDDisplayFontsUA.h"
|
||||
#endif
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16
|
||||
#define FONT_MEDIUM ArialMT_Plain_24
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_SMALL ArialMT_Plain_10_RU
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_SMALL ArialMT_Plain_10_UA
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10
|
||||
#endif
|
||||
#endif
|
||||
#define FONT_MEDIUM ArialMT_Plain_16
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#endif
|
||||
|
||||
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
|
||||
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
|
||||
#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
|
||||
#include "graphics/ScreenFonts.h"
|
||||
|
||||
// Remove Canned message screen if no action is taken for some milliseconds
|
||||
#define INACTIVATE_AFTER_MS 20000
|
||||
|
||||
+63
-13
@@ -1,74 +1,112 @@
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
#include "input/InputBroker.h"
|
||||
#include "input/RotaryEncoderInterruptImpl1.h"
|
||||
#include "input/TrackballInterruptImpl1.h"
|
||||
#include "input/UpDownInterruptImpl1.h"
|
||||
#include "input/cardKbI2cImpl.h"
|
||||
#include "input/kbMatrixImpl.h"
|
||||
#endif
|
||||
#include "modules/AdminModule.h"
|
||||
#if !MESHTASTIC_EXCLUDE_ATAK
|
||||
#include "modules/AtakPluginModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
|
||||
#include "modules/CannedMessageModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
|
||||
#include "modules/DetectionSensorModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
|
||||
#include "modules/NeighborInfoModule.h"
|
||||
#endif
|
||||
#include "modules/NodeInfoModule.h"
|
||||
#include "modules/PositionModule.h"
|
||||
#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
|
||||
#include "modules/RemoteHardwareModule.h"
|
||||
#include "modules/ReplyModule.h"
|
||||
#endif
|
||||
#include "modules/RoutingModule.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
#include "modules/TraceRouteModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_WAYPOINT
|
||||
#include "modules/WaypointModule.h"
|
||||
#endif
|
||||
#if ARCH_PORTDUINO
|
||||
#include "input/LinuxInputImpl.h"
|
||||
#endif
|
||||
#if HAS_TELEMETRY
|
||||
#include "modules/Telemetry/DeviceTelemetry.h"
|
||||
#endif
|
||||
#if HAS_SENSOR
|
||||
#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
#include "modules/Telemetry/AirQualityTelemetry.h"
|
||||
#include "modules/Telemetry/EnvironmentTelemetry.h"
|
||||
#endif
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY
|
||||
#include "modules/Telemetry/PowerTelemetry.h"
|
||||
#endif
|
||||
#ifdef ARCH_ESP32
|
||||
#ifdef USE_SX1280
|
||||
#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO
|
||||
#include "modules/esp32/AudioModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
|
||||
#include "modules/esp32/PaxcounterModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
||||
#include "modules/esp32/StoreForwardModule.h"
|
||||
#endif
|
||||
#endif
|
||||
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)
|
||||
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_RANGETEST
|
||||
#include "modules/RangeTestModule.h"
|
||||
#endif
|
||||
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
#if !MESHTASTIC_EXCLUDE_SERIAL
|
||||
#include "modules/SerialModule.h"
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
/**
|
||||
* Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else)
|
||||
*/
|
||||
void setupModules()
|
||||
{
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
|
||||
#if HAS_BUTTON || ARCH_PORTDUINO
|
||||
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
inputBroker = new InputBroker();
|
||||
#endif
|
||||
adminModule = new AdminModule();
|
||||
nodeInfoModule = new NodeInfoModule();
|
||||
positionModule = new PositionModule();
|
||||
#if !MESHTASTIC_EXCLUDE_WAYPOINT
|
||||
waypointModule = new WaypointModule();
|
||||
#endif
|
||||
textMessageModule = new TextMessageModule();
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
traceRouteModule = new TraceRouteModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
|
||||
neighborInfoModule = new NeighborInfoModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
|
||||
detectionSensorModule = new DetectionSensorModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_ATAK
|
||||
atakPluginModule = new AtakPluginModule();
|
||||
#endif
|
||||
// Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
|
||||
// to a global variable.
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
|
||||
new RemoteHardwareModule();
|
||||
new ReplyModule();
|
||||
#if HAS_BUTTON || ARCH_PORTDUINO
|
||||
#endif
|
||||
// Example: Put your module here
|
||||
// new ReplyModule();
|
||||
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
|
||||
if (!rotaryEncoderInterruptImpl1->init()) {
|
||||
delete rotaryEncoderInterruptImpl1;
|
||||
@@ -90,47 +128,59 @@ void setupModules()
|
||||
aLinuxInputImpl = new LinuxInputImpl();
|
||||
aLinuxInputImpl->init();
|
||||
#endif
|
||||
#if HAS_TRACKBALL
|
||||
#if HAS_TRACKBALL && !MESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
trackballInterruptImpl1 = new TrackballInterruptImpl1();
|
||||
trackballInterruptImpl1->init();
|
||||
#endif
|
||||
#if HAS_SCREEN
|
||||
#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
|
||||
cannedMessageModule = new CannedMessageModule();
|
||||
#endif
|
||||
#if HAS_TELEMETRY
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||
new DeviceTelemetryModule();
|
||||
#endif
|
||||
#if HAS_SENSOR
|
||||
#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
new EnvironmentTelemetryModule();
|
||||
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
|
||||
new AirQualityTelemetryModule();
|
||||
}
|
||||
#endif
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY
|
||||
new PowerTelemetryModule();
|
||||
#endif
|
||||
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
|
||||
!defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if !MESHTASTIC_EXCLUDE_SERIAL
|
||||
new SerialModule();
|
||||
#endif
|
||||
#endif
|
||||
#ifdef ARCH_ESP32
|
||||
// Only run on an esp32 based device.
|
||||
#ifdef USE_SX1280
|
||||
#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO
|
||||
audioModule = new AudioModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
||||
storeForwardModule = new StoreForwardModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
|
||||
paxcounterModule = new PaxcounterModule();
|
||||
#endif
|
||||
#endif
|
||||
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)
|
||||
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
|
||||
externalNotificationModule = new ExternalNotificationModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_RANGETEST
|
||||
new RangeTestModule();
|
||||
#endif
|
||||
#endif
|
||||
} else {
|
||||
adminModule = new AdminModule();
|
||||
#if HAS_TELEMETRY
|
||||
new DeviceTelemetryModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
traceRouteModule = new TraceRouteModule();
|
||||
#endif
|
||||
}
|
||||
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
|
||||
// acks
|
||||
|
||||
@@ -95,6 +95,7 @@ NeighborInfoModule::NeighborInfoModule()
|
||||
ourPortNum = meshtastic_PortNum_NEIGHBORINFO_APP;
|
||||
|
||||
if (moduleConfig.neighbor_info.enabled) {
|
||||
isPromiscuous = true; // Update neighbors from all packets
|
||||
this->loadProtoForModule();
|
||||
setIntervalFromNow(35 * 1000);
|
||||
} else {
|
||||
@@ -202,9 +203,12 @@ Pass it to an upper client; do not persist this data on the mesh
|
||||
*/
|
||||
bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np)
|
||||
{
|
||||
if (enabled) {
|
||||
if (np) {
|
||||
printNeighborInfo("RECEIVED", np);
|
||||
updateNeighbors(mp, np);
|
||||
} else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
|
||||
// If the hopLimit is the same as hopStart, then it is a neighbor
|
||||
getOrCreateNeighbor(mp.from, mp.from, 0, mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
|
||||
}
|
||||
// Allow others to handle this packet
|
||||
return false;
|
||||
@@ -261,7 +265,7 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
|
||||
nbr->snr = snr;
|
||||
nbr->last_rx_time = getTime();
|
||||
// Only if this is the original sender, the broadcast interval corresponds to it
|
||||
if (originalSender == n)
|
||||
if (originalSender == n && node_broadcast_interval_secs != 0)
|
||||
nbr->node_broadcast_interval_secs = node_broadcast_interval_secs;
|
||||
saveProtoForModule(); // Save the updated neighbor
|
||||
return nbr;
|
||||
@@ -277,8 +281,10 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
|
||||
new_nbr->snr = snr;
|
||||
new_nbr->last_rx_time = getTime();
|
||||
// Only if this is the original sender, the broadcast interval corresponds to it
|
||||
if (originalSender == n)
|
||||
if (originalSender == n && node_broadcast_interval_secs != 0)
|
||||
new_nbr->node_broadcast_interval_secs = node_broadcast_interval_secs;
|
||||
else // Assume the same broadcast interval as us for the neighbor if we don't know it
|
||||
new_nbr->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval;
|
||||
saveProtoForModule(); // Save the new neighbor
|
||||
return new_nbr;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ class NeighborInfoModule : public ProtobufModule<meshtastic_NeighborInfo>, priva
|
||||
/* Does our periodic broadcast */
|
||||
int32_t runOnce() override;
|
||||
|
||||
// Override wantPacket to say we want to see all packets when enabled, not just those for our port number
|
||||
virtual bool wantPacket(const meshtastic_MeshPacket *p) override { return enabled; }
|
||||
|
||||
/* These are for debugging only */
|
||||
void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np);
|
||||
void printNodeDBNodes(const char *header);
|
||||
|
||||
@@ -116,6 +116,11 @@ meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
}
|
||||
localPosition.seq_number++;
|
||||
|
||||
if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) {
|
||||
LOG_WARN("Skipping position send because lat/lon are zero!\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// lat/lon are unconditionally included - IF AVAILABLE!
|
||||
LOG_DEBUG("Sending location with precision %i\n", precision);
|
||||
if (precision < 32 && precision > 0) {
|
||||
|
||||
@@ -113,8 +113,8 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
|
||||
meshtastic_MeshPacket *p = allocDataPacket();
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
|
||||
p->want_ack = true;
|
||||
p->hop_limit = 0;
|
||||
p->want_ack = false;
|
||||
|
||||
packetSequence++;
|
||||
|
||||
@@ -295,4 +295,4 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,13 +36,28 @@ meshtastic_MeshPacket *RoutingModule::allocReply()
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex)
|
||||
void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart,
|
||||
uint8_t hopLimit)
|
||||
{
|
||||
auto p = allocAckNak(err, to, idFrom, chIndex);
|
||||
auto p = allocAckNak(err, to, idFrom, chIndex, hopStart, hopLimit);
|
||||
|
||||
router->sendLocal(p); // we sometimes send directly to the local node
|
||||
}
|
||||
|
||||
uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit)
|
||||
{
|
||||
if (hopStart != 0) {
|
||||
// Hops used by the request. If somebody in between running modified firmware modified it, ignore it
|
||||
uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit;
|
||||
if (hopsUsed > config.lora.hop_limit) {
|
||||
return hopsUsed; // If the request used more hops than the limit, use the same amount of hops
|
||||
} else if (hopsUsed + 2 < config.lora.hop_limit) {
|
||||
return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different
|
||||
}
|
||||
}
|
||||
return config.lora.hop_limit; // Use the default hop limit
|
||||
}
|
||||
|
||||
RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
|
||||
{
|
||||
isPromiscuous = true;
|
||||
|
||||
@@ -13,7 +13,11 @@ class RoutingModule : public ProtobufModule<meshtastic_Routing>
|
||||
*/
|
||||
RoutingModule();
|
||||
|
||||
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
|
||||
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart = 0,
|
||||
uint8_t hopLimit = 0);
|
||||
|
||||
// Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response
|
||||
uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit);
|
||||
|
||||
protected:
|
||||
friend class Router;
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
// Sensors
|
||||
#include "Sensor/BME280Sensor.h"
|
||||
#include "Sensor/BME680Sensor.h"
|
||||
#include "Sensor/BMP085Sensor.h"
|
||||
#include "Sensor/BMP280Sensor.h"
|
||||
#include "Sensor/LPS22HBSensor.h"
|
||||
#include "Sensor/MCP9808Sensor.h"
|
||||
#include "Sensor/SHT31Sensor.h"
|
||||
#include "Sensor/SHTC3Sensor.h"
|
||||
|
||||
BMP085Sensor bmp085Sensor;
|
||||
BMP280Sensor bmp280Sensor;
|
||||
BME280Sensor bme280Sensor;
|
||||
BME680Sensor bme680Sensor;
|
||||
@@ -33,23 +35,7 @@ SHT31Sensor sht31Sensor;
|
||||
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
|
||||
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16
|
||||
#define FONT_MEDIUM ArialMT_Plain_24
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10
|
||||
#define FONT_MEDIUM ArialMT_Plain_16
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#endif
|
||||
|
||||
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
|
||||
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
|
||||
#include "graphics/ScreenFonts.h"
|
||||
|
||||
int32_t EnvironmentTelemetryModule::runOnce()
|
||||
{
|
||||
@@ -83,6 +69,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
LOG_INFO("Environment Telemetry: Initializing\n");
|
||||
// it's possible to have this module enabled, only for displaying values on the screen.
|
||||
// therefore, we should only enable the sensor loop if measurement is also enabled
|
||||
if (bmp085Sensor.hasSensor())
|
||||
result = bmp085Sensor.runOnce();
|
||||
if (bmp280Sensor.hasSensor())
|
||||
result = bmp280Sensor.runOnce();
|
||||
if (bme280Sensor.hasSensor())
|
||||
@@ -235,6 +223,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
valid = lps22hbSensor.getMetrics(&m);
|
||||
if (shtc3Sensor.hasSensor())
|
||||
valid = shtc3Sensor.getMetrics(&m);
|
||||
if (bmp085Sensor.hasSensor())
|
||||
valid = bmp085Sensor.getMetrics(&m);
|
||||
if (bmp280Sensor.hasSensor())
|
||||
valid = bmp280Sensor.getMetrics(&m);
|
||||
if (bme280Sensor.hasSensor())
|
||||
|
||||
@@ -14,23 +14,7 @@
|
||||
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
|
||||
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16
|
||||
#define FONT_MEDIUM ArialMT_Plain_24
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10
|
||||
#define FONT_MEDIUM ArialMT_Plain_16
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#endif
|
||||
|
||||
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
|
||||
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
|
||||
#include "graphics/ScreenFonts.h"
|
||||
|
||||
int32_t PowerTelemetryModule::runOnce()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "BMP085Sensor.h"
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include "configuration.h"
|
||||
#include <Adafruit_BMP085.h>
|
||||
#include <typeinfo>
|
||||
|
||||
BMP085Sensor::BMP085Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP085, "BMP085") {}
|
||||
|
||||
int32_t BMP085Sensor::runOnce()
|
||||
{
|
||||
LOG_INFO("Init sensor: %s\n", sensorName);
|
||||
if (!hasSensor()) {
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
bmp085 = Adafruit_BMP085();
|
||||
status = bmp085.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
|
||||
|
||||
return initI2CSensor();
|
||||
}
|
||||
|
||||
void BMP085Sensor::setup() {}
|
||||
|
||||
bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
LOG_DEBUG("BMP085Sensor::getMetrics\n");
|
||||
measurement->variant.environment_metrics.temperature = bmp085.readTemperature();
|
||||
measurement->variant.environment_metrics.barometric_pressure = bmp085.readPressure() / 100.0F;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include <Adafruit_BMP085.h>
|
||||
|
||||
class BMP085Sensor : public TelemetrySensor
|
||||
{
|
||||
private:
|
||||
Adafruit_BMP085 bmp085;
|
||||
|
||||
protected:
|
||||
virtual void setup() override;
|
||||
|
||||
public:
|
||||
BMP085Sensor();
|
||||
virtual int32_t runOnce() override;
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||
};
|
||||
@@ -12,7 +12,8 @@ int32_t SHT31Sensor::runOnce()
|
||||
if (!hasSensor()) {
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
status = sht31.begin();
|
||||
sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second);
|
||||
status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first);
|
||||
return initI2CSensor();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class SHT31Sensor : public TelemetrySensor
|
||||
{
|
||||
private:
|
||||
Adafruit_SHT31 sht31 = Adafruit_SHT31();
|
||||
Adafruit_SHT31 sht31;
|
||||
|
||||
protected:
|
||||
virtual void setup() override;
|
||||
|
||||
@@ -8,14 +8,6 @@
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
|
||||
#ifdef OLED_RU
|
||||
#include "graphics/fonts/OLEDDisplayFontsRU.h"
|
||||
#endif
|
||||
|
||||
#ifdef OLED_UA
|
||||
#include "graphics/fonts/OLEDDisplayFontsUA.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
AudioModule
|
||||
A interface to send raw codec2 audio data over the mesh network. Based on the example code from the ESP32_codec2 project.
|
||||
@@ -48,32 +40,7 @@ AudioModule *audioModule;
|
||||
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x)
|
||||
#endif
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16
|
||||
#define FONT_MEDIUM ArialMT_Plain_24
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_SMALL ArialMT_Plain_10_RU
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_SMALL ArialMT_Plain_10_UA
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10
|
||||
#endif
|
||||
#endif
|
||||
#define FONT_MEDIUM ArialMT_Plain_16
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#endif
|
||||
|
||||
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
|
||||
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
|
||||
#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
|
||||
#include "graphics/ScreenFonts.h"
|
||||
|
||||
void run_codec2(void *parameter)
|
||||
{
|
||||
|
||||
@@ -7,9 +7,19 @@
|
||||
|
||||
PaxcounterModule *paxcounterModule;
|
||||
|
||||
void NullFunc(){};
|
||||
|
||||
// paxcounterModule->sendInfo(NODENUM_BROADCAST);
|
||||
/**
|
||||
* Callback function for libpax.
|
||||
* We only clear our sent flag here, since this function is called from another thread, so we
|
||||
* cannot send to the mesh directly.
|
||||
*/
|
||||
void PaxcounterModule::handlePaxCounterReportRequest()
|
||||
{
|
||||
// The libpax library already updated our data structure, just before invoking this callback.
|
||||
LOG_INFO("PaxcounterModule: libpax reported new data: wifi=%d; ble=%d; uptime=%lu\n",
|
||||
paxcounterModule->count_from_libpax.wifi_count, paxcounterModule->count_from_libpax.ble_count, millis() / 1000);
|
||||
paxcounterModule->reportedDataSent = false;
|
||||
paxcounterModule->setIntervalFromNow(0);
|
||||
}
|
||||
|
||||
PaxcounterModule::PaxcounterModule()
|
||||
: concurrency::OSThread("PaxcounterModule"),
|
||||
@@ -17,11 +27,20 @@ PaxcounterModule::PaxcounterModule()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the Pax information to the mesh if we got new data from libpax.
|
||||
* This is called periodically from our runOnce() method and will actually send the data to the mesh
|
||||
* if libpax updated it since the last transmission through the callback.
|
||||
* @param dest - destination node (usually NODENUM_BROADCAST)
|
||||
* @return false if sending is unnecessary, true if information was sent
|
||||
*/
|
||||
bool PaxcounterModule::sendInfo(NodeNum dest)
|
||||
{
|
||||
libpax_counter_count(&count_from_libpax);
|
||||
LOG_INFO("(Sending): pax: wifi=%d; ble=%d; uptime=%d\n", count_from_libpax.wifi_count, count_from_libpax.ble_count,
|
||||
millis() / 1000);
|
||||
if (paxcounterModule->reportedDataSent)
|
||||
return false;
|
||||
|
||||
LOG_INFO("PaxcounterModule: sending pax info wifi=%d; ble=%d; uptime=%lu\n", count_from_libpax.wifi_count,
|
||||
count_from_libpax.ble_count, millis() / 1000);
|
||||
|
||||
meshtastic_Paxcount pl = meshtastic_Paxcount_init_default;
|
||||
pl.wifi = count_from_libpax.wifi_count;
|
||||
@@ -34,6 +53,9 @@ bool PaxcounterModule::sendInfo(NodeNum dest)
|
||||
p->priority = meshtastic_MeshPacket_Priority_MIN;
|
||||
|
||||
service.sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
|
||||
paxcounterModule->reportedDataSent = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -67,7 +89,7 @@ int32_t PaxcounterModule::runOnce()
|
||||
libpax_default_config(&configuration);
|
||||
|
||||
configuration.blecounter = 1;
|
||||
configuration.blescantime = 0; // infinit
|
||||
configuration.blescantime = 0; // infinite
|
||||
configuration.wificounter = 1;
|
||||
configuration.wifi_channel_map = WIFI_CHANNEL_ALL;
|
||||
configuration.wifi_channel_switch_interval = 50;
|
||||
@@ -76,7 +98,8 @@ int32_t PaxcounterModule::runOnce()
|
||||
libpax_update_config(&configuration);
|
||||
|
||||
// internal processing initialization
|
||||
libpax_counter_init(NullFunc, &count_from_libpax, UINT16_MAX, 1);
|
||||
libpax_counter_init(handlePaxCounterReportRequest, &count_from_libpax,
|
||||
moduleConfig.paxcounter.paxcounter_update_interval, 0);
|
||||
libpax_counter_start();
|
||||
} else {
|
||||
sendInfo(NODENUM_BROADCAST);
|
||||
@@ -89,38 +112,7 @@ int32_t PaxcounterModule::runOnce()
|
||||
|
||||
#if HAS_SCREEN
|
||||
|
||||
#ifdef OLED_RU
|
||||
#include "graphics/fonts/OLEDDisplayFontsRU.h"
|
||||
#endif
|
||||
|
||||
#ifdef OLED_UA
|
||||
#include "graphics/fonts/OLEDDisplayFontsUA.h"
|
||||
#endif
|
||||
|
||||
// TODO / FIXME: This code is copied from src/graphics/Screen.cpp
|
||||
// It appears (in slightly variants) also in other modules like
|
||||
// src/modules/Telemetry/PowerTelemetry.cpp, src/modules/Telemetry/EnvironmentTelemetry.cpp
|
||||
// and src/modules/CannedMessageModule.cpp
|
||||
// It probably should go to a common header file for consistency
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16 // Height: 19
|
||||
#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_SMALL ArialMT_Plain_10_RU
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_SMALL ArialMT_Plain_10_UA
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10 // Height: 13
|
||||
#endif
|
||||
#endif
|
||||
#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#endif
|
||||
#include "graphics/ScreenFonts.h"
|
||||
|
||||
void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
@@ -138,4 +130,4 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
}
|
||||
#endif // HAS_SCREEN
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
#include <libpax_api.h>
|
||||
|
||||
/**
|
||||
* A simple example module that just replies with "Message received" to any message it receives.
|
||||
* Wrapper module for the estimate passenger (PAX) count library (https://github.com/dbinfrago/libpax) which
|
||||
* implements the core functionality of the ESP32 Paxcounter project (https://github.com/cyberman54/ESP32-Paxcounter)
|
||||
*/
|
||||
class PaxcounterModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Paxcount>
|
||||
{
|
||||
bool firstTime = true;
|
||||
bool reportedDataSent = true;
|
||||
|
||||
static void handlePaxCounterReportRequest();
|
||||
|
||||
public:
|
||||
PaxcounterModule();
|
||||
|
||||
+101
-13
@@ -77,8 +77,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length)
|
||||
if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
|
||||
memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
|
||||
p->decoded.payload.size = jsonPayloadStr.length();
|
||||
meshtastic_MeshPacket *packet = packetPool.allocCopy(*p);
|
||||
service.sendToMesh(packet, RX_SRC_LOCAL);
|
||||
service.sendToMesh(p, RX_SRC_LOCAL);
|
||||
} else {
|
||||
LOG_WARN("Received MQTT json payload too long, dropping\n");
|
||||
}
|
||||
@@ -186,10 +185,17 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
|
||||
statusTopic = moduleConfig.mqtt.root + statusTopic;
|
||||
cryptTopic = moduleConfig.mqtt.root + cryptTopic;
|
||||
jsonTopic = moduleConfig.mqtt.root + jsonTopic;
|
||||
mapTopic = moduleConfig.mqtt.root + jsonTopic;
|
||||
} else {
|
||||
statusTopic = "msh" + statusTopic;
|
||||
cryptTopic = "msh" + cryptTopic;
|
||||
jsonTopic = "msh" + jsonTopic;
|
||||
mapTopic = "msh" + mapTopic;
|
||||
}
|
||||
|
||||
if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) {
|
||||
map_position_precision = moduleConfig.mqtt.map_report_settings.position_precision;
|
||||
map_publish_interval_secs = moduleConfig.mqtt.map_report_settings.publish_interval_secs;
|
||||
}
|
||||
|
||||
#ifdef HAS_NETWORKING
|
||||
@@ -365,27 +371,30 @@ void MQTT::sendSubscriptions()
|
||||
|
||||
bool MQTT::wantsLink() const
|
||||
{
|
||||
bool hasChannel = false;
|
||||
bool hasChannelorMapReport = false;
|
||||
|
||||
if (moduleConfig.mqtt.enabled) {
|
||||
// No need for link if no channel needed it
|
||||
size_t numChan = channels.getNumChannels();
|
||||
for (size_t i = 0; i < numChan; i++) {
|
||||
const auto &ch = channels.getByIndex(i);
|
||||
if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) {
|
||||
hasChannel = true;
|
||||
break;
|
||||
hasChannelorMapReport = moduleConfig.mqtt.map_reporting_enabled;
|
||||
if (!hasChannelorMapReport) {
|
||||
// No need for link if no channel needed it
|
||||
size_t numChan = channels.getNumChannels();
|
||||
for (size_t i = 0; i < numChan; i++) {
|
||||
const auto &ch = channels.getByIndex(i);
|
||||
if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) {
|
||||
hasChannelorMapReport = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasChannel && moduleConfig.mqtt.proxy_to_client_enabled)
|
||||
if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled)
|
||||
return true;
|
||||
|
||||
#if HAS_WIFI
|
||||
return hasChannel && WiFi.isConnected();
|
||||
return hasChannelorMapReport && WiFi.isConnected();
|
||||
#endif
|
||||
#if HAS_ETHERNET
|
||||
return hasChannel && Ethernet.linkStatus() == LinkON;
|
||||
return hasChannelorMapReport && Ethernet.linkStatus() == LinkON;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -397,6 +406,8 @@ int32_t MQTT::runOnce()
|
||||
|
||||
bool wantConnection = wantsLink();
|
||||
|
||||
perhapsReportToMap();
|
||||
|
||||
// If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server
|
||||
if (moduleConfig.mqtt.proxy_to_client_enabled) {
|
||||
publishQueuedMessages();
|
||||
@@ -536,6 +547,78 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &
|
||||
}
|
||||
}
|
||||
|
||||
void MQTT::perhapsReportToMap()
|
||||
{
|
||||
if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly()))
|
||||
return;
|
||||
|
||||
if (millis() - last_report_to_map < map_publish_interval_secs * 1000) {
|
||||
return;
|
||||
} else {
|
||||
if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
|
||||
LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate ServiceEnvelope and fill it
|
||||
meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed();
|
||||
se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id
|
||||
se->gateway_id = owner.id;
|
||||
|
||||
// Allocate MeshPacket and fill it
|
||||
meshtastic_MeshPacket *mp = packetPool.allocZeroed();
|
||||
mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
|
||||
mp->from = nodeDB.getNodeNum();
|
||||
mp->to = NODENUM_BROADCAST;
|
||||
mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP;
|
||||
|
||||
// Fill MapReport message
|
||||
meshtastic_MapReport mapReport = meshtastic_MapReport_init_default;
|
||||
memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name));
|
||||
memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name));
|
||||
mapReport.role = config.device.role;
|
||||
mapReport.hw_model = owner.hw_model;
|
||||
strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version));
|
||||
mapReport.region = config.lora.region;
|
||||
mapReport.modem_preset = config.lora.modem_preset;
|
||||
mapReport.has_default_channel = channels.hasDefaultChannel();
|
||||
|
||||
// Set position with precision (same as in PositionModule)
|
||||
if (map_position_precision < 32 && map_position_precision > 0) {
|
||||
mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision));
|
||||
mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision));
|
||||
mapReport.latitude_i += (1 << (31 - map_position_precision));
|
||||
mapReport.longitude_i += (1 << (31 - map_position_precision));
|
||||
} else {
|
||||
mapReport.latitude_i = localPosition.latitude_i;
|
||||
mapReport.longitude_i = localPosition.longitude_i;
|
||||
}
|
||||
mapReport.altitude = localPosition.altitude;
|
||||
mapReport.position_precision = map_position_precision;
|
||||
|
||||
mapReport.num_online_local_nodes = nodeDB.getNumOnlineMeshNodes(true);
|
||||
|
||||
// Encode MapReport message and set it to MeshPacket in ServiceEnvelope
|
||||
mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes),
|
||||
&meshtastic_MapReport_msg, &mapReport);
|
||||
se->packet = mp;
|
||||
|
||||
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
|
||||
static uint8_t bytes[meshtastic_MeshPacket_size + 64];
|
||||
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se);
|
||||
|
||||
LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str());
|
||||
publish(mapTopic.c_str(), bytes, numBytes, false);
|
||||
|
||||
// Release the allocated memory for ServiceEnvelope and MeshPacket
|
||||
mqttPool.release(se);
|
||||
packetPool.release(mp);
|
||||
|
||||
// Update the last report time
|
||||
last_report_to_map = millis();
|
||||
}
|
||||
}
|
||||
|
||||
// converts a downstream packet into a json message
|
||||
std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
|
||||
{
|
||||
@@ -655,6 +738,9 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
|
||||
if ((int)decoded->VDOP) {
|
||||
msgPayload["VDOP"] = new JSONValue((int)decoded->VDOP);
|
||||
}
|
||||
if ((int)decoded->precision_bits) {
|
||||
msgPayload["precision_bits"] = new JSONValue((int)decoded->precision_bits);
|
||||
}
|
||||
jsonObj["payload"] = new JSONValue(msgPayload);
|
||||
} else {
|
||||
LOG_ERROR("Error decoding protobuf for position message!\n");
|
||||
@@ -808,6 +894,8 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
|
||||
jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi);
|
||||
if (mp->rx_snr != 0)
|
||||
jsonObj["snr"] = new JSONValue((float)mp->rx_snr);
|
||||
if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start)
|
||||
jsonObj["hops_away"] = new JSONValue((uint)(mp->hop_start - mp->hop_limit));
|
||||
|
||||
// serialize and write it to the stream
|
||||
JSONValue *value = new JSONValue(jsonObj);
|
||||
|
||||
+14
-4
@@ -79,10 +79,17 @@ class MQTT : private concurrency::OSThread
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
std::string statusTopic = "/2/stat/";
|
||||
std::string cryptTopic = "/2/c/"; // msh/2/c/CHANNELID/NODEID
|
||||
std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID
|
||||
/** return true if we have a channel that wants uplink/downlink
|
||||
std::string statusTopic = "/2/stat/"; // For "online"/"offline" message
|
||||
std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID
|
||||
std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID
|
||||
std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages
|
||||
|
||||
// For map reporting (only applies when enabled)
|
||||
uint32_t last_report_to_map = 0;
|
||||
uint32_t map_position_precision = 32; // default to full precision
|
||||
uint32_t map_publish_interval_secs = 60 * 15; // default to 15 minutes
|
||||
|
||||
/** return true if we have a channel that wants uplink/downlink or map reporting is enabled
|
||||
*/
|
||||
bool wantsLink() const;
|
||||
|
||||
@@ -102,6 +109,9 @@ class MQTT : private concurrency::OSThread
|
||||
void publishStatus();
|
||||
void publishQueuedMessages();
|
||||
|
||||
// Check if we should report unencrypted information about our node for consumption by a map
|
||||
void perhapsReportToMap();
|
||||
|
||||
// returns true if this is a valid JSON envelope which we accept on downlink
|
||||
bool isValidJsonEnvelope(JSONObject &json);
|
||||
|
||||
|
||||
@@ -16,5 +16,5 @@ class NimbleBluetooth : BluetoothApi
|
||||
void startAdvertising();
|
||||
};
|
||||
|
||||
void setBluetoothEnable(bool on);
|
||||
void clearNVS();
|
||||
void setBluetoothEnable(bool enable);
|
||||
void clearNVS();
|
||||
@@ -20,21 +20,21 @@
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
|
||||
void setBluetoothEnable(bool on)
|
||||
void setBluetoothEnable(bool enable)
|
||||
{
|
||||
if (!isWifiAvailable() && config.bluetooth.enabled == true) {
|
||||
if (!nimbleBluetooth) {
|
||||
nimbleBluetooth = new NimbleBluetooth();
|
||||
}
|
||||
if (on && !nimbleBluetooth->isActive()) {
|
||||
if (enable && !nimbleBluetooth->isActive()) {
|
||||
nimbleBluetooth->setup();
|
||||
} else if (!on) {
|
||||
} else if (!enable) {
|
||||
nimbleBluetooth->shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
void setBluetoothEnable(bool on) {}
|
||||
void setBluetoothEnable(bool enable) {}
|
||||
void updateBatteryLevel(uint8_t level) {}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -210,8 +210,10 @@ void NRF52Bluetooth::shutdown()
|
||||
{
|
||||
// Shutdown bluetooth for minimum power draw
|
||||
LOG_INFO("Disable NRF52 bluetooth\n");
|
||||
if (connectionHandle != 0) {
|
||||
Bluefruit.disconnect(connectionHandle);
|
||||
}
|
||||
Bluefruit.Advertising.stop();
|
||||
Bluefruit.setTxPower(0); // Minimum power
|
||||
}
|
||||
|
||||
bool NRF52Bluetooth::isConnected()
|
||||
@@ -289,6 +291,14 @@ void NRF52Bluetooth::setup()
|
||||
}
|
||||
}
|
||||
|
||||
void NRF52Bluetooth::resumeAdverising()
|
||||
{
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0);
|
||||
}
|
||||
|
||||
/// Given a level between 0-100, update the BLE attribute
|
||||
void updateBatteryLevel(uint8_t level)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ class NRF52Bluetooth : BluetoothApi
|
||||
public:
|
||||
void setup();
|
||||
void shutdown();
|
||||
void resumeAdverising();
|
||||
void clearBonds();
|
||||
bool isConnected();
|
||||
int getRssi();
|
||||
@@ -17,4 +18,4 @@ class NRF52Bluetooth : BluetoothApi
|
||||
void convertToUint8(uint8_t target[4], uint32_t source);
|
||||
static bool onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request);
|
||||
static void onPairingCompleted(uint16_t conn_handle, uint8_t auth_status);
|
||||
};
|
||||
};
|
||||
@@ -63,28 +63,29 @@ static void initBrownout()
|
||||
// We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice
|
||||
}
|
||||
|
||||
static bool bleOn = false;
|
||||
static const bool useSoftDevice = true; // Set to false for easier debugging
|
||||
|
||||
void setBluetoothEnable(bool on)
|
||||
void setBluetoothEnable(bool enable)
|
||||
{
|
||||
if (on != bleOn && config.bluetooth.enabled == true) {
|
||||
if (on) {
|
||||
if (enable && config.bluetooth.enabled) {
|
||||
if (!useSoftDevice) {
|
||||
LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n");
|
||||
} else {
|
||||
if (!nrf52Bluetooth) {
|
||||
if (!useSoftDevice)
|
||||
LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n");
|
||||
else {
|
||||
nrf52Bluetooth = new NRF52Bluetooth();
|
||||
nrf52Bluetooth->setup();
|
||||
LOG_DEBUG("Initializing NRF52 Bluetooth\n");
|
||||
nrf52Bluetooth = new NRF52Bluetooth();
|
||||
nrf52Bluetooth->setup();
|
||||
|
||||
// We delay brownout init until after BLE because BLE starts soft device
|
||||
initBrownout();
|
||||
}
|
||||
// We delay brownout init until after BLE because BLE starts soft device
|
||||
initBrownout();
|
||||
} else {
|
||||
nrf52Bluetooth->resumeAdverising();
|
||||
}
|
||||
} else if (nrf52Bluetooth) {
|
||||
}
|
||||
} else {
|
||||
if (nrf52Bluetooth) {
|
||||
nrf52Bluetooth->shutdown();
|
||||
}
|
||||
bleOn = on;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ std::map<configNames, std::string> settingsStrings;
|
||||
char *configPath = nullptr;
|
||||
|
||||
// FIXME - move setBluetoothEnable into a HALPlatform class
|
||||
void setBluetoothEnable(bool on)
|
||||
void setBluetoothEnable(bool enable)
|
||||
{
|
||||
// not needed
|
||||
}
|
||||
@@ -195,6 +195,11 @@ void portduinoSetup()
|
||||
settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as<std::string>("");
|
||||
}
|
||||
|
||||
if (yamlConfig["Webserver"]) {
|
||||
settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as<int>(-1);
|
||||
settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as<std::string>("");
|
||||
}
|
||||
|
||||
} catch (YAML::Exception e) {
|
||||
std::cout << "*** Exception " << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
@@ -33,7 +33,10 @@ enum configNames {
|
||||
displayOffsetY,
|
||||
displayInvert,
|
||||
keyboardDevice,
|
||||
logoutputlevel
|
||||
logoutputlevel,
|
||||
webserver,
|
||||
webserverport,
|
||||
webserverrootpath
|
||||
};
|
||||
enum { no_screen, st7789, st7735, st7735s, ili9341 };
|
||||
enum { no_touchscreen, xpt2046, stmpe610 };
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <pico/unique_id.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void setBluetoothEnable(bool on)
|
||||
void setBluetoothEnable(bool enable)
|
||||
{
|
||||
// not needed
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <stm32wle5xx.h>
|
||||
#include <stm32wlxx_hal.h>
|
||||
|
||||
void setBluetoothEnable(bool on) {}
|
||||
void setBluetoothEnable(bool enable) {}
|
||||
|
||||
void playStartMelody() {}
|
||||
|
||||
@@ -33,4 +33,4 @@ int _gettimeofday(struct timeval *tv, void *tzvp)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,6 +200,13 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false)
|
||||
|
||||
nodeDB.saveToDisk();
|
||||
|
||||
#ifdef TTGO_T_ECHO
|
||||
#ifdef PIN_POWER_EN
|
||||
pinMode(PIN_POWER_EN, INPUT); // power off peripherals
|
||||
pinMode(PIN_POWER_EN1, INPUT_PULLDOWN);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Kill GPS power completely (even if previously we just had it in sleep mode)
|
||||
if (gps)
|
||||
gps->setGPSPower(false, false, 0);
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
// Functions that are unique to particular target types (esp32, bare, nrf52 etc...)
|
||||
|
||||
// Enable/disable bluetooth.
|
||||
void setBluetoothEnable(bool on);
|
||||
void setBluetoothEnable(bool enable);
|
||||
|
||||
void getMacAddr(uint8_t *dmac);
|
||||
@@ -4,6 +4,9 @@ board = nordic_pca10059
|
||||
board_level = extra
|
||||
build_flags = ${nrf52840_base.build_flags} -Ivariants/Dongle_nRF52840-pca10059-v1 -D NORDIC_PCA10059
|
||||
-L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard"
|
||||
-DEINK_DISPLAY_MODEL=GxEPD2_420_M01
|
||||
-DEINK_WIDTH=300
|
||||
-DEINK_HEIGHT=400
|
||||
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/Dongle_nRF52840-pca10059-v1>
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
|
||||
@@ -136,7 +136,7 @@ static const uint8_t SCK = PIN_SPI_SCK;
|
||||
#define USE_SX1262
|
||||
#define SX126X_CS (0 + 31) // LORA_CS P0.31
|
||||
#define SX126X_DIO1 (0 + 29) // DIO1 P0.29
|
||||
#define SX126X_BUSY (0 + 2) // LORA_BUSY P0.02
|
||||
#define SX126X_BUSY (0 + 2) // LORA_BUSY P0.02
|
||||
#define SX126X_RESET (32 + 15) // LORA_RESET P1.15
|
||||
#define SX126X_TXEN (32 + 13) // TXEN P1.13 NiceRF 868 dont use
|
||||
#define SX126X_RXEN (32 + 10) // RXEN P1.10 NiceRF 868 dont use
|
||||
|
||||
@@ -10,5 +10,8 @@ lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f
|
||||
zinggjm/GxEPD2@^1.4.9
|
||||
-DEINK_DISPLAY_MODEL=GxEPD2_290_T5D
|
||||
-DEINK_WIDTH=296
|
||||
-DEINK_HEIGHT=128
|
||||
debug_tool = jlink
|
||||
;upload_port = /dev/ttyACM4
|
||||
|
||||
@@ -103,6 +103,9 @@ static const uint8_t A0 = PIN_A0;
|
||||
#define EXTERNAL_FLASH_DEVICES MX25R1635F
|
||||
#define EXTERNAL_FLASH_USE_QSPI
|
||||
|
||||
// Add a delay on startup to allow LoRa and GPS to power up
|
||||
#define PIN_PWR_DELAY_MS 100
|
||||
|
||||
/*
|
||||
* Lora radio
|
||||
*/
|
||||
|
||||
@@ -16,9 +16,9 @@ build_flags = ${esp32_base.build_flags}
|
||||
;-DPRIVATE_HW
|
||||
-Ivariants/esp32-s3-pico
|
||||
-DBOARD_HAS_PSRAM
|
||||
-DTECHO_DISPLAY_MODEL=GxEPD2_290_T94_V2
|
||||
-DEPD_HEIGHT=128
|
||||
-DEPD_WIDTH=296
|
||||
-DEINK_DISPLAY_MODEL=GxEPD2_290_T94_V2
|
||||
-DEINK_WIDTH=296
|
||||
-DEINK_HEIGHT=128
|
||||
|
||||
lib_deps = ${esp32s3_base.lib_deps}
|
||||
zinggjm/GxEPD2@^1.5.3
|
||||
|
||||
@@ -74,4 +74,4 @@
|
||||
#define PIN_EINK_DC 33
|
||||
#define PIN_EINK_RES 42 // 37 //(-1) // cant be MISO Waveshare ??)
|
||||
#define PIN_EINK_SCLK 35
|
||||
#define PIN_EINK_MOSI 36
|
||||
#define PIN_EINK_MOSI 36
|
||||
@@ -9,7 +9,7 @@
|
||||
#define LED_PIN 18 // LED
|
||||
#define LED_INVERTED 1
|
||||
|
||||
#define HAS_SCREEN 0
|
||||
#define HAS_SCREEN 1
|
||||
#define HAS_GPS 0
|
||||
#undef GPS_RX_PIN
|
||||
#undef GPS_TX_PIN
|
||||
|
||||
@@ -2,10 +2,21 @@
|
||||
extends = esp32s3_base
|
||||
board = heltec_wifi_lora_32_V3
|
||||
build_flags =
|
||||
${esp32s3_base.build_flags} -D HELTEC_WIRELESS_PAPER -I variants/heltec_wireless_paper
|
||||
${esp32s3_base.build_flags}
|
||||
-I variants/heltec_wireless_paper
|
||||
-D HELTEC_WIRELESS_PAPER
|
||||
-D EINK_DISPLAY_MODEL=GxEPD2_213_FC1
|
||||
-D EINK_WIDTH=250
|
||||
-D EINK_HEIGHT=122
|
||||
-D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
|
||||
-D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
|
||||
-D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
|
||||
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
|
||||
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
|
||||
-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
https://github.com/ixt/GxEPD2#39f325b677713eb04dfcc83b8e402e77523fb8bf
|
||||
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
|
||||
adafruit/Adafruit BusIO@^1.13.2
|
||||
lewisxhe/PCF8563_Library@^1.0.1
|
||||
upload_speed = 115200
|
||||
@@ -5,6 +5,7 @@
|
||||
#define I2C_SCL SCL
|
||||
|
||||
#define USE_EINK
|
||||
|
||||
/*
|
||||
* eink display pins
|
||||
*/
|
||||
|
||||
@@ -5,9 +5,18 @@ build_flags =
|
||||
${esp32s3_base.build_flags}
|
||||
-I variants/heltec_wireless_paper_v1
|
||||
-D HELTEC_WIRELESS_PAPER_V1_0
|
||||
-D EINK_DISPLAY_MODEL=GxEPD2_213_BN
|
||||
-D EINK_WIDTH=250
|
||||
-D EINK_HEIGHT=122
|
||||
-D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
|
||||
-D EINK_LIMIT_FASTREFRESH=5 ; How many consecutive fast-refreshes are permitted
|
||||
-D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
|
||||
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
|
||||
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
|
||||
;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
https://github.com/meshtastic/GxEPD2/
|
||||
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
|
||||
adafruit/Adafruit BusIO@^1.13.2
|
||||
lewisxhe/PCF8563_Library@^1.0.1
|
||||
upload_speed = 115200
|
||||
@@ -6,13 +6,6 @@
|
||||
|
||||
#define USE_EINK
|
||||
|
||||
// Settings for Dynamic Refresh mode
|
||||
// Change between full-refresh, fast-refresh, or update-skipping, to balance urgency and display health.
|
||||
#define USE_EINK_DYNAMIC_REFRESH
|
||||
#define EINK_LOWPRIORITY_LIMIT_SECONDS 30
|
||||
#define EINK_HIGHPRIORITY_LIMIT_SECONDS 1
|
||||
#define EINK_FASTREFRESH_REPEAT_LIMIT 5
|
||||
|
||||
/*
|
||||
* eink display pins
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user