mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-24 03:31:14 +00:00
Compare commits
337 Commits
v2.3.0.5f4
...
v2.3.9.f06
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f06c56a51b | ||
|
|
ac22a503de | ||
|
|
676319a9ca | ||
|
|
5d9800b7c2 | ||
|
|
0c89aff0f6 | ||
|
|
5e160b21c7 | ||
|
|
75dc8cccec | ||
|
|
147de75a02 | ||
|
|
5371f134ba | ||
|
|
8105c0440a | ||
|
|
cbf20e4cee | ||
|
|
c009c0db1e | ||
|
|
2c99f11073 | ||
|
|
0d57d29cbd | ||
|
|
353c7e07d1 | ||
|
|
77a66e1dce | ||
|
|
e98c3bf5d0 | ||
|
|
5e9d48d0d7 | ||
|
|
8e91f895a6 | ||
|
|
b155a5b6dc | ||
|
|
2c30923e3e | ||
|
|
0afe2d459f | ||
|
|
0b239e618d | ||
|
|
e9ebdfeff2 | ||
|
|
6fb7d7f2d7 | ||
|
|
70712d859c | ||
|
|
4d9081b3b1 | ||
|
|
7643a1acb1 | ||
|
|
61216e579e | ||
|
|
e31bb2d513 | ||
|
|
a8c38c4580 | ||
|
|
85e0372d26 | ||
|
|
53bd9de9b8 | ||
|
|
13ad524538 | ||
|
|
5f90f45ac4 | ||
|
|
dc0593c5a7 | ||
|
|
df3cceb108 | ||
|
|
827dcfca4a | ||
|
|
9fb6148aff | ||
|
|
6c1377aa39 | ||
|
|
077ca5919a | ||
|
|
668b716119 | ||
|
|
eaa7e21bc7 | ||
|
|
be0e882be1 | ||
|
|
09080d76ad | ||
|
|
d490a332a7 | ||
|
|
5dfa4b837f | ||
|
|
9501f3bda9 | ||
|
|
b69a1cada9 | ||
|
|
06e7d2b845 | ||
|
|
71400103b3 | ||
|
|
40e361e6d0 | ||
|
|
d1b6f11429 | ||
|
|
0527fb10ce | ||
|
|
5f929a8024 | ||
|
|
4f54862d63 | ||
|
|
0f4ac94559 | ||
|
|
45c1b46bd0 | ||
|
|
5095efc55f | ||
|
|
ec92f7a5a3 | ||
|
|
57da37cfbc | ||
|
|
3619ac87b8 | ||
|
|
e51ee91c39 | ||
|
|
21311bbeda | ||
|
|
472db5b237 | ||
|
|
18e69a0906 | ||
|
|
93f77ea7d2 | ||
|
|
ee4c4ae6c9 | ||
|
|
6cc7dee95c | ||
|
|
38c4d35a7b | ||
|
|
e683d8f552 | ||
|
|
e66aec8223 | ||
|
|
a06a01d25e | ||
|
|
f8c3f43ea6 | ||
|
|
dfcabba0b2 | ||
|
|
827bacdfc8 | ||
|
|
5806a266d3 | ||
|
|
30fbcabf84 | ||
|
|
c14043f196 | ||
|
|
e3610a2eb1 | ||
|
|
9baccc80d8 | ||
|
|
ac16ccf40c | ||
|
|
1c0227f90c | ||
|
|
57d296e0db | ||
|
|
9c9d126f6b | ||
|
|
27f0e42d2f | ||
|
|
4599534616 | ||
|
|
7acaec8ef5 | ||
|
|
d0e81b9151 | ||
|
|
c6e940af81 | ||
|
|
3302fbcc53 | ||
|
|
ccbf635eef | ||
|
|
6669b22db3 | ||
|
|
ec2b854ea2 | ||
|
|
378a2d723e | ||
|
|
5dd08e9533 | ||
|
|
125add9792 | ||
|
|
250cf16bf8 | ||
|
|
8b5fad21b0 | ||
|
|
30d4c3a945 | ||
|
|
45fd5e25ac | ||
|
|
ac6a668362 | ||
|
|
f47b40cf68 | ||
|
|
fd9461505f | ||
|
|
84edaabfe9 | ||
|
|
048f0a1601 | ||
|
|
4a48a3fb52 | ||
|
|
39bbf0d352 | ||
|
|
675d8fe089 | ||
|
|
0406be82d2 | ||
|
|
679e068e19 | ||
|
|
952393ca0f | ||
|
|
a231cd2ad0 | ||
|
|
ac87c0065f | ||
|
|
9822a85274 | ||
|
|
a957065fe8 | ||
|
|
41f3557491 | ||
|
|
402b0d7e0b | ||
|
|
13ebda6b2f | ||
|
|
1dd19cec6e | ||
|
|
df718ab294 | ||
|
|
dfc43bae18 | ||
|
|
f6cfdfe881 | ||
|
|
820c5dc8c5 | ||
|
|
1f9c295c9e | ||
|
|
5218aaafcf | ||
|
|
c480f0870c | ||
|
|
9e4ef92e6d | ||
|
|
cf65661c7c | ||
|
|
fb7a878d94 | ||
|
|
e72792afc8 | ||
|
|
ec39e1136a | ||
|
|
94e1f016e5 | ||
|
|
9170fe0580 | ||
|
|
ef9808cdd6 | ||
|
|
0972a8dccb | ||
|
|
419eb13968 | ||
|
|
e7828c4c64 | ||
|
|
44aa248099 | ||
|
|
e0513d4078 | ||
|
|
2100f3135e | ||
|
|
2f36d4990e | ||
|
|
65bde8538f | ||
|
|
7a3570aecf | ||
|
|
4a471ded79 | ||
|
|
eea85d26ca | ||
|
|
64edfb76e0 | ||
|
|
8ac308e73b | ||
|
|
0ae7674982 | ||
|
|
e4b5f2ce14 | ||
|
|
7d77b23eb6 | ||
|
|
a149999cec | ||
|
|
78d915b454 | ||
|
|
4c0b7ea409 | ||
|
|
425a715995 | ||
|
|
2e13aeeacb | ||
|
|
747c713ba9 | ||
|
|
4b5549be8f | ||
|
|
172d271b0b | ||
|
|
2e14234b77 | ||
|
|
d47e9bed19 | ||
|
|
bc085ab840 | ||
|
|
2450031b1b | ||
|
|
2cd877d2eb | ||
|
|
c34956e9d8 | ||
|
|
afb4de21d9 | ||
|
|
86223d8806 | ||
|
|
0632b96fcb | ||
|
|
dcfc9c9f03 | ||
|
|
8a3322fbcb | ||
|
|
55c9c3b298 | ||
|
|
9599549477 | ||
|
|
e813703bf5 | ||
|
|
699ea74672 | ||
|
|
a01069a549 | ||
|
|
3413b9da41 | ||
|
|
7d3175dc83 | ||
|
|
441638c2eb | ||
|
|
2f9b68e08b | ||
|
|
27ae4399bc | ||
|
|
385d7296fe | ||
|
|
d1cd686644 | ||
|
|
1291da746b | ||
|
|
2803fa964e | ||
|
|
1d97544041 | ||
|
|
5b52c31a76 | ||
|
|
00d4c011c7 | ||
|
|
1447148811 | ||
|
|
4f205718f0 | ||
|
|
5047468d9f | ||
|
|
ec3971bce5 | ||
|
|
0a246bfe9b | ||
|
|
f1a1834ee2 | ||
|
|
2a6e26620e | ||
|
|
3f45c2d4f0 | ||
|
|
11adfe05ce | ||
|
|
b4009f9f2f | ||
|
|
917b739e62 | ||
|
|
2c4db16336 | ||
|
|
4c9646f7d9 | ||
|
|
8fd32f3452 | ||
|
|
178877f2d9 | ||
|
|
6de0363eea | ||
|
|
f4a2023dba | ||
|
|
927d07e2c6 | ||
|
|
a4a8556aa2 | ||
|
|
8e29efcb50 | ||
|
|
3bee6ce9c3 | ||
|
|
fcab20fb3b | ||
|
|
2d81c97b98 | ||
|
|
cfd98b2c91 | ||
|
|
6e7405e56b | ||
|
|
77082e35f5 | ||
|
|
daa64b055a | ||
|
|
ec74fba2bd | ||
|
|
e89575bfd1 | ||
|
|
ea61808fd9 | ||
|
|
b14ac777f1 | ||
|
|
65e5bdc212 | ||
|
|
aa3280c18c | ||
|
|
68e657fd07 | ||
|
|
47b8f7b6c6 | ||
|
|
fde20db95f | ||
|
|
40a7fd145a | ||
|
|
33842b67e8 | ||
|
|
2db061ded9 | ||
|
|
1baad2875a | ||
|
|
0e9f1beb40 | ||
|
|
03f60dcb49 | ||
|
|
5b5f9c62b5 | ||
|
|
577de1e517 | ||
|
|
f6e6f975c0 | ||
|
|
902f38238d | ||
|
|
9b2d862b7d | ||
|
|
4cdfae71cf | ||
|
|
be889015f7 | ||
|
|
f0b6ff9b2d | ||
|
|
30ebb6ae46 | ||
|
|
d1db51830b | ||
|
|
eb0e705ba9 | ||
|
|
46ad4bf0e5 | ||
|
|
a570e50aca | ||
|
|
2caed6d29c | ||
|
|
f2ed0f7c8c | ||
|
|
8bb562c5fa | ||
|
|
15501e84dd | ||
|
|
a4c22321fc | ||
|
|
46a63bf293 | ||
|
|
279464f96d | ||
|
|
3cf6c47bab | ||
|
|
64fd866494 | ||
|
|
7b391d1a9f | ||
|
|
8187fa7115 | ||
|
|
daa4d387c6 | ||
|
|
4c2d5c6a89 | ||
|
|
b5ec35ec78 | ||
|
|
5732eed86b | ||
|
|
1542afb847 | ||
|
|
acc32916c3 | ||
|
|
728b58fb94 | ||
|
|
77fb230baa | ||
|
|
b960dc1b41 | ||
|
|
5f529f7ca3 | ||
|
|
b4dbc2b4bf | ||
|
|
63df972d42 | ||
|
|
c87fdfece7 | ||
|
|
381d5230b8 | ||
|
|
a7c005ccdf | ||
|
|
71ca6f768f | ||
|
|
4cce4c7c93 | ||
|
|
9e8860d188 | ||
|
|
d30d6bd3eb | ||
|
|
94e4301f2f | ||
|
|
54818b5f8d | ||
|
|
c77c58d656 | ||
|
|
794e99c2f9 | ||
|
|
7aa013a716 | ||
|
|
a57f7730ea | ||
|
|
35754d661d | ||
|
|
79cfb1e876 | ||
|
|
155df45d92 | ||
|
|
907d075917 | ||
|
|
9c88906acc | ||
|
|
defeb8e52b | ||
|
|
6dd337a651 | ||
|
|
0a7ddb7594 | ||
|
|
4debcd5ccd | ||
|
|
fd26914d88 | ||
|
|
dfcd0d14f6 | ||
|
|
f4095ce00d | ||
|
|
7aa21f6e3f | ||
|
|
5e832e2fc6 | ||
|
|
4fa7f5a748 | ||
|
|
a6625998f5 | ||
|
|
711b85cfe8 | ||
|
|
b98176e73e | ||
|
|
aae49f5ecf | ||
|
|
0d1d79b6d1 | ||
|
|
bb57ccfc9e | ||
|
|
e27f029d09 | ||
|
|
13cc1b0252 | ||
|
|
54a2a4bcc6 | ||
|
|
611f291d4d | ||
|
|
9586606229 | ||
|
|
0de36fbfb0 | ||
|
|
0dda20bc35 | ||
|
|
52cfec29fc | ||
|
|
4d0d82f7e7 | ||
|
|
34bc22f94d | ||
|
|
cb3740708b | ||
|
|
e8ec167854 | ||
|
|
b900415218 | ||
|
|
2eb78fec53 | ||
|
|
da7cd5fc7f | ||
|
|
b06c77d46f | ||
|
|
cbc0aa16c5 | ||
|
|
876a0520a9 | ||
|
|
50cc4cfcf1 | ||
|
|
ec6bdeed81 | ||
|
|
a085c3ddb3 | ||
|
|
58cdf360f8 | ||
|
|
9c37e57e75 | ||
|
|
9d2fcbe1e1 | ||
|
|
3995e2f708 | ||
|
|
216f85ff22 | ||
|
|
2efe436102 | ||
|
|
fb16390205 | ||
|
|
333c3c1c9e | ||
|
|
724fa38a55 | ||
|
|
38ea681433 | ||
|
|
ee685b4ed7 | ||
|
|
cf11807f97 | ||
|
|
7f063fbf81 | ||
|
|
6215495ccc | ||
|
|
045dda64e7 | ||
|
|
affbd7f2b9 | ||
|
|
f9bf9e2dcc |
1
.github/workflows/main_matrix.yml
vendored
1
.github/workflows/main_matrix.yml
vendored
@@ -100,6 +100,7 @@ jobs:
|
||||
- board: t-deck
|
||||
- board: picomputer-s3
|
||||
- board: station-g2
|
||||
- board: unphone
|
||||
uses: ./.github/workflows/build_esp32_s3.yml
|
||||
with:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
4
.github/workflows/package_raspbian.yml
vendored
4
.github/workflows/package_raspbian.yml
vendored
@@ -45,6 +45,7 @@ jobs:
|
||||
|
||||
- name: build .debpkg
|
||||
run: |
|
||||
mkdir -p .debpkg/debian
|
||||
mkdir -p .debpkg/usr/share/doc/meshtasticd/web
|
||||
mkdir -p .debpkg/usr/sbin
|
||||
mkdir -p .debpkg/etc/meshtasticd
|
||||
@@ -55,6 +56,7 @@ jobs:
|
||||
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
|
||||
chmod +x .debpkg/usr/sbin/meshtasticd
|
||||
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
|
||||
echo "etc/meshtasticd/config.yaml" > .debpkg/debian/conffiles
|
||||
|
||||
- uses: jiro4989/build-deb-action@v3
|
||||
with:
|
||||
@@ -63,7 +65,7 @@ jobs:
|
||||
maintainer: Jonathan Bennett
|
||||
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
|
||||
arch: arm64
|
||||
depends: libyaml-cpp0.7, openssl
|
||||
depends: libyaml-cpp0.7, openssl, libulfius2.7
|
||||
desc: Native Linux Meshtastic binary.
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
|
||||
6
.github/workflows/update_protobufs.yml
vendored
6
.github/workflows/update_protobufs.yml
vendored
@@ -17,9 +17,9 @@ jobs:
|
||||
|
||||
- name: Download nanopb
|
||||
run: |
|
||||
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.7-linux-x86.tar.gz
|
||||
tar xvzf nanopb-0.4.7-linux-x86.tar.gz
|
||||
mv nanopb-0.4.7-linux-x86 nanopb-0.4.7
|
||||
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
|
||||
tar xvzf nanopb-0.4.8-linux-x86.tar.gz
|
||||
mv nanopb-0.4.8-linux-x86 nanopb-0.4.8
|
||||
|
||||
- name: Re-generate protocol buffers
|
||||
run: |
|
||||
|
||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -6,4 +6,4 @@
|
||||
"platformio.platformio-ide",
|
||||
"trunk.io"
|
||||
],
|
||||
}
|
||||
}
|
||||
60
Dockerfile
60
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM debian:bullseye-slim AS builder
|
||||
FROM debian:bookworm-slim AS builder
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Etc/UTC
|
||||
@@ -11,31 +11,45 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# Install build deps
|
||||
USER root
|
||||
RUN apt-get update && \
|
||||
apt-get -y install wget python3 g++ zip python3-venv git vim ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev
|
||||
|
||||
# create a non-priveleged user & group
|
||||
# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue
|
||||
# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y wget python3 python3-pip python3-wheel python3-venv g++ zip git \
|
||||
ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev \
|
||||
libulfius-dev liborcania-dev libssl-dev pkg-config && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* && mkdir /tmp/firmware
|
||||
|
||||
RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh && chown mesh:mesh /tmp/firmware
|
||||
USER mesh
|
||||
|
||||
WORKDIR /tmp/firmware
|
||||
RUN python3 -m venv /tmp/firmware
|
||||
RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14
|
||||
# trunk-ignore(terrascan/AC_DOCKER_00024): We would actually like these files to be owned by mesh tyvm
|
||||
COPY --chown=mesh:mesh . /tmp/firmware
|
||||
RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh
|
||||
RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd"
|
||||
|
||||
|
||||
##### PRODUCTION BUILD #############
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Etc/UTC
|
||||
|
||||
# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue
|
||||
# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libulfius2.7 liborcania2.3 libssl3 && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
|
||||
|
||||
USER mesh
|
||||
RUN wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -qO /tmp/get-platformio.py && \
|
||||
chmod +x /tmp/get-platformio.py && \
|
||||
python3 /tmp/get-platformio.py && \
|
||||
git clone https://github.com/meshtastic/firmware --recurse-submodules /tmp/firmware && \
|
||||
cd /tmp/firmware && \
|
||||
chmod +x /tmp/firmware/bin/build-native.sh && \
|
||||
source ~/.platformio/penv/bin/activate && \
|
||||
./bin/build-native.sh
|
||||
|
||||
FROM frolvlad/alpine-glibc:glibc-2.31
|
||||
|
||||
RUN apk --update add --no-cache g++ shadow && \
|
||||
groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
|
||||
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd_linux_x86_64 /home/mesh/
|
||||
|
||||
USER mesh
|
||||
WORKDIR /home/mesh
|
||||
CMD sh -cx "./meshtasticd_linux_x86_64 --hwid '${HWID:-$RANDOM}'"
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /home/mesh/
|
||||
|
||||
HEALTHCHECK NONE
|
||||
VOLUME /home/mesh/data
|
||||
|
||||
CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ]
|
||||
|
||||
HEALTHCHECK NONE
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
extends = esp32_base
|
||||
|
||||
build_src_filter =
|
||||
${esp32_base.build_src_filter} -<nimble/> -<mesh/raspihttp>
|
||||
${esp32_base.build_src_filter} - <libpax/> -<nimble/> -<mesh/raspihttp>
|
||||
|
||||
monitor_speed = 115200
|
||||
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-DHAS_BLUETOOTH=0
|
||||
-DMESHTASTIC_EXCLUDE_PAXCOUNTER
|
||||
-DMESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
|
||||
lib_ignore =
|
||||
${esp32_base.lib_ignore}
|
||||
NimBLE-Arduino
|
||||
NimBLE-Arduino
|
||||
libpax
|
||||
@@ -1,9 +1,9 @@
|
||||
[nrf52_base]
|
||||
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
|
||||
platform = platformio/nordicnrf52@^10.1.0
|
||||
platform = platformio/nordicnrf52@^10.4.0
|
||||
extends = arduino_base
|
||||
|
||||
build_type = debug ; I'm debugging with ICE a lot now
|
||||
build_type = debug
|
||||
build_flags =
|
||||
${arduino_base.build_flags}
|
||||
-DSERIAL_BUFFER_SIZE=1024
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
; The Portduino based sim environment on top of any host OS, all hardware will be simulated
|
||||
[portduino_base]
|
||||
platform = https://github.com/meshtastic/platform-native.git#a28dd5a9ccd5c48a9bede46037855ff83915d74b
|
||||
platform = https://github.com/meshtastic/platform-native.git#9881bf3721d610cccacf5ae8e3a07839cce75d63
|
||||
framework = arduino
|
||||
|
||||
build_src_filter =
|
||||
@@ -24,7 +24,7 @@ lib_deps =
|
||||
${env.lib_deps}
|
||||
${networking_base.lib_deps}
|
||||
rweather/Crypto@^0.4.0
|
||||
lovyan03/LovyanGFX@^1.1.12
|
||||
https://github.com/lovyan03/LovyanGFX.git#5a39989aa2c9492572255b22f033843ec8900233
|
||||
|
||||
build_flags =
|
||||
${arduino_base.build_flags}
|
||||
@@ -34,4 +34,4 @@ build_flags =
|
||||
-DPORTDUINO_LINUX_HARDWARE
|
||||
-lbluetooth
|
||||
-lgpiod
|
||||
-lyaml-cpp
|
||||
-lyaml-cpp
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
; Common settings for rp2040 Processor based targets
|
||||
[rp2040_base]
|
||||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#612de5399d68b359053f1307ed223d400aea975c
|
||||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#60d6ae81fcc73c34b1493ca9e261695e471bc0c2
|
||||
extends = arduino_base
|
||||
platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.6.2
|
||||
platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.7.2
|
||||
|
||||
board_build.core = earlephilhower
|
||||
board_build.filesystem_size = 0.5m
|
||||
|
||||
@@ -13,8 +13,8 @@ mkdir -p $OUTDIR/
|
||||
rm -r $OUTDIR/* || true
|
||||
|
||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||
platformio pkg update
|
||||
platformio pkg update --environment native
|
||||
pio run --environment native
|
||||
cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(arch)"
|
||||
cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(uname -m)"
|
||||
cp bin/device-install.* $OUTDIR
|
||||
cp bin/device-update.* $OUTDIR
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
### Define your devices here using Broadcom pin numbering
|
||||
### Uncomment the block that corresponds to your hardware
|
||||
### Including the "Module:" line!
|
||||
---
|
||||
Lora:
|
||||
# Module: sx1262 # Waveshare SX126X XXXM
|
||||
@@ -37,11 +38,22 @@ Lora:
|
||||
# Busy: 20
|
||||
# Reset: 18
|
||||
|
||||
# Module: sx1268 # SX1268-based modules, tested with Ebyte E22 400M33S
|
||||
# CS: 21
|
||||
# IRQ: 16
|
||||
# Busy: 20
|
||||
# Reset: 18
|
||||
# TXen: 6
|
||||
# RXen: 12
|
||||
# DIO3_TCXO_VOLTAGE: true
|
||||
|
||||
# DIO3_TCXO_VOLTAGE: true # the Waveshare Core1262 and others are known to need this setting
|
||||
|
||||
# TXen: x # TX and RX enable pins
|
||||
# RXen: x
|
||||
|
||||
# ch341_quirk: true # Uncomment this to use the chunked SPI transfer that seems to fix the ch341
|
||||
|
||||
### Set gpio chip to use in /dev/. Defaults to 0.
|
||||
### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4
|
||||
# gpiochip: 4
|
||||
@@ -95,23 +107,29 @@ Display:
|
||||
# Panel: ILI9341
|
||||
# CS: 8
|
||||
# DC: 25
|
||||
# Backlight: 2
|
||||
# Width: 320
|
||||
# Height: 240
|
||||
# Width: 240
|
||||
# Height: 320
|
||||
# Rotate: true
|
||||
|
||||
Touchscreen:
|
||||
# Module: STMPE610
|
||||
### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching.
|
||||
|
||||
# Module: STMPE610 # Option 1 for Adafruit PiTFT 2.8
|
||||
# CS: 7
|
||||
# IRQ: 24
|
||||
|
||||
# Module: XPT2046
|
||||
# Module: FT5x06 # Option 2 for Adafruit PiTFT 2.8
|
||||
# IRQ: 24
|
||||
# I2CAddr: 0x38
|
||||
|
||||
# Module: XPT2046 # Waveshare 2.8inch
|
||||
# CS: 7
|
||||
# IRQ: 17
|
||||
|
||||
### Configure device for direct keyboard input
|
||||
|
||||
Input:
|
||||
# KeyboardDevice: /dev/input/event0
|
||||
# KeyboardDevice: /dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd
|
||||
|
||||
###
|
||||
|
||||
@@ -121,3 +139,6 @@ Logging:
|
||||
Webserver:
|
||||
# Port: 443 # Port for Webserver & Webservices
|
||||
# RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer
|
||||
|
||||
General:
|
||||
MaxNodes: 200
|
||||
|
||||
@@ -32,7 +32,7 @@ IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME%
|
||||
|
||||
@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% (
|
||||
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% IF x%FILENAME:station-g2=%==x%FILENAME% IF x%FILENAME:unphone=%==x%FILENAME% (
|
||||
IF x%FILENAME:esp32c3=%==x%FILENAME% (
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin
|
||||
) else (
|
||||
|
||||
@@ -52,7 +52,7 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
||||
"$PYTHON" -m esptool erase_flash
|
||||
"$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
|
||||
# Account for S3 board's different OTA partition
|
||||
if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ]; then
|
||||
if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ] && [ -n "${FILENAME##*"unphone"*}" ]; then
|
||||
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
|
||||
"$PYTHON" -m esptool write_flash 0x260000 bleota.bin
|
||||
else
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cp "release/meshtasticd_linux_$(arch)" /usr/sbin/meshtasticd
|
||||
cp "release/meshtasticd_linux_$(uname -m)" /usr/sbin/meshtasticd
|
||||
mkdir /etc/meshtasticd
|
||||
if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
|
||||
cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml
|
||||
|
||||
@@ -1 +1 @@
|
||||
cd protobufs && ..\nanopb-0.4.7\generator-bin\protoc.exe --experimental_allow_proto3_optional --nanopb_out=-v:..\src\mesh\generated -I=..\protobufs ..\protobufs\meshtastic\*.proto
|
||||
cd protobufs && ..\nanopb-0.4.8\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto
|
||||
|
||||
@@ -2,19 +2,10 @@
|
||||
|
||||
set -e
|
||||
|
||||
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.7 to be located in the"
|
||||
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.8 to be located in the"
|
||||
echo "firmware root directory if the following step fails, you should download the correct"
|
||||
echo "prebuilt binaries for your computer into nanopb-0.4.7"
|
||||
echo "prebuilt binaries for your computer into nanopb-0.4.8"
|
||||
|
||||
# the nanopb tool seems to require that the .options file be in the current directory!
|
||||
cd protobufs
|
||||
../nanopb-0.4.7/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated/ -I=../protobufs meshtastic/*.proto --experimental_allow_proto3_optional
|
||||
|
||||
# cd ../src/mesh/generated/meshtastic
|
||||
# sed -i 's/#include "meshtastic/#include "./g' -- *
|
||||
|
||||
# sed -i 's/meshtastic_//g' -- *
|
||||
|
||||
#echo "Regenerating protobuf documentation - if you see an error message"
|
||||
#echo "you can ignore it unless doing a new protobuf release to github."
|
||||
#bin/regen-docs.sh
|
||||
../nanopb-0.4.8/generator-bin/protoc --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto
|
||||
|
||||
38
boards/CDEBYTE_EoRa-S3.json
Normal file
38
boards/CDEBYTE_EoRa-S3.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-D CDEBYTE_EORA_S3",
|
||||
"-D ARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-D ARDUINO_USB_MODE=0",
|
||||
"-D ARDUINO_RUNNING_CORE=1",
|
||||
"-D ARDUINO_EVENT_RUNNING_CORE=1",
|
||||
"-D BOARD_HAS_PSRAM"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "CDEBYTE_EoRa-S3"
|
||||
},
|
||||
"connectivity": ["wifi"],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "CDEBYTE EoRa-S3",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 4194304,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.cdebyte.com/Module-Testkits-EoRaPI",
|
||||
"vendor": "CDEBYTE"
|
||||
}
|
||||
39
boards/ESP32-S3-WROOM-1-N4.json
Normal file
39
boards/ESP32-S3-WROOM-1-N4.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-D ARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_MSC_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_DFU_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_MODE=0",
|
||||
"-D ARDUINO_RUNNING_CORE=1",
|
||||
"-D ARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "ESP32-S3-WROOM-1-N4"
|
||||
},
|
||||
"connectivity": ["wifi"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "ESP32-S3-WROOM-1-N4 (4 MB Flash, No PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 524288,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
52
boards/promicro-nrf52840.json
Normal file
52
boards/promicro-nrf52840.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x00B3"],
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x802A"]
|
||||
],
|
||||
"usb_product": "ProMicro compatible nRF52840",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "promicro_diy",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "ProMicro compatible nRF52840",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.nologo.tech/product/otherboard/NRF52840.html",
|
||||
"vendor": "Nologo"
|
||||
}
|
||||
@@ -7,8 +7,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DLILYGO_TBEAM_S3_CORE",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
34
boards/wiphone.json
Normal file
34
boards/wiphone.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32_out.ld",
|
||||
"partitions": "default_16MB.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_WIPHONE14",
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-mfix-esp32-psram-cache-issue",
|
||||
"-mfix-esp32-psram-cache-strategy=memw"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
"flash_mode": "dio",
|
||||
"mcu": "esp32",
|
||||
"variant": "wiphone",
|
||||
"board": "WiPhone"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth"],
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "WIPhone Integrated 1.4",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 532480,
|
||||
"maximum_size": 6553600,
|
||||
"maximum_data_size": 4521984,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.wiphone.io/",
|
||||
"vendor": "HackEDA"
|
||||
}
|
||||
@@ -69,16 +69,17 @@ build_flags = -Wno-missing-field-initializers
|
||||
-DRADIOLIB_EXCLUDE_PAGER
|
||||
-DRADIOLIB_EXCLUDE_FSK4
|
||||
-DRADIOLIB_EXCLUDE_APRS
|
||||
-DRADIOLIB_EXCLUDE_LORAWAN
|
||||
|
||||
monitor_speed = 115200
|
||||
|
||||
lib_deps =
|
||||
jgromes/RadioLib@^6.4.0
|
||||
jgromes/RadioLib@~6.5.0
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306
|
||||
mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce
|
||||
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
|
||||
https://github.com/meshtastic/TinyGPSPlus.git#2044b2c51e91ab4cd8cc93b15e40658cd808dd06
|
||||
https://github.com/meshtastic/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3
|
||||
https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
|
||||
https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
|
||||
nanopb/Nanopb@^0.4.7
|
||||
erriez/ErriezCRC32@^1.0.1
|
||||
|
||||
@@ -95,7 +96,6 @@ check_flags =
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
mprograms/QMC5883LCompass@^1.2.0
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
https://github.com/meshtastic/SparkFun_ATECCX08a_Arduino_Library.git#5cf62b36c6f30bc72a07bdb2c11fc9a22d1e31da
|
||||
|
||||
@@ -113,7 +113,7 @@ lib_deps =
|
||||
; (not included in native / portduino)
|
||||
[environmental_base]
|
||||
lib_deps =
|
||||
adafruit/Adafruit BusIO@^1.11.4
|
||||
adafruit/Adafruit BusIO@^1.15.0
|
||||
adafruit/Adafruit Unified Sensor@^1.1.11
|
||||
adafruit/Adafruit BMP280 Library@^2.6.8
|
||||
adafruit/Adafruit BMP085 Library@^1.2.4
|
||||
@@ -130,4 +130,7 @@ lib_deps =
|
||||
adafruit/Adafruit PM25 AQI Sensor@^1.0.6
|
||||
adafruit/Adafruit MPU6050@^2.2.4
|
||||
adafruit/Adafruit LIS3DH@^1.2.4
|
||||
https://github.com/lewisxhe/BMA423_Library@^0.0.1
|
||||
https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17
|
||||
adafruit/Adafruit LSM6DS@^4.7.2
|
||||
mprograms/QMC5883LCompass@^1.2.0
|
||||
https://github.com/Sensirion/arduino-i2c-sht4x#1.1.0
|
||||
|
||||
Submodule protobufs updated: 00332412b2...1bfe0354d1
@@ -1,22 +1,26 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
|
||||
#include "PowerFSM.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "power.h"
|
||||
|
||||
#include <Adafruit_LIS3DH.h>
|
||||
#include <Adafruit_LSM6DS3TRC.h>
|
||||
#include <Adafruit_MPU6050.h>
|
||||
#include <Arduino.h>
|
||||
#include <SensorBMA423.hpp>
|
||||
#include <Wire.h>
|
||||
#include <bma.h>
|
||||
|
||||
BMA423 bmaSensor;
|
||||
SensorBMA423 bmaSensor;
|
||||
bool BMA_IRQ = false;
|
||||
|
||||
#define ACCELEROMETER_CHECK_INTERVAL_MS 100
|
||||
#define ACCELEROMETER_CLICK_THRESHOLD 40
|
||||
|
||||
uint16_t readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
|
||||
int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
|
||||
{
|
||||
Wire.beginTransmission(address);
|
||||
Wire.write(reg);
|
||||
@@ -29,7 +33,7 @@ uint16_t readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
|
||||
return 0; // Pass
|
||||
}
|
||||
|
||||
uint16_t writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
|
||||
int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
|
||||
{
|
||||
Wire.beginTransmission(address);
|
||||
Wire.write(reg);
|
||||
@@ -72,24 +76,14 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
lis.setRange(LIS3DH_RANGE_2_G);
|
||||
// Adjust threshold, higher numbers are less sensitive
|
||||
lis.setClick(config.device.double_tap_as_button_press ? 2 : 1, ACCELEROMETER_CLICK_THRESHOLD);
|
||||
} else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.begin(readRegister, writeRegister, delay)) {
|
||||
} else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 &&
|
||||
bmaSensor.begin(accelerometer_found.address, &readRegister, &writeRegister)) {
|
||||
LOG_DEBUG("BMA423 initializing\n");
|
||||
Acfg cfg;
|
||||
cfg.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
|
||||
cfg.range = BMA4_ACCEL_RANGE_2G;
|
||||
cfg.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
|
||||
cfg.perf_mode = BMA4_CONTINUOUS_MODE;
|
||||
bmaSensor.setAccelConfig(cfg);
|
||||
bmaSensor.enableAccel();
|
||||
|
||||
struct bma4_int_pin_config pin_config;
|
||||
pin_config.edge_ctrl = BMA4_LEVEL_TRIGGER;
|
||||
pin_config.lvl = BMA4_ACTIVE_HIGH;
|
||||
pin_config.od = BMA4_PUSH_PULL;
|
||||
pin_config.output_en = BMA4_OUTPUT_ENABLE;
|
||||
pin_config.input_en = BMA4_INPUT_DISABLE;
|
||||
// The correct trigger interrupt needs to be configured as needed
|
||||
bmaSensor.setINTPinConfig(pin_config, BMA4_INTR1_MAP);
|
||||
bmaSensor.configAccelerometer(bmaSensor.RANGE_2G, bmaSensor.ODR_100HZ, bmaSensor.BW_NORMAL_AVG4,
|
||||
bmaSensor.PERF_CONTINUOUS_MODE);
|
||||
bmaSensor.enableAccelerometer();
|
||||
bmaSensor.configInterrupt(BMA4_LEVEL_TRIGGER, BMA4_ACTIVE_HIGH, BMA4_PUSH_PULL, BMA4_OUTPUT_ENABLE,
|
||||
BMA4_INPUT_DISABLE);
|
||||
|
||||
#ifdef BMA423_INT
|
||||
pinMode(BMA4XX_INT, INPUT);
|
||||
@@ -102,34 +96,31 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
RISING); // Select the interrupt mode according to the actual circuit
|
||||
#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);
|
||||
bmaSensor.enableFeature(BMA423_TILT, true);
|
||||
bmaSensor.enableFeature(BMA423_WAKEUP, true);
|
||||
// sensor.resetStepCounter();
|
||||
bmaSensor.setReampAxes(bmaSensor.REMAP_TOP_LAYER_RIGHT_CORNER);
|
||||
#else
|
||||
bmaSensor.setReampAxes(bmaSensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER);
|
||||
#endif
|
||||
// bmaSensor.enableFeature(bmaSensor.FEATURE_STEP_CNTR, true);
|
||||
bmaSensor.enableFeature(bmaSensor.FEATURE_TILT, true);
|
||||
bmaSensor.enableFeature(bmaSensor.FEATURE_WAKEUP, true);
|
||||
// bmaSensor.resetPedometer();
|
||||
|
||||
// Turn on feature interrupt
|
||||
bmaSensor.enableStepCountInterrupt();
|
||||
bmaSensor.enableTiltInterrupt();
|
||||
bmaSensor.enablePedometerIRQ();
|
||||
bmaSensor.enableTiltIRQ();
|
||||
// It corresponds to isDoubleClick interrupt
|
||||
bmaSensor.enableWakeupInterrupt();
|
||||
bmaSensor.enableWakeupIRQ();
|
||||
} else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) {
|
||||
LOG_DEBUG("LSM6DS3 initializing\n");
|
||||
// Default threshold of 2G, less sensitive options are 4, 8 or 16G
|
||||
lsm.setAccelRange(LSM6DS_ACCEL_RANGE_2_G);
|
||||
#ifndef LSM6DS3_WAKE_THRESH
|
||||
#define LSM6DS3_WAKE_THRESH 20
|
||||
#endif
|
||||
lsm.enableWakeup(config.display.wake_on_tap_or_motion, 1, LSM6DS3_WAKE_THRESH);
|
||||
// Duration is number of occurances needed to trigger, higher threshold is less sensitive
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,11 +141,14 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
buttonPress();
|
||||
return 500;
|
||||
}
|
||||
} else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.getINT()) {
|
||||
if (bmaSensor.isTilt() || bmaSensor.isDoubleClick()) {
|
||||
} else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) {
|
||||
if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) {
|
||||
wakeScreen();
|
||||
return 500;
|
||||
}
|
||||
} else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) {
|
||||
wakeScreen();
|
||||
return 500;
|
||||
}
|
||||
|
||||
return ACCELEROMETER_CHECK_INTERVAL_MS;
|
||||
@@ -178,6 +172,9 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
ScanI2C::DeviceType acceleremoter_type;
|
||||
Adafruit_MPU6050 mpu;
|
||||
Adafruit_LIS3DH lis;
|
||||
Adafruit_LSM6DS3TRC lsm;
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
#endif
|
||||
@@ -5,6 +5,16 @@
|
||||
NCP5623 rgb;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_NEOPIXEL
|
||||
#include <graphics/NeoPixel.h>
|
||||
Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE);
|
||||
#endif
|
||||
|
||||
#ifdef UNPHONE
|
||||
#include "unPhone.h"
|
||||
extern unPhone unphone;
|
||||
#endif
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
class AmbientLightingThread : public concurrency::OSThread
|
||||
@@ -27,15 +37,31 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
|
||||
if (!moduleConfig.ambient_lighting.led_state) {
|
||||
LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF\n");
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("AmbientLightingThread initializing\n");
|
||||
#ifdef HAS_NCP5623
|
||||
if (_type == ScanI2C::NCP5623) {
|
||||
rgb.begin();
|
||||
#endif
|
||||
#ifdef RGBLED_RED
|
||||
pinMode(RGBLED_RED, OUTPUT);
|
||||
pinMode(RGBLED_GREEN, OUTPUT);
|
||||
pinMode(RGBLED_BLUE, OUTPUT);
|
||||
#endif
|
||||
#ifdef HAS_NEOPIXEL
|
||||
pixels.begin(); // Initialise the pixel(s)
|
||||
pixels.clear(); // Set all pixel colors to 'off'
|
||||
pixels.setBrightness(moduleConfig.ambient_lighting.current);
|
||||
#endif
|
||||
setLighting();
|
||||
#endif
|
||||
#ifdef HAS_NCP5623
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -43,16 +69,17 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
protected:
|
||||
int32_t runOnce() override
|
||||
{
|
||||
#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
|
||||
#ifdef HAS_NCP5623
|
||||
if (_type == ScanI2C::NCP5623 && moduleConfig.ambient_lighting.led_state) {
|
||||
#endif
|
||||
setLighting();
|
||||
return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification
|
||||
} else {
|
||||
return disable();
|
||||
#ifdef HAS_NCP5623
|
||||
}
|
||||
#else
|
||||
return disable();
|
||||
#endif
|
||||
#endif
|
||||
return disable();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -65,9 +92,36 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
rgb.setRed(moduleConfig.ambient_lighting.red);
|
||||
rgb.setGreen(moduleConfig.ambient_lighting.green);
|
||||
rgb.setBlue(moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Initializing Ambient lighting w/ current=%d, red=%d, green=%d, blue=%d\n",
|
||||
LOG_DEBUG("Initializing NCP5623 Ambient lighting w/ current=%d, red=%d, green=%d, blue=%d\n",
|
||||
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
|
||||
moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef HAS_NEOPIXEL
|
||||
pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
|
||||
moduleConfig.ambient_lighting.blue),
|
||||
0, NEOPIXEL_COUNT);
|
||||
pixels.show();
|
||||
LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d\n",
|
||||
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
|
||||
moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef RGBLED_CA
|
||||
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
|
||||
analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green);
|
||||
analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Initializing Ambient lighting RGB Common Anode w/ red=%d, green=%d, blue=%d\n",
|
||||
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#elif defined(RGBLED_RED)
|
||||
analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red);
|
||||
analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green);
|
||||
analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Initializing Ambient lighting RGB Common Cathode w/ red=%d, green=%d, blue=%d\n",
|
||||
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef UNPHONE
|
||||
unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Initializing unPhone Ambient lighting w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "ButtonThread.h"
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "GPS.h"
|
||||
#endif
|
||||
#include "MeshService.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "buzz.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
#include "power.h"
|
||||
@@ -21,18 +23,24 @@
|
||||
|
||||
using namespace concurrency;
|
||||
|
||||
ButtonThread *buttonThread; // Declared extern in header
|
||||
volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE;
|
||||
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
|
||||
OneButton ButtonThread::userButton; // Get reference to static member
|
||||
#endif
|
||||
|
||||
ButtonThread::ButtonThread() : OSThread("Button")
|
||||
{
|
||||
#if defined(ARCH_PORTDUINO) || defined(BUTTON_PIN)
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
|
||||
userButton = OneButton(settingsMap[user], true, true);
|
||||
this->userButton = OneButton(settingsMap[user], true, true);
|
||||
LOG_DEBUG("Using GPIO%02d for button\n", settingsMap[user]);
|
||||
}
|
||||
#elif defined(BUTTON_PIN)
|
||||
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
|
||||
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin
|
||||
this->userButton = OneButton(pin, true, true);
|
||||
LOG_DEBUG("Using GPIO%02d for button\n", pin);
|
||||
#endif
|
||||
@@ -41,31 +49,20 @@ ButtonThread::ButtonThread() : OSThread("Button")
|
||||
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
|
||||
pinMode(pin, INPUT_PULLUP_SENSE);
|
||||
#endif
|
||||
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
|
||||
userButton.attachClick(userButtonPressed);
|
||||
userButton.setClickMs(250);
|
||||
userButton.setPressMs(c_longPressTime);
|
||||
userButton.setClickMs(BUTTON_CLICK_MS);
|
||||
userButton.setPressMs(BUTTON_LONGPRESS_MS);
|
||||
userButton.setDebounceMs(1);
|
||||
userButton.attachDoubleClick(userButtonDoublePressed);
|
||||
userButton.attachMultiClick(userButtonMultiPressed);
|
||||
userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton
|
||||
#ifndef T_DECK // T-Deck immediately wakes up after shutdown, so disable this function
|
||||
userButton.attachLongPressStart(userButtonPressedLongStart);
|
||||
userButton.attachLongPressStop(userButtonPressedLongStop);
|
||||
#endif
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
|
||||
wakeOnIrq(settingsMap[user], FALLING);
|
||||
#else
|
||||
static OneButton *pBtn = &userButton; // only one instance of ButtonThread is created, so static is safe
|
||||
attachInterrupt(
|
||||
pin,
|
||||
[]() {
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
pBtn->tick();
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
|
||||
#ifdef INPUT_PULLUP_SENSE
|
||||
@@ -73,19 +70,21 @@ ButtonThread::ButtonThread() : OSThread("Button")
|
||||
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
|
||||
#endif
|
||||
userButtonAlt.attachClick(userButtonPressed);
|
||||
userButtonAlt.setClickMs(250);
|
||||
userButtonAlt.setPressMs(c_longPressTime);
|
||||
userButtonAlt.setClickMs(BUTTON_CLICK_MS);
|
||||
userButtonAlt.setPressMs(BUTTON_LONGPRESS_MS);
|
||||
userButtonAlt.setDebounceMs(1);
|
||||
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
|
||||
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
|
||||
userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
|
||||
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
|
||||
userButtonTouch.attachClick(touchPressed);
|
||||
wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
|
||||
userButtonTouch.setPressMs(BUTTON_TOUCH_MS);
|
||||
userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click?
|
||||
#endif
|
||||
|
||||
attachButtonInterrupts();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -136,25 +135,44 @@ int32_t ButtonThread::runOnce()
|
||||
|
||||
case BUTTON_EVENT_DOUBLE_PRESSED: {
|
||||
LOG_BUTTON("Double press!\n");
|
||||
#if defined(USE_EINK) && defined(PIN_EINK_EN)
|
||||
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
|
||||
#endif
|
||||
service.refreshLocalMeshNode();
|
||||
service.sendNetworkPing(NODENUM_BROADCAST, true);
|
||||
if (screen)
|
||||
screen->print("Sent ad-hoc ping\n");
|
||||
auto sentPosition = service.trySendPosition(NODENUM_BROADCAST, true);
|
||||
if (screen) {
|
||||
if (sentPosition)
|
||||
screen->print("Sent ad-hoc position\n");
|
||||
else
|
||||
screen->print("Sent ad-hoc nodeinfo\n");
|
||||
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BUTTON_EVENT_MULTI_PRESSED: {
|
||||
LOG_BUTTON("Multi press!\n");
|
||||
if (!config.device.disable_triple_click && (gps != nullptr)) {
|
||||
gps->toggleGpsMode();
|
||||
if (screen)
|
||||
screen->forceDisplay();
|
||||
}
|
||||
LOG_BUTTON("Mulitipress! %hux\n", multipressClickCount);
|
||||
switch (multipressClickCount) {
|
||||
#if HAS_GPS
|
||||
// 3 clicks: toggle GPS
|
||||
case 3:
|
||||
if (!config.device.disable_triple_click && (gps != nullptr)) {
|
||||
gps->toggleGpsMode();
|
||||
if (screen)
|
||||
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_EINK) && defined(PIN_EINK_EN) // i.e. T-Echo
|
||||
// 4 clicks: toggle backlight
|
||||
case 4:
|
||||
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
|
||||
break;
|
||||
#endif
|
||||
// No valid multipress action
|
||||
default:
|
||||
break;
|
||||
} // end switch: click count
|
||||
|
||||
break;
|
||||
}
|
||||
} // end multipress event
|
||||
|
||||
case BUTTON_EVENT_LONG_PRESSED: {
|
||||
LOG_BUTTON("Long press!\n");
|
||||
@@ -174,21 +192,85 @@ int32_t ButtonThread::runOnce()
|
||||
power->shutdown();
|
||||
break;
|
||||
}
|
||||
case BUTTON_EVENT_TOUCH_PRESSED: {
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
|
||||
LOG_BUTTON("Touch press!\n");
|
||||
if (screen)
|
||||
if (screen) {
|
||||
// Wake if asleep
|
||||
if (powerFSM.getState() == &stateDARK)
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
|
||||
// Update display (legacy behaviour)
|
||||
screen->forceDisplay();
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif // BUTTON_PIN_TOUCH
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
btnEvent = BUTTON_EVENT_NONE;
|
||||
}
|
||||
|
||||
runASAP = false;
|
||||
return 50;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach (or re-attach) hardware interrupts for buttons
|
||||
* Public method. Used outside class when waking from MCU sleep
|
||||
*/
|
||||
void ButtonThread::attachButtonInterrupts()
|
||||
{
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
|
||||
wakeOnIrq(settingsMap[user], FALLING);
|
||||
#elif defined(BUTTON_PIN)
|
||||
// Interrupt for user button, during normal use. Improves responsiveness.
|
||||
attachInterrupt(
|
||||
config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN,
|
||||
[]() {
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
ButtonThread::userButton.tick();
|
||||
runASAP = true;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Detach the "normal" button interrupts.
|
||||
* Public method. Used before attaching a "wake-on-button" interrupt for MCU sleep
|
||||
*/
|
||||
void ButtonThread::detachButtonInterrupts()
|
||||
{
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
|
||||
detachInterrupt(settingsMap[user]);
|
||||
#elif defined(BUTTON_PIN)
|
||||
detachInterrupt(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
detachInterrupt(BUTTON_PIN_ALT);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
detachInterrupt(BUTTON_PIN_TOUCH);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch a GPIO and if we get an IRQ, wake the main thread.
|
||||
* Use to add wake on button press
|
||||
@@ -200,10 +282,30 @@ void ButtonThread::wakeOnIrq(int irq, int mode)
|
||||
[] {
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
runASAP = true;
|
||||
},
|
||||
FALLING);
|
||||
}
|
||||
|
||||
// Static callback
|
||||
void ButtonThread::userButtonMultiPressed(void *callerThread)
|
||||
{
|
||||
// Grab click count from non-static button, while the info is still valid
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
thread->storeClickCount();
|
||||
|
||||
// Then handle later, in the usual way
|
||||
btnEvent = BUTTON_EVENT_MULTI_PRESSED;
|
||||
}
|
||||
|
||||
// Non-static method, runs during callback. Grabs info while still valid
|
||||
void ButtonThread::storeClickCount()
|
||||
{
|
||||
#ifdef BUTTON_PIN
|
||||
multipressClickCount = userButton.getNumberClicks();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ButtonThread::userButtonPressedLongStart()
|
||||
{
|
||||
if (millis() > c_holdOffTime) {
|
||||
|
||||
@@ -4,11 +4,22 @@
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#ifndef BUTTON_CLICK_MS
|
||||
#define BUTTON_CLICK_MS 250
|
||||
#endif
|
||||
|
||||
#ifndef BUTTON_LONGPRESS_MS
|
||||
#define BUTTON_LONGPRESS_MS 5000
|
||||
#endif
|
||||
|
||||
#ifndef BUTTON_TOUCH_MS
|
||||
#define BUTTON_TOCH_MS 400
|
||||
#endif
|
||||
|
||||
class ButtonThread : public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
static const uint32_t c_longPressTime = 5000; // shutdown after 5s
|
||||
static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
|
||||
static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
|
||||
|
||||
enum ButtonEventType {
|
||||
BUTTON_EVENT_NONE,
|
||||
@@ -17,15 +28,18 @@ class ButtonThread : public concurrency::OSThread
|
||||
BUTTON_EVENT_MULTI_PRESSED,
|
||||
BUTTON_EVENT_LONG_PRESSED,
|
||||
BUTTON_EVENT_LONG_RELEASED,
|
||||
BUTTON_EVENT_TOUCH_PRESSED
|
||||
BUTTON_EVENT_TOUCH_LONG_PRESSED,
|
||||
};
|
||||
|
||||
ButtonThread();
|
||||
int32_t runOnce() override;
|
||||
void attachButtonInterrupts();
|
||||
void detachButtonInterrupts();
|
||||
void storeClickCount();
|
||||
|
||||
private:
|
||||
#ifdef BUTTON_PIN
|
||||
OneButton userButton;
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
|
||||
static OneButton userButton; // Static - accessed from an interrupt
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
OneButton userButtonAlt;
|
||||
@@ -33,20 +47,22 @@ class ButtonThread : public concurrency::OSThread
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
OneButton userButtonTouch;
|
||||
#endif
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
OneButton userButton;
|
||||
#endif
|
||||
|
||||
// set during IRQ
|
||||
static volatile ButtonEventType btnEvent;
|
||||
|
||||
// Store click count during callback, for later use
|
||||
volatile int multipressClickCount = 0;
|
||||
|
||||
static void wakeOnIrq(int irq, int mode);
|
||||
|
||||
// IRQ callbacks
|
||||
static void touchPressed() { btnEvent = BUTTON_EVENT_TOUCH_PRESSED; }
|
||||
static void userButtonPressed() { btnEvent = BUTTON_EVENT_PRESSED; }
|
||||
static void userButtonDoublePressed() { btnEvent = BUTTON_EVENT_DOUBLE_PRESSED; }
|
||||
static void userButtonMultiPressed() { btnEvent = BUTTON_EVENT_MULTI_PRESSED; }
|
||||
static void userButtonMultiPressed(void *callerThread); // Retrieve click count from non-static Onebutton while still valid
|
||||
static void userButtonPressedLongStart();
|
||||
static void userButtonPressedLongStop();
|
||||
static void touchPressedLongStart() { btnEvent = BUTTON_EVENT_TOUCH_LONG_PRESSED; }
|
||||
};
|
||||
|
||||
extern ButtonThread *buttonThread;
|
||||
|
||||
@@ -212,8 +212,23 @@ void fsInit()
|
||||
LOG_ERROR("Filesystem mount Failed.\n");
|
||||
// assert(0); This auto-formats the partition, so no need to fail here.
|
||||
}
|
||||
#ifdef ARCH_ESP32
|
||||
#if defined(ARCH_ESP32)
|
||||
LOG_DEBUG("Filesystem files (%d/%d Bytes):\n", FSCom.usedBytes(), FSCom.totalBytes());
|
||||
#elif defined(ARCH_NRF52)
|
||||
/*
|
||||
* nRF52840 has a certain chance of automatic formatting failure.
|
||||
* Try to create a file after initializing the file system. If the creation fails,
|
||||
* it means that the file system is not working properly. Please format it manually again.
|
||||
* */
|
||||
Adafruit_LittleFS_Namespace::File file(FSCom);
|
||||
const char *filename = "/meshtastic.txt";
|
||||
if (!file.open(filename, FILE_O_WRITE)) {
|
||||
LOG_DEBUG("Format ....");
|
||||
FSCom.format();
|
||||
FSCom.begin();
|
||||
} else {
|
||||
file.close();
|
||||
}
|
||||
#else
|
||||
LOG_DEBUG("Filesystem files:\n");
|
||||
#endif
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
extern NodeDB nodeDB;
|
||||
|
||||
namespace meshtastic
|
||||
{
|
||||
|
||||
@@ -55,7 +53,7 @@ class GPSStatus : public Status
|
||||
#ifdef GPS_EXTRAVERBOSE
|
||||
LOG_WARN("Using fixed latitude\n");
|
||||
#endif
|
||||
meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
return node->position.latitude_i;
|
||||
} else {
|
||||
return p.latitude_i;
|
||||
@@ -68,7 +66,7 @@ class GPSStatus : public Status
|
||||
#ifdef GPS_EXTRAVERBOSE
|
||||
LOG_WARN("Using fixed longitude\n");
|
||||
#endif
|
||||
meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
return node->position.longitude_i;
|
||||
} else {
|
||||
return p.longitude_i;
|
||||
@@ -81,27 +79,18 @@ class GPSStatus : public Status
|
||||
#ifdef GPS_EXTRAVERBOSE
|
||||
LOG_WARN("Using fixed altitude\n");
|
||||
#endif
|
||||
meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
return node->position.altitude;
|
||||
} else {
|
||||
return p.altitude;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getDOP() const
|
||||
{
|
||||
return p.PDOP;
|
||||
}
|
||||
uint32_t getDOP() const { return p.PDOP; }
|
||||
|
||||
uint32_t getHeading() const
|
||||
{
|
||||
return p.ground_track;
|
||||
}
|
||||
uint32_t getHeading() const { return p.ground_track; }
|
||||
|
||||
uint32_t getNumSatellites() const
|
||||
{
|
||||
return p.sats_in_view;
|
||||
}
|
||||
uint32_t getNumSatellites() const { return p.sats_in_view; }
|
||||
|
||||
bool matches(const GPSStatus *newStatus) const
|
||||
{
|
||||
@@ -149,4 +138,4 @@ class GPSStatus : public Status
|
||||
|
||||
} // namespace meshtastic
|
||||
|
||||
extern meshtastic::GPSStatus *gpsStatus;
|
||||
extern meshtastic::GPSStatus *gpsStatus;
|
||||
|
||||
@@ -24,11 +24,13 @@
|
||||
#include "nrfx_power.h"
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_HEAP_MQTT
|
||||
#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT
|
||||
#include "mqtt/MQTT.h"
|
||||
#include "target_specific.h"
|
||||
#if !MESTASTIC_EXCLUDE_WIFI
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef DELAY_FOREVER
|
||||
#define DELAY_FOREVER portMAX_DELAY
|
||||
@@ -54,7 +56,20 @@ static const adc_atten_t atten = ADC_ATTENUATION;
|
||||
#endif
|
||||
#endif // BATTERY_PIN && ARCH_ESP32
|
||||
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
#ifndef EXT_CHRG_DETECT_MODE
|
||||
static const uint8_t ext_chrg_detect_mode = INPUT;
|
||||
#else
|
||||
static const uint8_t ext_chrg_detect_mode = EXT_CHRG_DETECT_MODE;
|
||||
#endif
|
||||
#ifndef EXT_CHRG_DETECT_VALUE
|
||||
static const uint8_t ext_chrg_detect_value = HIGH;
|
||||
#else
|
||||
static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
|
||||
INA260Sensor ina260Sensor;
|
||||
INA219Sensor ina219Sensor;
|
||||
INA3221Sensor ina3221Sensor;
|
||||
@@ -169,7 +184,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
virtual uint16_t getBattVoltage() override
|
||||
{
|
||||
|
||||
#if defined(HAS_TELEMETRY) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
if (hasINA()) {
|
||||
LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage\n", config.power.device_battery_ina_address);
|
||||
return getINAVoltage();
|
||||
@@ -208,7 +223,17 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
raw = raw / BATTERY_SENSE_SAMPLES;
|
||||
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
|
||||
#endif
|
||||
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
|
||||
|
||||
if (!initial_read_done) {
|
||||
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
|
||||
if (scaled > last_read_value)
|
||||
last_read_value = scaled;
|
||||
initial_read_done = true;
|
||||
} else {
|
||||
// Already initialized - filter this reading
|
||||
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
|
||||
}
|
||||
|
||||
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u\n", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
|
||||
// (last_read_value));
|
||||
}
|
||||
@@ -322,7 +347,14 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
|
||||
/// Assume charging if we have a battery and external power is connected.
|
||||
/// we can't be smart enough to say 'full'?
|
||||
virtual bool isCharging() override { return isBatteryConnect() && isVbusIn(); }
|
||||
virtual bool isCharging() override
|
||||
{
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
#else
|
||||
return isBatteryConnect() && isVbusIn();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||
@@ -335,10 +367,12 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
|
||||
// Start value from minimum voltage for the filter to not start from 0
|
||||
// that could trigger some events.
|
||||
// This value is over-written by the first ADC reading, it the voltage seems reasonable.
|
||||
bool initial_read_done = false;
|
||||
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
|
||||
uint32_t last_read_time_ms = 0;
|
||||
|
||||
#if defined(HAS_TELEMETRY) && !defined(ARCH_PORTDUINO)
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
|
||||
uint16_t getINAVoltage()
|
||||
{
|
||||
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
|
||||
@@ -389,6 +423,9 @@ bool Power::analogInit()
|
||||
#ifdef EXT_PWR_DETECT
|
||||
pinMode(EXT_PWR_DETECT, INPUT);
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
|
||||
#endif
|
||||
|
||||
#ifdef BATTERY_PIN
|
||||
LOG_DEBUG("Using analog input %d for battery level\n", BATTERY_PIN);
|
||||
@@ -475,12 +512,7 @@ void Power::shutdown()
|
||||
{
|
||||
LOG_INFO("Shutting down\n");
|
||||
|
||||
#ifdef HAS_PMU
|
||||
if (pmu_found == true) {
|
||||
PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF);
|
||||
PMU->shutdown();
|
||||
}
|
||||
#elif defined(ARCH_NRF52) || defined(ARCH_ESP32)
|
||||
#if defined(ARCH_NRF52) || defined(ARCH_ESP32)
|
||||
#ifdef PIN_LED1
|
||||
ledOff(PIN_LED1);
|
||||
#endif
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* actions to be taken upon entering or exiting each state.
|
||||
*/
|
||||
#include "PowerFSM.h"
|
||||
#include "Default.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "configuration.h"
|
||||
@@ -16,6 +17,10 @@
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
|
||||
#ifndef SLEEP_TIME
|
||||
#define SLEEP_TIME 30
|
||||
#endif
|
||||
|
||||
/// Should we behave as if we have AC power now?
|
||||
static bool isPowered()
|
||||
{
|
||||
@@ -45,7 +50,7 @@ static void sdsEnter()
|
||||
{
|
||||
LOG_DEBUG("Enter state: SDS\n");
|
||||
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
||||
doDeepSleep(getConfiguredOrDefaultMs(config.power.sds_secs), false);
|
||||
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false);
|
||||
}
|
||||
|
||||
extern Power *power;
|
||||
@@ -80,7 +85,7 @@ static void lsIdle()
|
||||
// If some other service would stall sleep, don't let sleep happen yet
|
||||
if (doPreflightSleep()) {
|
||||
// Briefly come out of sleep long enough to blink the led once every few seconds
|
||||
uint32_t sleepTime = 30;
|
||||
uint32_t sleepTime = SLEEP_TIME;
|
||||
|
||||
setLed(false); // Never leave led on while in light sleep
|
||||
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
|
||||
@@ -102,9 +107,7 @@ static void lsIdle()
|
||||
break;
|
||||
|
||||
default:
|
||||
// We woke for some other reason (button press, device interrupt)
|
||||
// uint64_t status = esp_sleep_get_ext1_wakeup_status();
|
||||
LOG_INFO("wakeCause2 %d\n", wakeCause2);
|
||||
// We woke for some other reason (button press, device IRQ interrupt)
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
bool pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
|
||||
@@ -182,10 +185,12 @@ static void powerEnter()
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
// within enter() the function getState() returns the state we came from
|
||||
if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 &&
|
||||
|
||||
// Mothballed: print change of power-state to device screen
|
||||
/* if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 &&
|
||||
strcmp(powerFSM.getState()->name, "DARK") != 0) {
|
||||
screen->print("Powered...\n");
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,8 +207,10 @@ static void powerExit()
|
||||
{
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
if (!isPowered())
|
||||
screen->print("Unpowered...\n");
|
||||
|
||||
// Mothballed: print change of power-state to device screen
|
||||
/*if (!isPowered())
|
||||
screen->print("Unpowered...\n");*/
|
||||
}
|
||||
|
||||
static void onEnter()
|
||||
@@ -245,7 +252,6 @@ Fsm powerFSM(&stateBOOT);
|
||||
void PowerFSM_setup()
|
||||
{
|
||||
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
|
||||
bool isInfrastructureRole = isRouter || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER;
|
||||
bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
|
||||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER ||
|
||||
config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;
|
||||
@@ -343,13 +349,10 @@ void PowerFSM_setup()
|
||||
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
||||
|
||||
powerFSM.add_timed_transition(&stateON, &stateDARK,
|
||||
getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
|
||||
Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
|
||||
"Screen-on timeout");
|
||||
powerFSM.add_timed_transition(&statePOWER, &stateDARK,
|
||||
getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
|
||||
"Screen-on timeout");
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateDARK,
|
||||
getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
|
||||
Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
|
||||
"Screen-on timeout");
|
||||
|
||||
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
|
||||
@@ -358,13 +361,27 @@ void PowerFSM_setup()
|
||||
// Don't add power saving transitions if we are a power saving tracker or sensor. Sleep will be initiatiated through the
|
||||
// modules
|
||||
if ((isRouter || config.power.is_power_saving) && !isTrackerOrSensor) {
|
||||
powerFSM.add_timed_transition(&stateNB, isInfrastructureRole ? &stateSDS : &stateLS,
|
||||
getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL,
|
||||
powerFSM.add_timed_transition(&stateNB, &stateLS,
|
||||
Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL,
|
||||
"Min wake timeout");
|
||||
powerFSM.add_timed_transition(&stateDARK, isInfrastructureRole ? &stateSDS : &stateLS,
|
||||
getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs),
|
||||
NULL, "Bluetooth timeout");
|
||||
|
||||
// If ESP32 and using power-saving, timer mover from DARK to light-sleep
|
||||
// Also serves purpose of the old DARK to DARK transition(?) See https://github.com/meshtastic/firmware/issues/3517
|
||||
powerFSM.add_timed_transition(
|
||||
&stateDARK, &stateLS,
|
||||
Default::getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL,
|
||||
"Bluetooth timeout");
|
||||
} else {
|
||||
// If ESP32, but not using power-saving, check periodically if config has drifted out of stateDark
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateDARK,
|
||||
Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs),
|
||||
NULL, "Screen-on timeout");
|
||||
}
|
||||
#else
|
||||
// If not ESP32, light-sleep not used. Check periodically if config has drifted out of stateDark
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateDARK,
|
||||
Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
|
||||
"Screen-on timeout");
|
||||
#endif
|
||||
|
||||
powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "Default.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
@@ -28,7 +29,7 @@ class PowerFSMThread : public OSThread
|
||||
timeLastPowered = millis();
|
||||
} else if (config.power.on_battery_shutdown_after_secs > 0 && config.power.on_battery_shutdown_after_secs != UINT32_MAX &&
|
||||
millis() > (timeLastPowered +
|
||||
getConfiguredOrDefaultMs(
|
||||
Default::getConfiguredOrDefaultMs(
|
||||
config.power.on_battery_shutdown_after_secs))) { // shutdown after 30 minutes unpowered
|
||||
powerFSM.trigger(EVENT_SHUTDOWN);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
||||
|
||||
// If we are the first message on a report, include the header
|
||||
if (!isContinuationMessage) {
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
|
||||
if (rtc_sec > 0) {
|
||||
long hms = rtc_sec % SEC_PER_DAY;
|
||||
// hms += tz.tz_dsttime * SEC_PER_HOUR;
|
||||
@@ -182,11 +182,11 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
||||
void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len)
|
||||
{
|
||||
const char alphabet[17] = "0123456789abcdef";
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+\n");
|
||||
log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |\n");
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+\n");
|
||||
log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |\n");
|
||||
for (uint16_t i = 0; i < len; i += 16) {
|
||||
if (i % 128 == 0)
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+\n");
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+\n");
|
||||
char s[] = "| | | |\n";
|
||||
uint8_t ix = 1, iy = 52;
|
||||
for (uint8_t j = 0; j < 16; j++) {
|
||||
@@ -208,7 +208,7 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
|
||||
log(logLevel, ".");
|
||||
log(logLevel, s);
|
||||
}
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+\n");
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+\n");
|
||||
}
|
||||
|
||||
std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
#include "time.h"
|
||||
|
||||
#ifdef RP2040_SLOW_CLOCK
|
||||
#define Port Serial2
|
||||
#else
|
||||
#define Port Serial
|
||||
#endif
|
||||
// Defaulting to the formerly removed phone_timeout_secs value of 15 minutes
|
||||
#define SERIAL_CONNECTION_TIMEOUT (15 * 60) * 1000UL
|
||||
|
||||
@@ -31,6 +36,10 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
|
||||
canWrite = false; // We don't send packets to our port until it has talked to us first
|
||||
// setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks
|
||||
|
||||
#ifdef RP2040_SLOW_CLOCK
|
||||
Port.setTX(SERIAL2_TX);
|
||||
Port.setRX(SERIAL2_RX);
|
||||
#endif
|
||||
Port.begin(SERIAL_BAUD);
|
||||
#if defined(ARCH_NRF52) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_RP2040)
|
||||
time_t timeout = millis();
|
||||
@@ -42,7 +51,9 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if !ARCH_PORTDUINO
|
||||
emitRebooted();
|
||||
#endif
|
||||
}
|
||||
|
||||
int32_t SerialConsole::runOnce()
|
||||
@@ -64,7 +75,7 @@ bool SerialConsole::checkIsConnected()
|
||||
|
||||
/**
|
||||
* we override this to notice when we've received a protobuf over the serial
|
||||
* stream. Then we shunt off debug serial output.
|
||||
* stream. Then we shut off debug serial output.
|
||||
*/
|
||||
bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
|
||||
{
|
||||
|
||||
@@ -74,6 +74,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define RTC_DATA_ATTR
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Regulatory overrides for producing regional builds
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Define if region should override user saved region
|
||||
// #define LORA_REGIONCODE meshtastic_Config_LoRaConfig_RegionCode_SG_923
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Feature toggles
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -111,6 +118,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define MCP9808_ADDR 0x18
|
||||
#define INA_ADDR 0x40
|
||||
#define INA_ADDR_ALTERNATE 0x41
|
||||
#define INA_ADDR_WAVESHARE_UPS 0x43
|
||||
#define INA3221_ADDR 0x42
|
||||
#define QMC6310_ADDR 0x1C
|
||||
#define QMI8658_ADDR 0x6B
|
||||
@@ -118,8 +126,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define SHTC3_ADDR 0x70
|
||||
#define LPS22HB_ADDR 0x5C
|
||||
#define LPS22HB_ADDR_ALT 0x5D
|
||||
#define SHT31_ADDR 0x44
|
||||
#define SHT31_4x_ADDR 0x44
|
||||
#define PMSA0031_ADDR 0x12
|
||||
#define RCWL9620_ADDR 0x57
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ACCELEROMETER
|
||||
@@ -127,6 +136,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define MPU6050_ADDR 0x68
|
||||
#define LIS3DH_ADR 0x18
|
||||
#define BMA423_ADDR 0x19
|
||||
#define LSM6DS3_ADDR 0x6A
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LED
|
||||
@@ -136,9 +146,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// -----------------------------------------------------------------------------
|
||||
// Security
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define ATECC608B_ADDR 0x35
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// IO Expander
|
||||
// -----------------------------------------------------------------------------
|
||||
#define TCA9555_ADDR 0x26
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// GPS
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -160,19 +174,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
also enable HAS_ option not specifically disabled by variant.h */
|
||||
#include "architecture.h"
|
||||
|
||||
#ifndef DEFAULT_REBOOT_SECONDS
|
||||
#define DEFAULT_REBOOT_SECONDS 7
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_SHUTDOWN_SECONDS
|
||||
#define DEFAULT_SHUTDOWN_SECONDS 2
|
||||
#endif
|
||||
|
||||
/* Step #3: mop up with disabled values for HAS_ options not handled by the above two */
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// GPS
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#ifndef GPS_BAUDRATE
|
||||
#define GPS_BAUDRATE 9600
|
||||
#endif
|
||||
#ifndef GPS_THREAD_INTERVAL
|
||||
#define GPS_THREAD_INTERVAL 100
|
||||
#endif
|
||||
|
||||
#ifndef HAS_WIFI
|
||||
#define HAS_WIFI 0
|
||||
#endif
|
||||
@@ -223,7 +234,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#error HW_VENDOR must be defined
|
||||
#endif
|
||||
|
||||
// global switch to turn off all optional modules for a minimzed build
|
||||
// -----------------------------------------------------------------------------
|
||||
// Global switches to turn off features for a minimized build
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// #define MESHTASTIC_MINIMIZE_BUILD 1
|
||||
#ifdef MESHTASTIC_MINIMIZE_BUILD
|
||||
#define MESHTASTIC_EXCLUDE_MODULES 1
|
||||
#define MESHTASTIC_EXCLUDE_WIFI 1
|
||||
#define MESHTASTIC_EXCLUDE_BLUETOOTH 1
|
||||
#define MESHTASTIC_EXCLUDE_GPS 1
|
||||
#define MESHTASTIC_EXCLUDE_SCREEN 1
|
||||
#define MESHTASTIC_EXCLUDE_MQTT 1
|
||||
#endif
|
||||
|
||||
// Turn off all optional modules
|
||||
#ifdef MESHTASTIC_EXCLUDE_MODULES
|
||||
#define MESHTASTIC_EXCLUDE_AUDIO 1
|
||||
#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
|
||||
@@ -242,3 +267,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define MESHTASTIC_EXCLUDE_INPUTBROKER 1
|
||||
#define MESHTASTIC_EXCLUDE_SERIAL 1
|
||||
#endif
|
||||
|
||||
// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)
|
||||
#ifdef MESHTASTIC_EXCLUDE_WIFI
|
||||
#define MESHTASTIC_EXCLUDE_WEBSERVER 1
|
||||
#undef HAS_WIFI
|
||||
#define HAS_WIFI 0
|
||||
#endif
|
||||
|
||||
// // Turn off Bluetooth
|
||||
#ifdef MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
#undef HAS_BLUETOOTH
|
||||
#define HAS_BLUETOOTH 0
|
||||
#endif
|
||||
|
||||
// // Turn off GPS
|
||||
#ifdef MESHTASTIC_EXCLUDE_GPS
|
||||
#undef HAS_GPS
|
||||
#define HAS_GPS 0
|
||||
#undef MESHTASTIC_EXCLUDE_RANGETEST
|
||||
#define MESHTASTIC_EXCLUDE_RANGETEST 1
|
||||
#endif
|
||||
|
||||
// Turn off Screen
|
||||
#ifdef MESHTASTIC_EXCLUDE_SCREEN
|
||||
#undef HAS_SCREEN
|
||||
#define HAS_SCREEN 0
|
||||
#endif
|
||||
5
src/detect/LoRaRadioType.h
Normal file
5
src/detect/LoRaRadioType.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
enum LoRaRadioType { NO_RADIO, STM32WLx_RADIO, SIM_RADIO, RF95_RADIO, SX1262_RADIO, SX1268_RADIO, LLCC68_RADIO, SX1280_RADIO };
|
||||
|
||||
extern LoRaRadioType radioType;
|
||||
@@ -36,8 +36,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423};
|
||||
return firstOfOrNONE(3, types);
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3};
|
||||
return firstOfOrNONE(4, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const
|
||||
|
||||
@@ -29,6 +29,7 @@ class ScanI2C
|
||||
INA3221,
|
||||
MCP9808,
|
||||
SHT31,
|
||||
SHT4X,
|
||||
SHTC3,
|
||||
LPS22HB,
|
||||
QMC6310,
|
||||
@@ -38,9 +39,11 @@ class ScanI2C
|
||||
MPU6050,
|
||||
LIS3DH,
|
||||
BMA423,
|
||||
#ifdef HAS_NCP5623
|
||||
BQ24295,
|
||||
LSM6DS3,
|
||||
TCA9555,
|
||||
RCWL9620,
|
||||
NCP5623,
|
||||
#endif
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -183,8 +183,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
|
||||
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
case ATECC608B_ADDR:
|
||||
type = ATECC608B;
|
||||
if (atecc.begin(addr.address) == true) {
|
||||
#ifdef RP2040_SLOW_CLOCK
|
||||
if (atecc.begin(addr.address, Wire, Serial2) == true)
|
||||
#else
|
||||
if (atecc.begin(addr.address) == true)
|
||||
#endif
|
||||
|
||||
{
|
||||
LOG_INFO("ATECC608B initialized\n");
|
||||
} else {
|
||||
LOG_WARN("ATECC608B initialization failed\n");
|
||||
@@ -254,6 +259,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
|
||||
|
||||
case INA_ADDR:
|
||||
case INA_ADDR_ALTERNATE:
|
||||
case INA_ADDR_WAVESHARE_UPS:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
|
||||
LOG_DEBUG("Register MFG_UID: 0x%x\n", registerValue);
|
||||
if (registerValue == 0x5449) {
|
||||
@@ -265,8 +271,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
|
||||
}
|
||||
break;
|
||||
case INA3221_ADDR:
|
||||
LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address);
|
||||
type = INA3221;
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
|
||||
LOG_DEBUG("Register MFG_UID: 0x%x\n", registerValue);
|
||||
if (registerValue == 0x5449) {
|
||||
LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address);
|
||||
type = INA3221;
|
||||
} else { // Unknown device
|
||||
LOG_INFO("No INA3221 found at address 0x%x\n", (uint8_t)addr.address);
|
||||
}
|
||||
break;
|
||||
case MCP9808_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2);
|
||||
@@ -280,19 +292,50 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
|
||||
|
||||
break;
|
||||
|
||||
SCAN_SIMPLE_CASE(SHT31_ADDR, SHT31, "SHT31 sensor found\n")
|
||||
case SHT31_4x_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
|
||||
if (registerValue == 0x11a2) {
|
||||
type = SHT4X;
|
||||
LOG_INFO("SHT4X sensor found\n");
|
||||
} else {
|
||||
type = SHT31;
|
||||
LOG_INFO("SHT31 sensor found\n");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n")
|
||||
SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n")
|
||||
|
||||
case LPS22HB_ADDR_ALT:
|
||||
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n")
|
||||
|
||||
SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310 Highrate 3-Axis magnetic sensor found\n")
|
||||
SCAN_SIMPLE_CASE(QMI8658_ADDR, QMI8658, "QMI8658 Highrate 6-Axis inertial measurement sensor found\n")
|
||||
|
||||
case QMI8658_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID
|
||||
if (registerValue == 0xC0) {
|
||||
type = BQ24295;
|
||||
LOG_INFO("BQ24295 PMU found\n");
|
||||
break;
|
||||
}
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID
|
||||
if (registerValue == 0x6A) {
|
||||
type = LSM6DS3;
|
||||
LOG_INFO("LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address);
|
||||
} else {
|
||||
type = QMI8658;
|
||||
LOG_INFO("QMI8658 Highrate 6-Axis inertial measurement sensor found\n");
|
||||
}
|
||||
break;
|
||||
|
||||
SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L Highrate 3-Axis magnetic sensor found\n")
|
||||
|
||||
SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n")
|
||||
SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n");
|
||||
SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n");
|
||||
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n");
|
||||
|
||||
default:
|
||||
LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address);
|
||||
@@ -325,4 +368,4 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
|
||||
size_t ScanI2CTwoWire::countDevices() const
|
||||
{
|
||||
return foundDevices.size();
|
||||
}
|
||||
}
|
||||
|
||||
242
src/gps/GPS.cpp
242
src/gps/GPS.cpp
@@ -1,9 +1,14 @@
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "Default.h"
|
||||
#include "GPS.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#include "main.h" // pmu_found
|
||||
#include "sleep.h"
|
||||
|
||||
#include "cas.h"
|
||||
#include "ubx.h"
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
@@ -48,6 +53,28 @@ void GPS::UBXChecksum(uint8_t *message, size_t length)
|
||||
message[length - 1] = CK_B;
|
||||
}
|
||||
|
||||
// Calculate the checksum for a CAS packet
|
||||
void GPS::CASChecksum(uint8_t *message, size_t length)
|
||||
{
|
||||
uint32_t cksum = ((uint32_t)message[5] << 24); // Message ID
|
||||
cksum += ((uint32_t)message[4]) << 16; // Class
|
||||
cksum += message[2]; // Payload Len
|
||||
|
||||
// Iterate over the payload as a series of uint32_t's and
|
||||
// accumulate the cksum
|
||||
uint32_t const *payload = (uint32_t *)(message + 6);
|
||||
for (size_t i = 0; i < (length - 10) / 4; i++) {
|
||||
uint32_t pl = payload[i];
|
||||
cksum += pl;
|
||||
}
|
||||
|
||||
// Place the checksum values in the message
|
||||
message[length - 4] = (cksum & 0xFF);
|
||||
message[length - 3] = (cksum & (0xFF << 8)) >> 8;
|
||||
message[length - 2] = (cksum & (0xFF << 16)) >> 16;
|
||||
message[length - 1] = (cksum & (0xFF << 24)) >> 24;
|
||||
}
|
||||
|
||||
// Function to create a ublox packet for editing in memory
|
||||
uint8_t GPS::makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg)
|
||||
{
|
||||
@@ -69,6 +96,41 @@ uint8_t GPS::makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_siz
|
||||
return (payload_size + 8);
|
||||
}
|
||||
|
||||
// Function to create a CAS packet for editing in memory
|
||||
uint8_t GPS::makeCASPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg)
|
||||
{
|
||||
// General CAS structure
|
||||
// | H1 | H2 | payload_len | cls | msg | Payload ... | Checksum |
|
||||
// Size: | 1 | 1 | 2 | 1 | 1 | payload_len | 4 |
|
||||
// Pos: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 ... | 6 + payload_len ... |
|
||||
// |------|------|-------------|------|------|------|--------------|---------------------------|
|
||||
// | 0xBA | 0xCE | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX ... | 0xXX | 0xXX | 0xXX | 0xXX |
|
||||
|
||||
// Construct the CAS packet
|
||||
UBXscratch[0] = 0xBA; // header 1 (0xBA)
|
||||
UBXscratch[1] = 0xCE; // header 2 (0xCE)
|
||||
UBXscratch[2] = payload_size; // length 1
|
||||
UBXscratch[3] = 0; // length 2
|
||||
UBXscratch[4] = class_id; // class
|
||||
UBXscratch[5] = msg_id; // id
|
||||
|
||||
UBXscratch[6 + payload_size] = 0x00; // Checksum
|
||||
UBXscratch[7 + payload_size] = 0x00;
|
||||
UBXscratch[8 + payload_size] = 0x00;
|
||||
UBXscratch[9 + payload_size] = 0x00;
|
||||
|
||||
for (int i = 0; i < payload_size; i++) {
|
||||
UBXscratch[6 + i] = pgm_read_byte(&msg[i]);
|
||||
}
|
||||
CASChecksum(UBXscratch, (payload_size + 10));
|
||||
|
||||
#if defined(GPS_DEBUG) && defined(DEBUG_PORT)
|
||||
LOG_DEBUG("Constructed CAS packet: \n");
|
||||
DEBUG_PORT.hexDump(MESHTASTIC_LOG_LEVEL_DEBUG, UBXscratch, payload_size + 10);
|
||||
#endif
|
||||
return (payload_size + 10);
|
||||
}
|
||||
|
||||
GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
|
||||
{
|
||||
uint8_t buffer[768] = {0};
|
||||
@@ -78,6 +140,7 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
|
||||
while (millis() < startTimeout) {
|
||||
if (_serial_gps->available()) {
|
||||
b = _serial_gps->read();
|
||||
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG("%02X", (char *)buffer);
|
||||
#endif
|
||||
@@ -101,6 +164,67 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
|
||||
return GNSS_RESPONSE_NONE;
|
||||
}
|
||||
|
||||
GPS_RESPONSE GPS::getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
|
||||
{
|
||||
uint32_t startTime = millis();
|
||||
uint8_t buffer[CAS_ACK_NACK_MSG_SIZE] = {0};
|
||||
uint8_t bufferPos = 0;
|
||||
|
||||
// CAS-ACK-(N)ACK structure
|
||||
// | H1 | H2 | Payload Len | cls | msg | Payload | Checksum (4) |
|
||||
// | | | | | | Cls | Msg | Reserved | |
|
||||
// |------|------|-------------|------|------|------|------|-------------|---------------------------|
|
||||
// ACK-NACK| 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x00 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
|
||||
// ACK-ACK | 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x01 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
|
||||
|
||||
while (millis() - startTime < waitMillis) {
|
||||
if (_serial_gps->available()) {
|
||||
buffer[bufferPos++] = _serial_gps->read();
|
||||
|
||||
// keep looking at the first two bytes of buffer until
|
||||
// we have found the CAS frame header (0xBA, 0xCE), if not
|
||||
// keep reading bytes until we find a frame header or we run
|
||||
// out of time.
|
||||
if ((bufferPos == 2) && !(buffer[0] == 0xBA && buffer[1] == 0xCE)) {
|
||||
buffer[0] = buffer[1];
|
||||
buffer[1] = 0;
|
||||
bufferPos = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// we have read all the bytes required for the Ack/Nack (14-bytes)
|
||||
// and we must have found a frame to get this far
|
||||
if (bufferPos == sizeof(buffer) - 1) {
|
||||
uint8_t msg_cls = buffer[4]; // message class should be 0x05
|
||||
uint8_t msg_msg_id = buffer[5]; // message id should be 0x00 or 0x01
|
||||
uint8_t payload_cls = buffer[6]; // payload class id
|
||||
uint8_t payload_msg = buffer[7]; // payload message id
|
||||
|
||||
// Check for an ACK-ACK for the specified class and message id
|
||||
if ((msg_cls == 0x05) && (msg_msg_id == 0x01) && payload_cls == class_id && payload_msg == msg_id) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_INFO("Got ACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime);
|
||||
#endif
|
||||
return GNSS_RESPONSE_OK;
|
||||
}
|
||||
|
||||
// Check for an ACK-NACK for the specified class and message id
|
||||
if ((msg_cls == 0x05) && (msg_msg_id == 0x00) && payload_cls == class_id && payload_msg == msg_id) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_WARN("Got NACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime);
|
||||
#endif
|
||||
return GNSS_RESPONSE_NAK;
|
||||
}
|
||||
|
||||
// This isn't the frame we are looking for, clear the buffer
|
||||
// and try again until we run out of time.
|
||||
memset(buffer, 0x0, sizeof(buffer));
|
||||
bufferPos = 0;
|
||||
}
|
||||
}
|
||||
return GNSS_RESPONSE_NONE;
|
||||
}
|
||||
|
||||
GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
|
||||
{
|
||||
uint8_t b;
|
||||
@@ -290,6 +414,53 @@ bool GPS::setup()
|
||||
// Switch to Vehicle Mode, since SoftRF enables Aviation < 2g
|
||||
_serial_gps->write("$PCAS11,3*1E\r\n");
|
||||
delay(250);
|
||||
} else if (gnssModel == GNSS_MODEL_MTK_L76B) {
|
||||
// Waveshare Pico-GPS hat uses the L76B with 9600 baud
|
||||
// Initialize the L76B Chip, use GPS + GLONASS
|
||||
// See note in L76_Series_GNSS_Protocol_Specification, chapter 3.29
|
||||
_serial_gps->write("$PMTK353,1,1,0,0,0*2B\r\n");
|
||||
// Above command will reset the GPS and takes longer before it will accept new commands
|
||||
delay(1000);
|
||||
// only ask for RMC and GGA (GNRMC and GNGGA)
|
||||
// See note in L76_Series_GNSS_Protocol_Specification, chapter 2.1
|
||||
_serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
|
||||
delay(250);
|
||||
// Enable SBAS
|
||||
_serial_gps->write("$PMTK301,2*2E\r\n");
|
||||
delay(250);
|
||||
// Enable PPS for 2D/3D fix only
|
||||
_serial_gps->write("$PMTK285,3,100*3F\r\n");
|
||||
delay(250);
|
||||
// Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s)
|
||||
_serial_gps->write("$PMTK886,1*29\r\n");
|
||||
delay(250);
|
||||
} else if (gnssModel == GNSS_MODEL_ATGM336H) {
|
||||
// Set the intial configuration of the device - these _should_ work for most AT6558 devices
|
||||
msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H - Could not set Configuration");
|
||||
}
|
||||
|
||||
// Set the update frequence to 1Hz
|
||||
msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H - Could not set Update Frequency");
|
||||
}
|
||||
|
||||
// Set the NEMA output messages
|
||||
// Ask for only RMC and GGA
|
||||
uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA};
|
||||
for (unsigned int i = 0; i < sizeof(fields); i++) {
|
||||
// Construct a CAS-CFG-MSG packet
|
||||
uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00};
|
||||
msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x01, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H - Could not enable NMEA MSG: %d\n", fields[i]);
|
||||
}
|
||||
}
|
||||
} else if (gnssModel == GNSS_MODEL_UC6580) {
|
||||
// The Unicore UC6580 can use a lot of sat systems, enable it to
|
||||
// use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS
|
||||
@@ -475,7 +646,6 @@ bool GPS::setup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// LOG_INFO("u-blox M10 hardware found.\n");
|
||||
delay(1000);
|
||||
@@ -625,17 +795,27 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76K and clones
|
||||
#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones
|
||||
if (on) {
|
||||
LOG_INFO("Waking GPS\n");
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
// Some PCB's use an inverse logic due to a transistor driver
|
||||
// Example for this is the Pico-Waveshare Lora+GPS HAT
|
||||
#ifdef PIN_GPS_STANDBY_INVERTED
|
||||
digitalWrite(PIN_GPS_STANDBY, 0);
|
||||
#else
|
||||
digitalWrite(PIN_GPS_STANDBY, 1);
|
||||
#endif
|
||||
return;
|
||||
} else {
|
||||
LOG_INFO("GPS entering sleep\n");
|
||||
// notifyGPSSleep.notifyObservers(NULL);
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
#ifdef PIN_GPS_STANDBY_INVERTED
|
||||
digitalWrite(PIN_GPS_STANDBY, 1);
|
||||
#else
|
||||
digitalWrite(PIN_GPS_STANDBY, 0);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -729,7 +909,7 @@ uint32_t GPS::getWakeTime() const
|
||||
if (t == UINT32_MAX)
|
||||
return t; // already maxint
|
||||
|
||||
return getConfiguredOrDefaultMs(t, default_broadcast_interval_secs);
|
||||
return Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs);
|
||||
}
|
||||
|
||||
/** Get how long we should sleep between aqusition attempts in msecs
|
||||
@@ -745,7 +925,7 @@ uint32_t GPS::getSleepTime() const
|
||||
if (t == UINT32_MAX)
|
||||
return t; // already maxint
|
||||
|
||||
return t * 1000;
|
||||
return Default::getConfiguredOrDefaultMs(t, default_gps_update_interval);
|
||||
}
|
||||
|
||||
void GPS::publishUpdate()
|
||||
@@ -785,7 +965,7 @@ int32_t GPS::runOnce()
|
||||
LOG_WARN("GPS FactoryReset requested\n");
|
||||
if (gps->factoryReset()) { // If we don't succeed try again next time
|
||||
devicestate.did_gps_reset = true;
|
||||
nodeDB.saveToDisk(SEGMENT_DEVICESTATE);
|
||||
nodeDB->saveToDisk(SEGMENT_DEVICESTATE);
|
||||
}
|
||||
}
|
||||
GPSInitFinished = true;
|
||||
@@ -805,7 +985,7 @@ int32_t GPS::runOnce()
|
||||
if (devicestate.did_gps_reset && (millis() - lastWakeStartMsec > 60000) && !hasFlow()) {
|
||||
LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n");
|
||||
devicestate.did_gps_reset = false;
|
||||
nodeDB.saveDeviceStateToDisk();
|
||||
nodeDB->saveDeviceStateToDisk();
|
||||
return disable(); // Stop the GPS thread as it can do nothing useful until next reboot.
|
||||
}
|
||||
}
|
||||
@@ -916,10 +1096,18 @@ GnssModel_t GPS::probe(int serialSpeed)
|
||||
uint8_t buffer[768] = {0};
|
||||
delay(100);
|
||||
|
||||
// Close all NMEA sentences , Only valid for MTK platform
|
||||
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
|
||||
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
|
||||
delay(20);
|
||||
|
||||
// Get version information
|
||||
clearBuffer();
|
||||
_serial_gps->write("$PCAS06,1*1A\r\n");
|
||||
if (getACK("$GPTXT,01,01,02,HW=ATGM336H", 500) == GNSS_RESPONSE_OK) {
|
||||
LOG_INFO("ATGM336H GNSS init succeeded, using ATGM336H Module\n");
|
||||
return GNSS_MODEL_ATGM336H;
|
||||
}
|
||||
|
||||
// Get version information
|
||||
clearBuffer();
|
||||
_serial_gps->write("$PCAS06,0*1B\r\n");
|
||||
@@ -928,6 +1116,18 @@ GnssModel_t GPS::probe(int serialSpeed)
|
||||
return GNSS_MODEL_MTK;
|
||||
}
|
||||
|
||||
// Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS)
|
||||
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
|
||||
delay(20);
|
||||
|
||||
// Get version information
|
||||
clearBuffer();
|
||||
_serial_gps->write("$PMTK605*31\r\n");
|
||||
if (getACK("Quectel-L76B", 500) == GNSS_RESPONSE_OK) {
|
||||
LOG_INFO("L76B GNSS init succeeded, using L76B GNSS Module\n");
|
||||
return GNSS_MODEL_MTK_L76B;
|
||||
}
|
||||
|
||||
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
|
||||
UBXChecksum(cfg_rate, sizeof(cfg_rate));
|
||||
clearBuffer();
|
||||
@@ -1109,7 +1309,6 @@ GPS *GPS::createGps()
|
||||
LOG_DEBUG("Using GPIO%d for GPS RX\n", new_gps->rx_gpio);
|
||||
LOG_DEBUG("Using GPIO%d for GPS TX\n", new_gps->tx_gpio);
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
|
||||
|
||||
#else
|
||||
_serial_gps->begin(GPS_BAUDRATE);
|
||||
#endif
|
||||
@@ -1168,7 +1367,21 @@ bool GPS::factoryReset()
|
||||
// byte _message_CFG_RST_COLDSTART[] = {0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0xFF, 0xB9, 0x00, 0x00, 0xC6, 0x8B};
|
||||
// _serial_gps->write(_message_CFG_RST_COLDSTART, sizeof(_message_CFG_RST_COLDSTART));
|
||||
// delay(1000);
|
||||
} else if (gnssModel == GNSS_MODEL_MTK) {
|
||||
// send the CAS10 to perform a factory restart of the device (and other device that support PCAS statements)
|
||||
LOG_INFO("GNSS Factory Reset via PCAS10,3\n");
|
||||
_serial_gps->write("$PCAS10,3*1F\r\n");
|
||||
delay(100);
|
||||
} else if (gnssModel == GNSS_MODEL_ATGM336H) {
|
||||
LOG_INFO("Factory Reset via CAS-CFG-RST\n");
|
||||
uint8_t msglen = makeCASPacket(0x06, 0x02, sizeof(_message_CAS_CFG_RST_FACTORY), _message_CAS_CFG_RST_FACTORY);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
delay(100);
|
||||
} else {
|
||||
// fire this for good measure, if we have an L76B - won't harm other devices.
|
||||
_serial_gps->write("$PMTK104*37\r\n");
|
||||
// No PMTK_ACK for this command.
|
||||
delay(100);
|
||||
// send the UBLOX Factory Reset Command regardless of detect state, something is very wrong, just assume it's UBLOX.
|
||||
// Factory Reset
|
||||
byte _message_reset[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0xFF, 0xFB, 0x00, 0x00, 0x00,
|
||||
@@ -1228,7 +1441,7 @@ bool GPS::lookForLocation()
|
||||
|
||||
#ifndef TINYGPS_OPTION_NO_STATISTICS
|
||||
if (reader.failedChecksum() > lastChecksumFailCount) {
|
||||
LOG_WARN("Warning, %u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount,
|
||||
LOG_WARN("%u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount,
|
||||
reader.failedChecksum());
|
||||
lastChecksumFailCount = reader.failedChecksum();
|
||||
}
|
||||
@@ -1254,7 +1467,7 @@ bool GPS::lookForLocation()
|
||||
#endif // GPS_EXTRAVERBOSE
|
||||
|
||||
// Is this a new point or are we re-reading the previous one?
|
||||
if (!reader.location.isUpdated())
|
||||
if (!reader.location.isUpdated() && !reader.altitude.isUpdated())
|
||||
return false;
|
||||
|
||||
// check if a complete GPS solution set is available for reading
|
||||
@@ -1327,7 +1540,7 @@ bool GPS::lookForLocation()
|
||||
t.tm_mon = reader.date.month() - 1;
|
||||
t.tm_year = reader.date.year() - 1900;
|
||||
t.tm_isdst = false;
|
||||
p.timestamp = mktime(&t);
|
||||
p.timestamp = gm_mktime(&t);
|
||||
|
||||
// Nice to have, if available
|
||||
if (reader.satellites.isUpdated()) {
|
||||
@@ -1371,7 +1584,7 @@ bool GPS::hasFlow()
|
||||
|
||||
bool GPS::whileIdle()
|
||||
{
|
||||
int charsInBuf = 0;
|
||||
unsigned int charsInBuf = 0;
|
||||
bool isValid = false;
|
||||
if (!isAwake) {
|
||||
clearBuffer();
|
||||
@@ -1431,4 +1644,5 @@ void GPS::toggleGpsMode()
|
||||
LOG_DEBUG("Flag set to true to restore power. GpsMode: ENABLED\n");
|
||||
enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // Exclude GPS
|
||||
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
|
||||
#include "GPSStatus.h"
|
||||
#include "Observer.h"
|
||||
@@ -21,10 +23,12 @@ struct uBloxGnssModelInfo {
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
GNSS_MODEL_ATGM336H,
|
||||
GNSS_MODEL_MTK,
|
||||
GNSS_MODEL_UBLOX,
|
||||
GNSS_MODEL_UC6580,
|
||||
GNSS_MODEL_UNKNOWN,
|
||||
GNSS_MODEL_MTK_L76B
|
||||
} GnssModel_t;
|
||||
|
||||
typedef enum {
|
||||
@@ -92,8 +96,11 @@ class GPS : private concurrency::OSThread
|
||||
|
||||
public:
|
||||
/** If !NULL we will use this serial port to construct our GPS */
|
||||
#if defined(RPI_PICO_WAVESHARE)
|
||||
static SerialUART *_serial_gps;
|
||||
#else
|
||||
static HardwareSerial *_serial_gps;
|
||||
|
||||
#endif
|
||||
static uint8_t _message_PMREQ[];
|
||||
static uint8_t _message_PMREQ_10[];
|
||||
static const uint8_t _message_CFG_RXM_PSM[];
|
||||
@@ -133,6 +140,11 @@ class GPS : private concurrency::OSThread
|
||||
static const uint8_t _message_VALSET_DISABLE_SBAS_RAM[];
|
||||
static const uint8_t _message_VALSET_DISABLE_SBAS_BBR[];
|
||||
|
||||
// CASIC commands for ATGM336H
|
||||
static const uint8_t _message_CAS_CFG_RST_FACTORY[];
|
||||
static const uint8_t _message_CAS_CFG_NAVX_CONF[];
|
||||
static const uint8_t _message_CAS_CFG_RATE_1HZ[];
|
||||
|
||||
meshtastic_Position p = meshtastic_Position_init_default;
|
||||
|
||||
GPS() : concurrency::OSThread("GPS") {}
|
||||
@@ -174,6 +186,7 @@ class GPS : private concurrency::OSThread
|
||||
|
||||
// Create a ublox packet for editing in memory
|
||||
uint8_t makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg);
|
||||
uint8_t makeCASPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg);
|
||||
|
||||
// scratch space for creating ublox packets
|
||||
uint8_t UBXscratch[250] = {0};
|
||||
@@ -184,6 +197,8 @@ class GPS : private concurrency::OSThread
|
||||
GPS_RESPONSE getACK(uint8_t c, uint8_t i, uint32_t waitMillis);
|
||||
GPS_RESPONSE getACK(const char *message, uint32_t waitMillis);
|
||||
|
||||
GPS_RESPONSE getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis);
|
||||
|
||||
/**
|
||||
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
||||
*
|
||||
@@ -243,6 +258,7 @@ class GPS : private concurrency::OSThread
|
||||
|
||||
// Calculate checksum
|
||||
void UBXChecksum(uint8_t *message, size_t length);
|
||||
void CASChecksum(uint8_t *message, size_t length);
|
||||
|
||||
/** Get how long we should stay looking for each aquisition
|
||||
*/
|
||||
@@ -270,4 +286,5 @@ class GPS : private concurrency::OSThread
|
||||
GnssModel_t gnssModel = GNSS_MODEL_UNKNOWN;
|
||||
};
|
||||
|
||||
extern GPS *gps;
|
||||
extern GPS *gps;
|
||||
#endif // Exclude GPS
|
||||
@@ -1,3 +1,4 @@
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "NMEAWPL.h"
|
||||
#include "GeoCoord.h"
|
||||
#include "RTC.h"
|
||||
@@ -74,10 +75,10 @@ uint32_t printWPL(char *buf, size_t bufsz, const meshtastic_Position &pos, const
|
||||
uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos)
|
||||
{
|
||||
GeoCoord geoCoord(pos.latitude_i, pos.longitude_i, pos.altitude);
|
||||
tm *t = localtime((time_t *)&pos.timestamp);
|
||||
tm *t = gmtime((time_t *)&pos.timestamp);
|
||||
if (getRTCQuality() > 0) { // use the device clock if we got time from somewhere. If not, use the GPS timestamp.
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
|
||||
t = localtime((time_t *)&rtc_sec);
|
||||
t = gmtime((time_t *)&rtc_sec);
|
||||
}
|
||||
|
||||
uint32_t len = snprintf(
|
||||
@@ -93,4 +94,6 @@ uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos)
|
||||
}
|
||||
len += snprintf(buf + len, bufsz - len, "*%02X\r\n", chk);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -40,7 +40,7 @@ void readFromRTC()
|
||||
t.tm_hour = rtc.getHour();
|
||||
t.tm_min = rtc.getMinute();
|
||||
t.tm_sec = rtc.getSecond();
|
||||
tv.tv_sec = mktime(&t);
|
||||
tv.tv_sec = gm_mktime(&t);
|
||||
tv.tv_usec = 0;
|
||||
LOG_DEBUG("Read RTC time from RV3028 as %ld\n", tv.tv_sec);
|
||||
timeStartMsec = now;
|
||||
@@ -68,7 +68,7 @@ void readFromRTC()
|
||||
t.tm_hour = tc.hour;
|
||||
t.tm_min = tc.minute;
|
||||
t.tm_sec = tc.second;
|
||||
tv.tv_sec = mktime(&t);
|
||||
tv.tv_sec = gm_mktime(&t);
|
||||
tv.tv_usec = 0;
|
||||
LOG_DEBUG("Read RTC time from PCF8563 as %ld\n", tv.tv_sec);
|
||||
timeStartMsec = now;
|
||||
@@ -104,13 +104,15 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
|
||||
bool shouldSet;
|
||||
if (q > currentQuality) {
|
||||
shouldSet = true;
|
||||
LOG_DEBUG("Upgrading time to quality %d\n", q);
|
||||
} else if (q == RTCQualityGPS && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) {
|
||||
// Every 12 hrs we will slam in a new GPS time, to correct for local RTC clock drift
|
||||
LOG_DEBUG("Upgrading time to quality %s\n", RtcName(q));
|
||||
} else if (q >= RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) {
|
||||
// Every 12 hrs we will slam in a new GPS or Phone GPS / NTP time, to correct for local RTC clock drift
|
||||
shouldSet = true;
|
||||
LOG_DEBUG("Reapplying external time to correct clock drift %ld secs\n", tv->tv_sec);
|
||||
} else
|
||||
} else {
|
||||
shouldSet = false;
|
||||
LOG_DEBUG("Current RTC quality: %s. Ignoring time of RTC quality of %s\n", RtcName(currentQuality), RtcName(q));
|
||||
}
|
||||
|
||||
if (shouldSet) {
|
||||
currentQuality = q;
|
||||
@@ -128,7 +130,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
|
||||
#else
|
||||
rtc.initI2C();
|
||||
#endif
|
||||
tm *t = localtime(&tv->tv_sec);
|
||||
tm *t = gmtime(&tv->tv_sec);
|
||||
rtc.setTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_wday, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
|
||||
LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
||||
t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec);
|
||||
@@ -142,7 +144,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
|
||||
#else
|
||||
rtc.begin();
|
||||
#endif
|
||||
tm *t = localtime(&tv->tv_sec);
|
||||
tm *t = gmtime(&tv->tv_sec);
|
||||
rtc.setDateTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
|
||||
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
||||
t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec);
|
||||
@@ -162,6 +164,24 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
|
||||
}
|
||||
}
|
||||
|
||||
const char *RtcName(RTCQuality quality)
|
||||
{
|
||||
switch (quality) {
|
||||
case RTCQualityNone:
|
||||
return "None";
|
||||
case RTCQualityDevice:
|
||||
return "Device";
|
||||
case RTCQualityFromNet:
|
||||
return "Net";
|
||||
case RTCQualityNTP:
|
||||
return "NTP";
|
||||
case RTCQualityGPS:
|
||||
return "GPS";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the RTC time if the provided time is of higher quality than the current RTC time.
|
||||
*
|
||||
@@ -175,7 +195,9 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
time_t res = mktime(&t);
|
||||
// horrible hack to make mktime TZ agnostic - best practise according to
|
||||
// https://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html
|
||||
time_t res = gm_mktime(&t);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = res;
|
||||
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
|
||||
@@ -189,14 +211,33 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timezone offset in seconds.
|
||||
*
|
||||
* @return The timezone offset in seconds.
|
||||
*/
|
||||
int32_t getTZOffset()
|
||||
{
|
||||
time_t now;
|
||||
struct tm *gmt;
|
||||
now = time(NULL);
|
||||
gmt = gmtime(&now);
|
||||
gmt->tm_isdst = -1;
|
||||
return (int32_t)difftime(now, mktime(gmt));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current time in seconds since the Unix epoch (January 1, 1970).
|
||||
*
|
||||
* @return The current time in seconds since the Unix epoch.
|
||||
*/
|
||||
uint32_t getTime()
|
||||
uint32_t getTime(bool local)
|
||||
{
|
||||
return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||
if (local) {
|
||||
return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs + getTZOffset();
|
||||
} else {
|
||||
return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,7 +246,19 @@ uint32_t getTime()
|
||||
* @param minQuality The minimum quality of the RTC time required for it to be considered valid.
|
||||
* @return The current time from the RTC if it meets the minimum quality requirement, or 0 if the time is not valid.
|
||||
*/
|
||||
uint32_t getValidTime(RTCQuality minQuality)
|
||||
uint32_t getValidTime(RTCQuality minQuality, bool local)
|
||||
{
|
||||
return (currentQuality >= minQuality) ? getTime() : 0;
|
||||
return (currentQuality >= minQuality) ? getTime(local) : 0;
|
||||
}
|
||||
|
||||
time_t gm_mktime(struct tm *tm)
|
||||
{
|
||||
setenv("TZ", "GMT0", 1);
|
||||
time_t res = mktime(tm);
|
||||
if (*config.device.tzdef) {
|
||||
setenv("TZ", config.device.tzdef, 1);
|
||||
} else {
|
||||
setenv("TZ", "UTC0", 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -28,14 +28,19 @@ RTCQuality getRTCQuality();
|
||||
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv);
|
||||
bool perhapsSetRTC(RTCQuality q, struct tm &t);
|
||||
|
||||
/// Return a string name for the quality
|
||||
const char *RtcName(RTCQuality quality);
|
||||
|
||||
/// Return time since 1970 in secs. While quality is RTCQualityNone we will be returning time based at zero
|
||||
uint32_t getTime();
|
||||
uint32_t getTime(bool local = false);
|
||||
|
||||
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
||||
uint32_t getValidTime(RTCQuality minQuality);
|
||||
uint32_t getValidTime(RTCQuality minQuality, bool local = false);
|
||||
|
||||
void readFromRTC();
|
||||
|
||||
time_t gm_mktime(struct tm *tm);
|
||||
|
||||
#define SEC_PER_DAY 86400
|
||||
#define SEC_PER_HOUR 3600
|
||||
#define SEC_PER_MIN 60
|
||||
63
src/gps/cas.h
Normal file
63
src/gps/cas.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
// CASIC binary message definitions
|
||||
// Reference: https://www.icofchina.com/d/file/xiazai/2020-09-22/20f1b42b3a11ac52089caf3603b43fb5.pdf
|
||||
// ATGM33H-5N: https://www.icofchina.com/pro/mokuai/2016-08-01/4.html
|
||||
// (https://www.icofchina.com/d/file/xiazai/2016-12-05/b5c57074f4b1fcc62ba8c7868548d18a.pdf)
|
||||
|
||||
// NEMA (Class ID - 0x4e) message IDs
|
||||
#define CAS_NEMA_GGA 0x00
|
||||
#define CAS_NEMA_GLL 0x01
|
||||
#define CAS_NEMA_GSA 0x02
|
||||
#define CAS_NEMA_GSV 0x03
|
||||
#define CAS_NEMA_RMC 0x04
|
||||
#define CAS_NEMA_VTG 0x05
|
||||
#define CAS_NEMA_GST 0x07
|
||||
#define CAS_NEMA_ZDA 0x08
|
||||
#define CAS_NEMA_DHV 0x0D
|
||||
|
||||
// Size of a CAS-ACK-(N)ACK message (14 bytes)
|
||||
#define CAS_ACK_NACK_MSG_SIZE 0x0E
|
||||
|
||||
// CFG-RST (0x06, 0x02)
|
||||
// Factory reset
|
||||
const uint8_t GPS::_message_CAS_CFG_RST_FACTORY[] = {
|
||||
0xFF, 0x03, // Fields to clear
|
||||
0x01, // Reset Mode: Controlled Software reset
|
||||
0x03 // Startup Mode: Factory
|
||||
};
|
||||
|
||||
// CFG_RATE (0x06, 0x01)
|
||||
// 1HZ update rate, this should always be the case after
|
||||
// factory reset but update it regardless
|
||||
const uint8_t GPS::_message_CAS_CFG_RATE_1HZ[] = {
|
||||
0xE8, 0x03, // Update Rate: 0x03E8 = 1000ms
|
||||
0x00, 0x00 // Reserved
|
||||
};
|
||||
|
||||
// CFG-NAVX (0x06, 0x07)
|
||||
// Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system
|
||||
// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
|
||||
// and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used.
|
||||
const uint8_t GPS::_message_CAS_CFG_NAVX_CONF[] = {
|
||||
0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings
|
||||
0x03, // Dynamic Mode: Automotive
|
||||
0x03, // Fix Mode: Auto 2D/3D
|
||||
0x00, // Min SV
|
||||
0x00, // Max SVs
|
||||
0x00, // Min CNO
|
||||
0x00, // Reserved1
|
||||
0x00, // Init 3D fix
|
||||
0x00, // Min Elevation
|
||||
0x00, // Dr Limit
|
||||
0x07, // Nav System: 2^0 = GPS, 2^1 = BDS 2^2 = GLONASS: 2^3
|
||||
// 3=GPS+BDS, 7=GPS+BDS+GLONASS
|
||||
0x00, 0x00, // Rollover Week
|
||||
0x00, 0x00, 0x00, 0x00, // Fix Altitude
|
||||
0x00, 0x00, 0x00, 0x00, // Fix Height Error
|
||||
0x00, 0x00, 0x00, 0x00, // PDOP Maximum
|
||||
0x00, 0x00, 0x00, 0x00, // TDOP Maximum
|
||||
0x00, 0x00, 0x00, 0x00, // Position Accuracy Max
|
||||
0x00, 0x00, 0x00, 0x00, // Time Accuracy Max
|
||||
0x00, 0x00, 0x00, 0x00 // Static Hold Threshold
|
||||
};
|
||||
@@ -151,31 +151,12 @@ bool EInkDisplay::connect()
|
||||
|
||||
#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();
|
||||
|
||||
// If waking from sleep, need to reverse rtc_gpio_isolate(), called in cpuDeepSleep()
|
||||
// Otherwise, SPI won't work
|
||||
if (wakeReason != ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||
// HSPI + other display pins
|
||||
rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_SCLK);
|
||||
rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_DC);
|
||||
rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_RES);
|
||||
rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_BUSY);
|
||||
rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_CS);
|
||||
rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_MOSI);
|
||||
}
|
||||
|
||||
// Start HSPI
|
||||
hspi = new SPIClass(HSPI);
|
||||
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
|
||||
|
||||
// Enable VExt (ACTIVE LOW)
|
||||
// Unsure if called elsewhere first?
|
||||
delay(100);
|
||||
pinMode(Vext, OUTPUT);
|
||||
digitalWrite(Vext, LOW);
|
||||
delay(100);
|
||||
// VExt already enabled in setup()
|
||||
// RTC GPIO hold disabled in setup()
|
||||
|
||||
// Create GxEPD2 objects
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
|
||||
@@ -184,7 +165,6 @@ bool EInkDisplay::connect()
|
||||
// Init GxEPD2
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh)
|
||||
}
|
||||
#elif defined(PCA10059)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
// Constructor
|
||||
EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
||||
: EInkDisplay(address, sda, scl, geometry, i2cBus)
|
||||
: EInkDisplay(address, sda, scl, geometry, i2cBus), NotifiedWorkerThread("EInkDynamicDisplay")
|
||||
{
|
||||
// If tracking ghost pixels, grab memory
|
||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||
@@ -112,12 +112,15 @@ 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.
|
||||
asyncRefreshRunning = true; // Set the flag - checked in determineMode(); cleared by onNotify()
|
||||
|
||||
if (previousFrameFlags & BLOCKING)
|
||||
awaitRefresh();
|
||||
else
|
||||
LOG_DEBUG("Async full-refresh begins\n");
|
||||
else {
|
||||
// Async begins
|
||||
LOG_DEBUG("Async full-refresh begins (dropping frames)\n");
|
||||
notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); // Hand-off to NotifiedWorkerThread
|
||||
}
|
||||
}
|
||||
|
||||
// Fast Refresh
|
||||
@@ -141,7 +144,7 @@ bool EInkDynamicDisplay::determineMode()
|
||||
checkInitialized();
|
||||
checkForPromotion();
|
||||
#if defined(HAS_EINK_ASYNCFULL)
|
||||
checkAsyncFullRefresh();
|
||||
checkBusyAsyncRefresh();
|
||||
#endif
|
||||
checkRateLimiting();
|
||||
|
||||
@@ -252,6 +255,7 @@ void EInkDynamicDisplay::checkRateLimiting()
|
||||
if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
|
||||
refresh = SKIPPED;
|
||||
reason = EXCEEDED_RATELIMIT_FAST;
|
||||
LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x\n", frameFlags);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -447,9 +451,67 @@ void EInkDynamicDisplay::resetGhostPixelTracking()
|
||||
}
|
||||
#endif // EINK_LIMIT_GHOSTING_PX
|
||||
|
||||
// Handle any asyc tasks
|
||||
void EInkDynamicDisplay::onNotify(uint32_t notification)
|
||||
{
|
||||
// Which task
|
||||
switch (notification) {
|
||||
case DUE_POLL_ASYNCREFRESH:
|
||||
pollAsyncRefresh();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#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()
|
||||
// Public: wait for an refresh already in progress, then run the post-update code. See Screen::setScreensaverFrames()
|
||||
void EInkDynamicDisplay::joinAsyncRefresh()
|
||||
{
|
||||
// If no async refresh running, nothing to do
|
||||
if (!asyncRefreshRunning)
|
||||
return;
|
||||
|
||||
LOG_DEBUG("Joining an async refresh in progress\n");
|
||||
|
||||
// Continually poll the BUSY pin
|
||||
while (adafruitDisplay->epd2.isBusy())
|
||||
yield();
|
||||
|
||||
// If 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("Refresh complete\n");
|
||||
|
||||
// Note: this code only works because of a modification to meshtastic/GxEPD2.
|
||||
// It is only equipped to intercept calls to nextPage()
|
||||
}
|
||||
|
||||
// Called from NotifiedWorkerThread. Run the post-update code if the hardware is ready
|
||||
void EInkDynamicDisplay::pollAsyncRefresh()
|
||||
{
|
||||
// In theory, this condition should never be met
|
||||
if (!asyncRefreshRunning)
|
||||
return;
|
||||
|
||||
// Still running, check back later
|
||||
if (adafruitDisplay->epd2.isBusy()) {
|
||||
// Schedule next call of pollAsyncRefresh()
|
||||
NotifiedWorkerThread::notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// If 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()
|
||||
}
|
||||
|
||||
// Check the status of "async full-refresh"; skip if running
|
||||
void EInkDynamicDisplay::checkBusyAsyncRefresh()
|
||||
{
|
||||
// No refresh taking place, continue with determineMode()
|
||||
if (!asyncRefreshRunning)
|
||||
@@ -473,14 +535,9 @@ void EInkDynamicDisplay::checkAsyncFullRefresh()
|
||||
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()
|
||||
// Async refresh appears to have stopped, but wasn't caught by onNotify()
|
||||
else
|
||||
pollAsyncRefresh(); // Check (and terminate) the async refresh manually
|
||||
}
|
||||
|
||||
// Hold control while an async refresh runs
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "EInkDisplay2.h"
|
||||
#include "GxEPD2_BW.h"
|
||||
#include "concurrency/NotifiedWorkerThread.h"
|
||||
|
||||
/*
|
||||
Derives from the EInkDisplay adapter class.
|
||||
@@ -14,7 +15,7 @@
|
||||
(Full, Fast, Skip)
|
||||
*/
|
||||
|
||||
class EInkDynamicDisplay : public EInkDisplay
|
||||
class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWorkerThread
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
@@ -61,13 +62,20 @@ class EInkDynamicDisplay : public EInkDisplay
|
||||
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()
|
||||
enum notificationTypes : uint8_t { // What was onNotify() called for
|
||||
NONE = 0, // This behavior (NONE=0) is fixed by NotifiedWorkerThread class
|
||||
DUE_POLL_ASYNCREFRESH = 1,
|
||||
};
|
||||
const uint32_t intervalPollAsyncRefresh = 100;
|
||||
|
||||
void onNotify(uint32_t notification) override; // Handle any async tasks - overrides NotifiedWorkerThread
|
||||
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 checkBusyAsyncRefresh()
|
||||
|
||||
// Checks as part of determineMode()
|
||||
void checkInitialized(); // Is this the very first frame?
|
||||
@@ -111,17 +119,30 @@ class EInkDynamicDisplay : public EInkDisplay
|
||||
|
||||
// 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()
|
||||
public:
|
||||
void joinAsyncRefresh(); // Main thread joins an async refresh already in progress. Blocks, then runs post-update code
|
||||
|
||||
protected:
|
||||
void pollAsyncRefresh(); // Run the post-update code if the hardware is ready
|
||||
void checkBusyAsyncRefresh(); // Check if display is busy running an async full-refresh (rejecting new frames)
|
||||
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 checkBusyAsyncRefresh()
|
||||
#else
|
||||
public:
|
||||
void joinAsyncRefresh() {} // Dummy method
|
||||
|
||||
protected:
|
||||
void pollAsyncRefresh() {} // Dummy method. In theory, not reachable
|
||||
#endif
|
||||
};
|
||||
|
||||
// Tidier calls to addFrameFlag() from outside class
|
||||
// Hide the ugly casts used in Screen.cpp
|
||||
#define EINK_ADD_FRAMEFLAG(display, flag) static_cast<EInkDynamicDisplay *>(display)->addFrameFlag(EInkDynamicDisplay::flag)
|
||||
#define EINK_JOIN_ASYNCREFRESH(display) static_cast<EInkDynamicDisplay *>(display)->joinAsyncRefresh()
|
||||
|
||||
#else // !USE_EINK_DYNAMICDISPLAY
|
||||
// Dummy-macro, removes the need for include guards
|
||||
#define EINK_ADD_FRAMEFLAG(display, flag)
|
||||
#define EINK_JOIN_ASYNCREFRESH(display)
|
||||
#endif
|
||||
4
src/graphics/NeoPixel.h
Normal file
4
src/graphics/NeoPixel.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#ifdef HAS_NEOPIXEL
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
extern Adafruit_NeoPixel pixels;
|
||||
#endif
|
||||
@@ -25,7 +25,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include <OLEDDisplay.h>
|
||||
|
||||
#include "DisplayFormatters.h"
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "GPS.h"
|
||||
#endif
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "error.h"
|
||||
@@ -71,7 +73,7 @@ namespace graphics
|
||||
// #define SHOW_REDRAWS
|
||||
|
||||
// A text message frame + debug frame + all the node infos
|
||||
static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
||||
FrameCallback *normalFrames;
|
||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||
static char btPIN[16] = "888888";
|
||||
|
||||
@@ -92,8 +94,15 @@ std::vector<MeshModule *> moduleFrames;
|
||||
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
|
||||
static char ourId[5];
|
||||
|
||||
// vector where symbols (string) are displayed in bottom corner of display.
|
||||
std::vector<std::string> functionSymbals;
|
||||
// string displayed in bottom right corner of display. Created from elements in functionSymbals vector
|
||||
std::string functionSymbalString = "";
|
||||
|
||||
#if HAS_GPS
|
||||
// GeoCoord object for the screen
|
||||
GeoCoord geoCoord;
|
||||
#endif
|
||||
|
||||
#ifdef SHOW_REDRAWS
|
||||
static bool heartbeat = false;
|
||||
@@ -256,16 +265,79 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
#endif
|
||||
}
|
||||
|
||||
// draw overlay in bottom right corner of screen to show when notifications are muted or modifier key is active
|
||||
static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||
{
|
||||
// LOG_DEBUG("Drawing function overlay\n");
|
||||
if (functionSymbals.begin() != functionSymbals.end()) {
|
||||
char buf[64];
|
||||
display->setFont(FONT_SMALL);
|
||||
snprintf(buf, sizeof(buf), "%s", functionSymbalString.c_str());
|
||||
display->drawString(SCREEN_WIDTH - display->getStringWidth(buf), SCREEN_HEIGHT - FONT_HEIGHT_SMALL, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_EINK
|
||||
/// Used on eink displays while in deep sleep
|
||||
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
static void drawDeepSleepScreen(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);
|
||||
|
||||
LOG_DEBUG("Drawing deep sleep screen\n");
|
||||
drawIconScreen("Sleeping...", display, state, x, y);
|
||||
}
|
||||
|
||||
/// Used on eink displays when screen updates are paused
|
||||
static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||
{
|
||||
LOG_DEBUG("Drawing screensaver overlay\n");
|
||||
|
||||
EINK_ADD_FRAMEFLAG(display, COSMETIC); // Take the opportunity for a full-refresh
|
||||
|
||||
// Config
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
const char *pauseText = "Screen Paused";
|
||||
const char *idText = owner.short_name;
|
||||
constexpr uint16_t padding = 5;
|
||||
constexpr uint8_t dividerGap = 1;
|
||||
constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in.
|
||||
|
||||
// Dimensions
|
||||
const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText));
|
||||
const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText));
|
||||
const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding;
|
||||
const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding;
|
||||
|
||||
// Position
|
||||
const int16_t boxLeft = (display->width() / 2) - (boxWidth / 2) + random(-imprecision, imprecision + 1);
|
||||
// const int16_t boxRight = boxLeft + boxWidth - 1;
|
||||
const int16_t boxTop = (display->height() / 2) - (boxHeight / 2 + random(-imprecision, imprecision + 1));
|
||||
const int16_t boxBottom = boxTop + boxHeight - 1;
|
||||
const int16_t idTextLeft = boxLeft + padding;
|
||||
const int16_t idTextTop = boxTop + padding;
|
||||
const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding;
|
||||
const int16_t pauseTextTop = boxTop + padding;
|
||||
const int16_t dividerX = boxLeft + padding + idTextWidth + padding;
|
||||
const int16_t dividerTop = boxTop + 1 + dividerGap;
|
||||
const int16_t dividerBottom = boxBottom - 1 - dividerGap;
|
||||
|
||||
// Draw: box
|
||||
display->setColor(EINK_WHITE);
|
||||
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Clear a slightly oversized area for the box
|
||||
display->setColor(EINK_BLACK);
|
||||
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
|
||||
|
||||
// Draw: Text
|
||||
display->drawString(idTextLeft, idTextTop, idText);
|
||||
display->drawString(pauseTextLeft, pauseTextTop, pauseText);
|
||||
display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold
|
||||
|
||||
// Draw: divider
|
||||
display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
@@ -354,7 +426,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
static char tempBuf[237];
|
||||
|
||||
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
|
||||
meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp));
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
|
||||
// LOG_DEBUG("drawing text message from 0x%x: %s\n", mp.from,
|
||||
// mp.decoded.variant.data.decoded.bytes);
|
||||
|
||||
@@ -392,7 +464,7 @@ static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
static char tempBuf[237];
|
||||
|
||||
meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
|
||||
meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp));
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
@@ -473,7 +545,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat
|
||||
{
|
||||
char usersString[20];
|
||||
snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x, y + 3, 8, 8, imgUser);
|
||||
#else
|
||||
@@ -483,7 +555,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat
|
||||
if (config.display.heading_bold)
|
||||
display->drawString(x + 11, y - 2, usersString);
|
||||
}
|
||||
|
||||
#if HAS_GPS
|
||||
// Draw GPS status summary
|
||||
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||
{
|
||||
@@ -625,7 +697,7 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -780,16 +852,16 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
if (state->currentFrame != prevFrame) {
|
||||
prevFrame = state->currentFrame;
|
||||
|
||||
nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes();
|
||||
meshtastic_NodeInfoLite *n = nodeDB.getMeshNodeByIndex(nodeIndex);
|
||||
if (n->num == nodeDB.getNodeNum()) {
|
||||
nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes();
|
||||
meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(nodeIndex);
|
||||
if (n->num == nodeDB->getNodeNum()) {
|
||||
// Don't show our node, just skip to next
|
||||
nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes();
|
||||
n = nodeDB.getMeshNodeByIndex(nodeIndex);
|
||||
nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes();
|
||||
n = nodeDB->getMeshNodeByIndex(nodeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite *node = nodeDB.getMeshNodeByIndex(nodeIndex);
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(nodeIndex);
|
||||
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
@@ -827,7 +899,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
} else {
|
||||
strncpy(distStr, "? km", sizeof(distStr));
|
||||
}
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB.getMeshNode(nodeDB.getNodeNum());
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
||||
int16_t compassX = 0, compassY = 0;
|
||||
|
||||
@@ -893,13 +965,14 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry)
|
||||
: concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32)
|
||||
{
|
||||
graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
||||
#if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64)
|
||||
dispdev = new SH1106Wire(address.address, -1, -1, geometry,
|
||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||
#elif defined(USE_SSD1306)
|
||||
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
|
||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014)
|
||||
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS)
|
||||
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
|
||||
@@ -931,6 +1004,11 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
||||
cmdQueue.setReader(this);
|
||||
}
|
||||
|
||||
Screen::~Screen()
|
||||
{
|
||||
delete[] graphics::normalFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
|
||||
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
|
||||
@@ -938,18 +1016,17 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
||||
void Screen::doDeepSleep()
|
||||
{
|
||||
#ifdef USE_EINK
|
||||
static FrameCallback sleepFrames[] = {drawSleepScreen};
|
||||
static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
|
||||
ui->setFrames(sleepFrames, sleepFrameCount);
|
||||
ui->update();
|
||||
setOn(false, drawDeepSleepScreen);
|
||||
#ifdef PIN_EINK_EN
|
||||
digitalWrite(PIN_EINK_EN, LOW); // power off backlight
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
// Without E-Ink display:
|
||||
setOn(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Screen::handleSetOn(bool on)
|
||||
void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
{
|
||||
if (!useDisplay)
|
||||
return;
|
||||
@@ -963,11 +1040,22 @@ void Screen::handleSetOn(bool on)
|
||||
#if !ARCH_PORTDUINO
|
||||
dispdev->displayOn();
|
||||
#endif
|
||||
|
||||
#if defined(ST7789_CS) && \
|
||||
!defined(M5STACK) // set display brightness when turning on screens. Just moved function from TFTDisplay to here.
|
||||
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
|
||||
#endif
|
||||
|
||||
dispdev->displayOn();
|
||||
|
||||
enabled = true;
|
||||
setInterval(0); // Draw ASAP
|
||||
runASAP = true;
|
||||
} else {
|
||||
#ifdef USE_EINK
|
||||
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
|
||||
setScreensaverFrames(einkScreensaver);
|
||||
#endif
|
||||
LOG_INFO("Turning off screen\n");
|
||||
dispdev->displayOff();
|
||||
#ifdef T_WATCH_S3
|
||||
@@ -1018,6 +1106,7 @@ void Screen::setup()
|
||||
logo_timeout *= 2;
|
||||
|
||||
// Add frames.
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
|
||||
static FrameCallback bootFrames[] = {drawBootScreen};
|
||||
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
||||
ui->setFrames(bootFrames, bootFrameCount);
|
||||
@@ -1036,7 +1125,7 @@ void Screen::setup()
|
||||
// Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically
|
||||
// flip it. If you have a headache now, you're welcome.
|
||||
if (!config.display.flip_screen) {
|
||||
#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014)
|
||||
#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS)
|
||||
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
|
||||
#else
|
||||
dispdev->flipScreenVertically();
|
||||
@@ -1088,10 +1177,33 @@ void Screen::setup()
|
||||
MeshModule::observeUIEvents(&uiFrameEventObserver);
|
||||
}
|
||||
|
||||
void Screen::forceDisplay()
|
||||
void Screen::forceDisplay(bool forceUiUpdate)
|
||||
{
|
||||
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
|
||||
#ifdef USE_EINK
|
||||
// If requested, make sure queued commands are run, and UI has rendered a new frame
|
||||
if (forceUiUpdate) {
|
||||
// No delay between UI frame rendering
|
||||
setFastFramerate();
|
||||
|
||||
// Make sure all CMDs have run first
|
||||
while (!cmdQueue.isEmpty())
|
||||
runOnce();
|
||||
|
||||
// Ensure at least one frame has drawn
|
||||
uint64_t startUpdate;
|
||||
do {
|
||||
startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
|
||||
delay(10);
|
||||
ui->update();
|
||||
} while (ui->getUiState()->lastUpdate < startUpdate);
|
||||
|
||||
// Return to normal frame rate
|
||||
targetFramerate = IDLE_FRAMERATE;
|
||||
ui->setTargetFPS(targetFramerate);
|
||||
}
|
||||
|
||||
// Tell EInk class to update the display
|
||||
static_cast<EInkDisplay *>(dispdev)->forceDisplay();
|
||||
#endif
|
||||
}
|
||||
@@ -1273,6 +1385,63 @@ void Screen::setWelcomeFrames()
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_EINK
|
||||
/// Determine which screensaver frame to use, then set the FrameCallback
|
||||
void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
|
||||
{
|
||||
// Remember current frame, restore position at power-on
|
||||
uint8_t frameNumber = ui->getUiState()->currentFrame;
|
||||
|
||||
// Retain specified frame / overlay callback beyond scope of this method
|
||||
static FrameCallback screensaverFrame;
|
||||
static OverlayCallback screensaverOverlay;
|
||||
|
||||
#if defined(HAS_EINK_ASYNCFULL) && defined(USE_EINK_DYNAMICDISPLAY)
|
||||
// Join (await) a currently running async refresh, then run the post-update code.
|
||||
// Avoid skipping of screensaver frame. Would otherwise be handled by NotifiedWorkerThread.
|
||||
EINK_JOIN_ASYNCREFRESH(dispdev);
|
||||
#endif
|
||||
|
||||
// If: one-off screensaver frame passed as argument. Handles doDeepSleep()
|
||||
if (einkScreensaver != NULL) {
|
||||
screensaverFrame = einkScreensaver;
|
||||
ui->setFrames(&screensaverFrame, 1);
|
||||
}
|
||||
|
||||
// Else, display the usual "overlay" screensaver
|
||||
else {
|
||||
screensaverOverlay = drawScreensaverOverlay;
|
||||
ui->setOverlays(&screensaverOverlay, 1);
|
||||
}
|
||||
|
||||
// Request new frame, ASAP
|
||||
setFastFramerate();
|
||||
uint64_t startUpdate;
|
||||
do {
|
||||
startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
|
||||
delay(1);
|
||||
ui->update();
|
||||
} while (ui->getUiState()->lastUpdate < startUpdate);
|
||||
|
||||
// Old EInkDisplay class
|
||||
#if !defined(USE_EINK_DYNAMICDISPLAY)
|
||||
static_cast<EInkDisplay *>(dispdev)->forceDisplay(0); // Screen::forceDisplay(), but override rate-limit
|
||||
#endif
|
||||
|
||||
// Prepare now for next frame, shown when display wakes
|
||||
ui->setOverlays(NULL, 0); // Clear overlay
|
||||
setFrames(); // Return to normal display updates
|
||||
ui->switchToFrame(frameNumber); // Attempt to return to same frame after power-on
|
||||
|
||||
// Pick a refresh method, for when display wakes
|
||||
#ifdef EINK_HASQUIRK_GHOSTING
|
||||
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // Really ugly to see ghosting from "screen paused"
|
||||
#else
|
||||
EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// restore our regular frame list
|
||||
void Screen::setFrames()
|
||||
{
|
||||
@@ -1287,7 +1456,7 @@ void Screen::setFrames()
|
||||
#endif
|
||||
|
||||
// We don't show the node info our our node (if we have it yet - we should)
|
||||
size_t numMeshNodes = nodeDB.getNumMeshNodes();
|
||||
size_t numMeshNodes = nodeDB->getNumMeshNodes();
|
||||
if (numMeshNodes > 0)
|
||||
numMeshNodes--;
|
||||
|
||||
@@ -1345,6 +1514,11 @@ void Screen::setFrames()
|
||||
ui->setFrames(normalFrames, numframes);
|
||||
ui->enableAllIndicators();
|
||||
|
||||
// Add function overlay here. This can show when notifications muted, modifier key is active etc
|
||||
static OverlayCallback functionOverlay[] = {drawFunctionOverlay};
|
||||
static const int functionOverlayCount = sizeof(functionOverlay) / sizeof(functionOverlay[0]);
|
||||
ui->setOverlays(functionOverlay, functionOverlayCount);
|
||||
|
||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
||||
// just changed)
|
||||
|
||||
@@ -1373,7 +1547,11 @@ 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
|
||||
#ifdef USE_EINK
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
|
||||
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
|
||||
handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
|
||||
#endif
|
||||
|
||||
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||
drawFrameText(display, state, x, y, "Shutting down...");
|
||||
@@ -1387,7 +1565,11 @@ 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
|
||||
#ifdef USE_EINK
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
|
||||
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
|
||||
handleSetOn(true); // Power-on to show rebooting screen (PowerFSM should handle?)
|
||||
#endif
|
||||
|
||||
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||
drawFrameText(display, state, x, y, "Rebooting...");
|
||||
@@ -1420,9 +1602,55 @@ void Screen::blink()
|
||||
delay(50);
|
||||
count = count - 1;
|
||||
}
|
||||
// The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay.
|
||||
dispdev->setBrightness(brightness);
|
||||
}
|
||||
|
||||
void Screen::increaseBrightness()
|
||||
{
|
||||
brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62);
|
||||
|
||||
#if defined(ST7789_CS)
|
||||
// run the setDisplayBrightness function. This works on t-decks
|
||||
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
|
||||
#endif
|
||||
|
||||
/* TO DO: add little popup in center of screen saying what brightness level it is set to*/
|
||||
}
|
||||
|
||||
void Screen::decreaseBrightness()
|
||||
{
|
||||
brightness = (brightness < 70) ? brightness : (brightness - 62);
|
||||
|
||||
#if defined(ST7789_CS)
|
||||
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
|
||||
#endif
|
||||
|
||||
/* TO DO: add little popup in center of screen saying what brightness level it is set to*/
|
||||
}
|
||||
|
||||
void Screen::setFunctionSymbal(std::string sym)
|
||||
{
|
||||
if (std::find(functionSymbals.begin(), functionSymbals.end(), sym) == functionSymbals.end()) {
|
||||
functionSymbals.push_back(sym);
|
||||
functionSymbalString = "";
|
||||
for (auto symbol : functionSymbals) {
|
||||
functionSymbalString = symbol + " " + functionSymbalString;
|
||||
}
|
||||
setFastFramerate();
|
||||
}
|
||||
}
|
||||
|
||||
void Screen::removeFunctionSymbal(std::string sym)
|
||||
{
|
||||
functionSymbals.erase(std::remove(functionSymbals.begin(), functionSymbals.end(), sym), functionSymbals.end());
|
||||
functionSymbalString = "";
|
||||
for (auto symbol : functionSymbals) {
|
||||
functionSymbalString = symbol + " " + functionSymbalString;
|
||||
}
|
||||
setFastFramerate();
|
||||
}
|
||||
|
||||
std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds)
|
||||
{
|
||||
std::string uptime;
|
||||
@@ -1513,8 +1741,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
char channelStr[20];
|
||||
{
|
||||
concurrency::LockGuard guard(&lock);
|
||||
auto chName = channels.getPrimaryName();
|
||||
snprintf(channelStr, sizeof(channelStr), "%s", chName);
|
||||
snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex()));
|
||||
}
|
||||
|
||||
// Display power status
|
||||
@@ -1537,6 +1764,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
} else {
|
||||
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
|
||||
}
|
||||
#if HAS_GPS
|
||||
// Display GPS status
|
||||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
drawGPSpowerstat(display, x, y + 2, gpsStatus);
|
||||
@@ -1547,7 +1775,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
display->setColor(WHITE);
|
||||
// Draw the channel name
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
|
||||
@@ -1556,7 +1784,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
#ifdef ARCH_ESP32
|
||||
if (millis() - storeForwardModule->lastHeartbeat >
|
||||
(storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
|
||||
imgQuestionL1);
|
||||
@@ -1567,7 +1795,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
imgQuestion);
|
||||
#endif
|
||||
} else {
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8,
|
||||
imgSFL1);
|
||||
@@ -1581,7 +1809,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
#endif
|
||||
} else {
|
||||
// TODO: Raspberry Pi supports more than just the one screen size
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \
|
||||
ARCH_PORTDUINO) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
|
||||
imgInfoL1);
|
||||
@@ -1742,7 +1971,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
// Show uptime as days, hours, minutes OR seconds
|
||||
std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds);
|
||||
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||||
if (rtc_sec > 0) {
|
||||
long hms = rtc_sec % SEC_PER_DAY;
|
||||
// hms += tz.tz_dsttime * SEC_PER_HOUR;
|
||||
@@ -1766,6 +1995,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
char chUtil[13];
|
||||
snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
|
||||
#if HAS_GPS
|
||||
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
// Line 3
|
||||
if (config.display.gps_format !=
|
||||
@@ -1777,6 +2007,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
} else {
|
||||
drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||||
}
|
||||
#endif
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
if (heartbeat)
|
||||
@@ -1793,7 +2024,7 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
|
||||
setFrames(); // Regen the list of screens
|
||||
}
|
||||
nodeDB.updateGUI = false;
|
||||
nodeDB->updateGUI = false;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class Screen
|
||||
void setOn(bool) {}
|
||||
void print(const char *) {}
|
||||
void doDeepSleep() {}
|
||||
void forceDisplay() {}
|
||||
void forceDisplay(bool forceUiUpdate = false) {}
|
||||
void startBluetoothPinScreen(uint32_t pin) {}
|
||||
void stopBluetoothPinScreen() {}
|
||||
void startRebootScreen() {}
|
||||
@@ -73,6 +73,10 @@ class Screen
|
||||
#define MILES_TO_FEET 5280
|
||||
#endif
|
||||
|
||||
// Intuitive colors. E-Ink display is inverted from OLED(?)
|
||||
#define EINK_BLACK OLEDDISPLAY_COLOR::WHITE
|
||||
#define EINK_WHITE OLEDDISPLAY_COLOR::BLACK
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
@@ -125,6 +129,8 @@ class Screen : public concurrency::OSThread
|
||||
public:
|
||||
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
|
||||
|
||||
~Screen();
|
||||
|
||||
Screen(const Screen &) = delete;
|
||||
Screen &operator=(const Screen &) = delete;
|
||||
|
||||
@@ -137,14 +143,14 @@ class Screen : public concurrency::OSThread
|
||||
// Not thread safe - must be called before any other methods are called.
|
||||
void setup();
|
||||
|
||||
/// Turns the screen on/off.
|
||||
void setOn(bool on)
|
||||
/// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink
|
||||
void setOn(bool on, FrameCallback einkScreensaver = NULL)
|
||||
{
|
||||
if (!on)
|
||||
handleSetOn(
|
||||
false); // We handle off commands immediately, because they might be called because the CPU is shutting down
|
||||
// We handle off commands immediately, because they might be called because the CPU is shutting down
|
||||
handleSetOn(false, einkScreensaver);
|
||||
else
|
||||
enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
|
||||
enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,9 +166,6 @@ class Screen : public concurrency::OSThread
|
||||
void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); }
|
||||
void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); }
|
||||
|
||||
// Implementation to Adjust Brightness
|
||||
uint8_t brightness = BRIGHTNESS_DEFAULT;
|
||||
|
||||
/// Starts showing the Bluetooth PIN screen.
|
||||
//
|
||||
// Switches over to a static frame showing the Bluetooth pairing screen
|
||||
@@ -196,6 +199,13 @@ class Screen : public concurrency::OSThread
|
||||
enqueueCmd(cmd);
|
||||
}
|
||||
|
||||
// functions for display brightness
|
||||
void increaseBrightness();
|
||||
void decreaseBrightness();
|
||||
|
||||
void setFunctionSymbal(std::string sym);
|
||||
void removeFunctionSymbal(std::string sym);
|
||||
|
||||
/// Stops showing the bluetooth PIN screen.
|
||||
void stopBluetoothPinScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); }
|
||||
|
||||
@@ -312,13 +322,18 @@ class Screen : public concurrency::OSThread
|
||||
int handleInputEvent(const InputEvent *arg);
|
||||
|
||||
/// Used to force (super slow) eink displays to draw critical frames
|
||||
void forceDisplay();
|
||||
void forceDisplay(bool forceUiUpdate = false);
|
||||
|
||||
/// Draws our SSL cert screen during boot (called from WebServer)
|
||||
void setSSLFrames();
|
||||
|
||||
void setWelcomeFrames();
|
||||
|
||||
#ifdef USE_EINK
|
||||
/// Draw an image to remain on E-Ink display after screen off
|
||||
void setScreensaverFrames(FrameCallback einkScreensaver = NULL);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/// Updates the UI.
|
||||
//
|
||||
@@ -349,7 +364,7 @@ class Screen : public concurrency::OSThread
|
||||
}
|
||||
|
||||
// Implementations of various commands, called from doTask().
|
||||
void handleSetOn(bool on);
|
||||
void handleSetOn(bool on, FrameCallback einkScreensaver = NULL);
|
||||
void handleOnPress();
|
||||
void handleShowNextFrame();
|
||||
void handleShowPrevFrame();
|
||||
@@ -384,6 +399,9 @@ class Screen : public concurrency::OSThread
|
||||
// Bluetooth PIN screen)
|
||||
bool showingNormalScreen = false;
|
||||
|
||||
// Implementation to Adjust Brightness
|
||||
uint8_t brightness = BRIGHTNESS_DEFAULT; // H = 254, MH = 192, ML = 130 L = 103
|
||||
|
||||
/// Holds state for debug information
|
||||
DebugInfo debugInfo;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "graphics/fonts/OLEDDisplayFontsUA.h"
|
||||
#endif
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16 // Height: 19
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
#define TFT_BACKLIGHT_ON HIGH
|
||||
#endif
|
||||
|
||||
#ifdef GPIO_EXTENDER
|
||||
#include <SparkFunSX1509.h>
|
||||
#include <Wire.h>
|
||||
extern SX1509 gpioExtender;
|
||||
#endif
|
||||
|
||||
#ifndef TFT_MESH
|
||||
#define TFT_MESH COLOR565(0x67, 0xEA, 0x94)
|
||||
#endif
|
||||
@@ -333,7 +339,7 @@ static LGFX *tft = nullptr;
|
||||
#include <TFT_eSPI.h> // Graphics and font library for ILI9341 driver chip
|
||||
|
||||
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
|
||||
#elif ARCH_PORTDUINO
|
||||
#elif ARCH_PORTDUINO && HAS_SCREEN != 0
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||
|
||||
class LGFX : public lgfx::LGFX_Device
|
||||
@@ -356,6 +362,7 @@ class LGFX : public lgfx::LGFX_Device
|
||||
_panel_instance = new lgfx::Panel_ILI9341;
|
||||
auto buscfg = _bus_instance.config();
|
||||
buscfg.spi_mode = 0;
|
||||
buscfg.spi_host = settingsMap[displayspidev];
|
||||
|
||||
buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable)
|
||||
|
||||
@@ -381,6 +388,8 @@ class LGFX : public lgfx::LGFX_Device
|
||||
_touch_instance = new lgfx::Touch_XPT2046;
|
||||
} else if (settingsMap[touchscreenModule] == stmpe610) {
|
||||
_touch_instance = new lgfx::Touch_STMPE610;
|
||||
} else if (settingsMap[touchscreenModule] == ft5x06) {
|
||||
_touch_instance = new lgfx::Touch_FT5x06;
|
||||
}
|
||||
auto touch_cfg = _touch_instance->config();
|
||||
|
||||
@@ -392,6 +401,11 @@ class LGFX : public lgfx::LGFX_Device
|
||||
touch_cfg.pin_int = settingsMap[touchscreenIRQ];
|
||||
touch_cfg.bus_shared = true;
|
||||
touch_cfg.offset_rotation = 1;
|
||||
if (settingsMap[touchscreenI2CAddr] != -1) {
|
||||
touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr];
|
||||
} else {
|
||||
touch_cfg.spi_host = settingsMap[touchscreenspidev];
|
||||
}
|
||||
|
||||
_touch_instance->config(touch_cfg);
|
||||
_panel_instance->setTouch(_touch_instance);
|
||||
@@ -402,13 +416,103 @@ class LGFX : public lgfx::LGFX_Device
|
||||
};
|
||||
|
||||
static LGFX *tft = nullptr;
|
||||
|
||||
#elif defined(HX8357_CS)
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for HX8357 driver chip
|
||||
|
||||
class LGFX : public lgfx::LGFX_Device
|
||||
{
|
||||
lgfx::Panel_HX8357D _panel_instance;
|
||||
lgfx::Bus_SPI _bus_instance;
|
||||
#if defined(USE_XPT2046)
|
||||
lgfx::Touch_XPT2046 _touch_instance;
|
||||
#endif
|
||||
|
||||
#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || ARCH_PORTDUINO
|
||||
public:
|
||||
LGFX(void)
|
||||
{
|
||||
// Panel_HX8357D
|
||||
{
|
||||
// configure SPI
|
||||
auto cfg = _bus_instance.config();
|
||||
|
||||
cfg.spi_host = HX8357_SPI_HOST;
|
||||
cfg.spi_mode = 0;
|
||||
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||||
// 80MHz by an integer)
|
||||
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||||
cfg.spi_3wire = false; // Set to true if reception is done on the MOSI pin
|
||||
cfg.use_lock = true; // Set to true to use transaction locking
|
||||
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||||
// SPI_DMA_CH_AUTO=auto setting)
|
||||
cfg.pin_sclk = HX8357_SCK; // Set SPI SCLK pin number
|
||||
cfg.pin_mosi = HX8357_MOSI; // Set SPI MOSI pin number
|
||||
cfg.pin_miso = HX8357_MISO; // Set SPI MISO pin number (-1 = disable)
|
||||
cfg.pin_dc = HX8357_RS; // Set SPI DC pin number (-1 = disable)
|
||||
|
||||
_bus_instance.config(cfg); // applies the set value to the bus.
|
||||
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||||
}
|
||||
{
|
||||
// Set the display panel control.
|
||||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||||
|
||||
cfg.pin_cs = HX8357_CS; // Pin number where CS is connected (-1 = disable)
|
||||
cfg.pin_rst = HX8357_RESET; // Pin number where RST is connected (-1 = disable)
|
||||
cfg.pin_busy = HX8357_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||||
|
||||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||||
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is upside down)
|
||||
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
|
||||
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||||
cfg.readable = true; // Set to true if data can be read
|
||||
cfg.invert = TFT_INVERT; // Set to true if the light/darkness of the panel is reversed
|
||||
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||||
cfg.dlen_16bit = false;
|
||||
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||||
|
||||
_panel_instance.config(cfg);
|
||||
}
|
||||
#if defined(USE_XPT2046)
|
||||
{
|
||||
// Configure settings for touch control.
|
||||
auto touch_cfg = _touch_instance.config();
|
||||
|
||||
touch_cfg.pin_cs = TOUCH_CS;
|
||||
touch_cfg.x_min = 0;
|
||||
touch_cfg.x_max = TFT_HEIGHT - 1;
|
||||
touch_cfg.y_min = 0;
|
||||
touch_cfg.y_max = TFT_WIDTH - 1;
|
||||
touch_cfg.pin_int = -1;
|
||||
touch_cfg.bus_shared = true;
|
||||
touch_cfg.offset_rotation = 1;
|
||||
|
||||
_touch_instance.config(touch_cfg);
|
||||
_panel_instance.setTouch(&_touch_instance);
|
||||
}
|
||||
#endif
|
||||
setPanel(&_panel_instance);
|
||||
}
|
||||
};
|
||||
|
||||
static LGFX *tft = nullptr;
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || \
|
||||
(ARCH_PORTDUINO && HAS_SCREEN != 0)
|
||||
#include "SPILock.h"
|
||||
#include "TFTDisplay.h"
|
||||
#include <SPI.h>
|
||||
|
||||
#ifdef UNPHONE
|
||||
#include "unPhone.h"
|
||||
extern unPhone unphone;
|
||||
#endif
|
||||
|
||||
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
||||
{
|
||||
LOG_DEBUG("TFTDisplay!\n");
|
||||
@@ -474,8 +578,10 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
#elif defined(ST7735_BL_V05)
|
||||
pinMode(ST7735_BL_V05, OUTPUT);
|
||||
digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON);
|
||||
#endif
|
||||
#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
||||
tft->wakeup();
|
||||
tft->powerSaveOff();
|
||||
#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
|
||||
digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
|
||||
#endif
|
||||
|
||||
@@ -486,9 +592,11 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
#ifdef VTFT_CTRL
|
||||
digitalWrite(VTFT_CTRL, LOW);
|
||||
#endif
|
||||
|
||||
#ifdef UNPHONE
|
||||
unphone.backlight(true); // using unPhone library
|
||||
#endif
|
||||
#ifdef RAK14014
|
||||
#elif !defined(M5STACK)
|
||||
#elif !defined(M5STACK) && !defined(ST7789_CS) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
|
||||
tft->setBrightness(172);
|
||||
#endif
|
||||
break;
|
||||
@@ -503,16 +611,22 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
#elif defined(ST7735_BL_V05)
|
||||
pinMode(ST7735_BL_V05, OUTPUT);
|
||||
digitalWrite(ST7735_BL_V05, !TFT_BACKLIGHT_ON);
|
||||
#endif
|
||||
#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
||||
tft->sleep();
|
||||
tft->powerSaveOn();
|
||||
#elif defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
|
||||
digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON);
|
||||
#endif
|
||||
|
||||
#ifdef VTFT_CTRL_V03
|
||||
digitalWrite(VTFT_CTRL_V03, HIGH);
|
||||
#endif
|
||||
#ifdef VTFT_CTRL
|
||||
digitalWrite(VTFT_CTRL, HIGH);
|
||||
#endif
|
||||
#ifdef UNPHONE
|
||||
unphone.backlight(false); // using unPhone library
|
||||
#endif
|
||||
#ifdef RAK14014
|
||||
#elif !defined(M5STACK)
|
||||
tft->setBrightness(0);
|
||||
@@ -526,6 +640,12 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
// Drop all other commands to device (we just update the buffer)
|
||||
}
|
||||
|
||||
void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
|
||||
{
|
||||
tft->setBrightness(_brightness);
|
||||
LOG_DEBUG("Brightness is set to value: %i \n", _brightness);
|
||||
}
|
||||
|
||||
void TFTDisplay::flipScreenVertically()
|
||||
{
|
||||
#if defined(T_WATCH_S3)
|
||||
@@ -584,6 +704,10 @@ bool TFTDisplay::connect()
|
||||
pinMode(ST7735_BL_V05, OUTPUT);
|
||||
digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON);
|
||||
#endif
|
||||
#ifdef UNPHONE
|
||||
unphone.backlight(true); // using unPhone library
|
||||
LOG_INFO("Power to TFT Backlight\n");
|
||||
#endif
|
||||
|
||||
tft->init();
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ class TFTDisplay : public OLEDDisplay
|
||||
static bool hasTouch(void);
|
||||
static bool getTouch(int16_t *x, int16_t *y);
|
||||
|
||||
// Functions for changing display brightness
|
||||
void setDisplayBrightness(uint8_t);
|
||||
|
||||
/**
|
||||
* shim to make the abstraction happy
|
||||
*
|
||||
|
||||
@@ -14,7 +14,8 @@ const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3
|
||||
const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF};
|
||||
const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF};
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \
|
||||
ARCH_PORTDUINO) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
|
||||
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
|
||||
@@ -30,4 +31,4 @@ const uint8_t imgQuestion[] PROGMEM = {0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1,
|
||||
const uint8_t imgSF[] PROGMEM = {0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5};
|
||||
#endif
|
||||
|
||||
#include "img/icon.xbm"
|
||||
#include "img/icon.xbm"
|
||||
|
||||
@@ -24,7 +24,8 @@ LinuxInput::LinuxInput(const char *name) : concurrency::OSThread(name)
|
||||
|
||||
void LinuxInput::deInit()
|
||||
{
|
||||
close(fd);
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int32_t LinuxInput::runOnce()
|
||||
|
||||
@@ -38,7 +38,7 @@ class LinuxInput : public Observable<const InputEvent *>, public concurrency::OS
|
||||
int queue_progress = 0;
|
||||
|
||||
struct epoll_event events[MAX_EVENTS];
|
||||
int fd;
|
||||
int fd = -1;
|
||||
int ret;
|
||||
uint8_t report[8];
|
||||
int epollfd;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "InputBroker.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
#include "time.h"
|
||||
|
||||
typedef struct _TouchEvent {
|
||||
const char *source;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "configuration.h"
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#if ARCH_PORTDUINO
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "UpDownInterruptBase.h"
|
||||
#include "configuration.h"
|
||||
|
||||
UpDownInterruptBase::UpDownInterruptBase(const char *name)
|
||||
UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThread(name)
|
||||
{
|
||||
this->_originName = name;
|
||||
}
|
||||
@@ -24,31 +24,48 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
|
||||
attachInterrupt(this->_pinUp, onIntUp, RISING);
|
||||
|
||||
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)\n", this->_pinUp, this->_pinDown, pinPress);
|
||||
|
||||
this->setInterval(100);
|
||||
}
|
||||
|
||||
int32_t UpDownInterruptBase::runOnce()
|
||||
{
|
||||
InputEvent e;
|
||||
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
|
||||
|
||||
if (this->action == UPDOWN_ACTION_PRESSED) {
|
||||
LOG_DEBUG("GPIO event Press\n");
|
||||
e.inputEvent = this->_eventPressed;
|
||||
} else if (this->action == UPDOWN_ACTION_UP) {
|
||||
LOG_DEBUG("GPIO event Up\n");
|
||||
e.inputEvent = this->_eventUp;
|
||||
} else if (this->action == UPDOWN_ACTION_DOWN) {
|
||||
LOG_DEBUG("GPIO event Down\n");
|
||||
e.inputEvent = this->_eventDown;
|
||||
}
|
||||
|
||||
if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) {
|
||||
e.source = this->_originName;
|
||||
e.kbchar = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
this->action = UPDOWN_ACTION_NONE;
|
||||
|
||||
return 100;
|
||||
}
|
||||
|
||||
void UpDownInterruptBase::intPressHandler()
|
||||
{
|
||||
InputEvent e;
|
||||
e.source = this->_originName;
|
||||
LOG_DEBUG("GPIO event Press\n");
|
||||
e.inputEvent = this->_eventPressed;
|
||||
this->notifyObservers(&e);
|
||||
this->action = UPDOWN_ACTION_PRESSED;
|
||||
}
|
||||
|
||||
void UpDownInterruptBase::intDownHandler()
|
||||
{
|
||||
InputEvent e;
|
||||
e.source = this->_originName;
|
||||
LOG_DEBUG("GPIO event Down\n");
|
||||
e.inputEvent = this->_eventDown;
|
||||
this->notifyObservers(&e);
|
||||
this->action = UPDOWN_ACTION_DOWN;
|
||||
}
|
||||
|
||||
void UpDownInterruptBase::intUpHandler()
|
||||
{
|
||||
InputEvent e;
|
||||
e.source = this->_originName;
|
||||
LOG_DEBUG("GPIO event Up\n");
|
||||
e.inputEvent = this->_eventUp;
|
||||
this->notifyObservers(&e);
|
||||
this->action = UPDOWN_ACTION_UP;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "InputBroker.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
|
||||
class UpDownInterruptBase : public Observable<const InputEvent *>
|
||||
class UpDownInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit UpDownInterruptBase(const char *name);
|
||||
@@ -13,6 +13,13 @@ class UpDownInterruptBase : public Observable<const InputEvent *>
|
||||
void intDownHandler();
|
||||
void intUpHandler();
|
||||
|
||||
int32_t runOnce() override;
|
||||
|
||||
protected:
|
||||
enum UpDownInterruptBaseActionType { UPDOWN_ACTION_NONE, UPDOWN_ACTION_PRESSED, UPDOWN_ACTION_UP, UPDOWN_ACTION_DOWN };
|
||||
|
||||
volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE;
|
||||
|
||||
private:
|
||||
uint8_t _pinDown = 0;
|
||||
uint8_t _pinUp = 0;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "kbI2cBase.h"
|
||||
|
||||
#include "configuration.h"
|
||||
#include "detect/ScanI2C.h"
|
||||
|
||||
@@ -138,6 +137,9 @@ int32_t KbI2cBase::runOnce()
|
||||
break;
|
||||
case 0x13: // Code scanner says the SYM key is 0x13
|
||||
is_sym = !is_sym;
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar =
|
||||
is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active
|
||||
break;
|
||||
case 0x0a: // apparently Enter on Q10 is a line feed instead of carriage return
|
||||
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
|
||||
@@ -193,6 +195,75 @@ int32_t KbI2cBase::runOnce()
|
||||
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
|
||||
e.source = this->_originName;
|
||||
switch (c) {
|
||||
case 0x71: // This is the button q. If modifier and q pressed, it cancels the input
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
|
||||
} else {
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x74: // letter t. if modifier and t pressed call 'tab'
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = 0x09; // TAB Scancode
|
||||
} else {
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x6d: // letter m. Modifier makes it mute notifications
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = 0xac; // mute notifications
|
||||
} else {
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x6f: // letter o(+). Modifier makes screen increase in brightness
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = 0x11; // Increase Brightness code
|
||||
} else {
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x69: // letter i(-). Modifier makes screen decrease in brightness
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = 0x12; // Decrease Brightness code
|
||||
} else {
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x20: // Space. Send network ping like double press does
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = 0xaf; // (fn + space)
|
||||
} else {
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x67: // letter g. toggle gps
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = 0x9e;
|
||||
} else {
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x1b: // ESC
|
||||
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
|
||||
break;
|
||||
@@ -216,6 +287,22 @@ int32_t KbI2cBase::runOnce()
|
||||
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
|
||||
e.kbchar = 0xb7;
|
||||
break;
|
||||
case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker))
|
||||
// toggle moddifiers button.
|
||||
is_sym = !is_sym;
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active
|
||||
break;
|
||||
case 0x90: // fn+r
|
||||
case 0x91: // fn+t
|
||||
case 0x9b: // fn+s
|
||||
case 0xac: // fn+m
|
||||
case 0x9e: // fn+g
|
||||
case 0xaf: // fn+space
|
||||
// just pass those unmodified
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = c;
|
||||
break;
|
||||
case 0x0d: // Enter
|
||||
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
|
||||
break;
|
||||
@@ -229,6 +316,7 @@ int32_t KbI2cBase::runOnce()
|
||||
}
|
||||
e.inputEvent = ANYKEY;
|
||||
e.kbchar = c;
|
||||
is_sym = false;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
135
src/main.cpp
135
src/main.cpp
@@ -1,4 +1,7 @@
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "GPS.h"
|
||||
#endif
|
||||
#include "MeshRadio.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
@@ -6,7 +9,7 @@
|
||||
#include "ReliableRouter.h"
|
||||
#include "airtime.h"
|
||||
#include "buzz.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "power.h"
|
||||
// #include "debug.h"
|
||||
@@ -33,10 +36,14 @@
|
||||
// #include <driver/rtc_io.h>
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#if !MESHTASTIC_EXCLUDE_WEBSERVER
|
||||
#include "mesh/http/WebServer.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
#include "nimble/NimbleBluetooth.h"
|
||||
NimbleBluetooth *nimbleBluetooth;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_NRF52
|
||||
#include "NRF52Bluetooth.h"
|
||||
@@ -52,16 +59,22 @@ NRF52Bluetooth *nrf52Bluetooth;
|
||||
#include "mesh/api/ethServerAPI.h"
|
||||
#include "mesh/eth/ethClient.h"
|
||||
#endif
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
|
||||
#include "LLCC68Interface.h"
|
||||
#include "RF95Interface.h"
|
||||
#include "SX1262Interface.h"
|
||||
#include "SX1268Interface.h"
|
||||
#include "SX1280Interface.h"
|
||||
#include "detect/LoRaRadioType.h"
|
||||
|
||||
#ifdef ARCH_STM32WL
|
||||
#include "STM32WLE5JCInterface.h"
|
||||
#endif
|
||||
|
||||
#if !HAS_RADIO && defined(ARCH_PORTDUINO)
|
||||
#include "platform/portduino/SimRadio.h"
|
||||
#endif
|
||||
@@ -78,6 +91,7 @@ NRF52Bluetooth *nrf52Bluetooth;
|
||||
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
|
||||
#include "ButtonThread.h"
|
||||
#endif
|
||||
|
||||
#include "PowerFSMThread.h"
|
||||
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
@@ -129,6 +143,9 @@ ATECCX08A atecc;
|
||||
Adafruit_DRV2605 drv;
|
||||
#endif
|
||||
|
||||
// Global LoRa radio type
|
||||
LoRaRadioType radioType = NO_RADIO;
|
||||
|
||||
bool isVibrating = false;
|
||||
|
||||
bool eink_found = true;
|
||||
@@ -162,6 +179,11 @@ const char *getDeviceName()
|
||||
|
||||
static int32_t ledBlinker()
|
||||
{
|
||||
// Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if
|
||||
// config.device.led_heartbeat_disabled is changed
|
||||
if (config.device.led_heartbeat_disabled)
|
||||
return 1000;
|
||||
|
||||
static bool ledOn;
|
||||
ledOn ^= 1;
|
||||
|
||||
@@ -175,10 +197,9 @@ uint32_t timeLastPowered = 0;
|
||||
|
||||
static Periodic *ledPeriodic;
|
||||
static OSThread *powerFSMthread;
|
||||
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
|
||||
static OSThread *buttonThread;
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
static OSThread *accelerometerThread;
|
||||
#endif
|
||||
static OSThread *ambientLightingThread;
|
||||
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
|
||||
@@ -223,7 +244,12 @@ void setup()
|
||||
#if defined(TTGO_T_ECHO) && defined(PIN_POWER_EN)
|
||||
pinMode(PIN_POWER_EN, OUTPUT);
|
||||
digitalWrite(PIN_POWER_EN, HIGH);
|
||||
digitalWrite(PIN_POWER_EN1, INPUT);
|
||||
// digitalWrite(PIN_POWER_EN1, INPUT);
|
||||
#endif
|
||||
|
||||
#if defined(LORA_TCXO_GPIO)
|
||||
pinMode(LORA_TCXO_GPIO, OUTPUT);
|
||||
digitalWrite(LORA_TCXO_GPIO, HIGH);
|
||||
#endif
|
||||
|
||||
#if defined(VEXT_ENABLE_V03)
|
||||
@@ -318,7 +344,7 @@ void setup()
|
||||
Wire.begin(I2C_SDA, I2C_SCL);
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
if (settingsStrings[i2cdev] != "") {
|
||||
LOG_INFO("Using %s as I2C device.\n", settingsStrings[i2cdev]);
|
||||
LOG_INFO("Using %s as I2C device.\n", settingsStrings[i2cdev].c_str());
|
||||
Wire.begin(settingsStrings[i2cdev].c_str());
|
||||
} else {
|
||||
LOG_INFO("No I2C device configured, skipping.\n");
|
||||
@@ -342,7 +368,7 @@ void setup()
|
||||
pinMode(PIN_3V3_EN, OUTPUT);
|
||||
digitalWrite(PIN_3V3_EN, HIGH);
|
||||
#endif
|
||||
#ifndef USE_EINK
|
||||
#ifdef AQ_SET_PIN
|
||||
// RAK-12039 set pin for Air quality sensor
|
||||
pinMode(AQ_SET_PIN, OUTPUT);
|
||||
digitalWrite(AQ_SET_PIN, HIGH);
|
||||
@@ -368,7 +394,7 @@ void setup()
|
||||
// We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to
|
||||
// accessories
|
||||
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
|
||||
#ifdef HAS_WIRE
|
||||
#if HAS_WIRE
|
||||
LOG_INFO("Scanning for i2c devices...\n");
|
||||
#endif
|
||||
|
||||
@@ -514,6 +540,8 @@ void setup()
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620)
|
||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X)
|
||||
|
||||
i2cScanner.reset();
|
||||
|
||||
@@ -549,7 +577,7 @@ void setup()
|
||||
|
||||
// We do this as early as possible because this loads preferences from flash
|
||||
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
|
||||
nodeDB.init();
|
||||
nodeDB = new NodeDB;
|
||||
|
||||
// If we're taking on the repeater role, use flood router and turn off 3V3_S rail because peripherals are not needed
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
|
||||
@@ -580,7 +608,7 @@ void setup()
|
||||
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
|
||||
#endif
|
||||
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
if (acc_info.type != ScanI2C::DeviceType::NONE) {
|
||||
config.display.wake_on_tap_or_motion = true;
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
@@ -588,7 +616,9 @@ void setup()
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
#if defined(HAS_NEOPIXEL) || defined(UNPHONE) || defined(RGBLED_RED)
|
||||
ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE);
|
||||
#elif !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
if (rgb_found.type != ScanI2C::DeviceType::NONE) {
|
||||
ambientLightingThread = new AmbientLightingThread(rgb_found.type);
|
||||
}
|
||||
@@ -611,39 +641,51 @@ void setup()
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
SPI1.begin(false);
|
||||
#else // HW_SPI1_DEVICE
|
||||
#else // HW_SPI1_DEVICE
|
||||
SPI.setSCK(LORA_SCK);
|
||||
SPI.setTX(LORA_MOSI);
|
||||
SPI.setRX(LORA_MISO);
|
||||
SPI.begin(false);
|
||||
#endif // HW_SPI1_DEVICE
|
||||
#elif ARCH_PORTDUINO
|
||||
SPI.begin(settingsStrings[spidev].c_str());
|
||||
#endif // HW_SPI1_DEVICE
|
||||
#elif !defined(ARCH_ESP32) // ARCH_RP2040
|
||||
SPI.begin();
|
||||
#else
|
||||
// ESP32
|
||||
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
LOG_WARN("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)\n", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
LOG_DEBUG("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)\n", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
SPI.setFrequency(4000000);
|
||||
#endif
|
||||
|
||||
// Initialize the screen first so we can show the logo while we start up everything else.
|
||||
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
|
||||
|
||||
// setup TZ prior to time actions.
|
||||
if (*config.device.tzdef) {
|
||||
setenv("TZ", config.device.tzdef, 1);
|
||||
} else {
|
||||
setenv("TZ", "GMT0", 1);
|
||||
}
|
||||
tzset();
|
||||
LOG_DEBUG("Set Timezone to %s\n", getenv("TZ"));
|
||||
|
||||
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
// If we're taking on the repeater role, ignore GPS
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
|
||||
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
|
||||
gps = GPS::createGps();
|
||||
if (HAS_GPS) {
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
|
||||
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
|
||||
gps = GPS::createGps();
|
||||
if (gps) {
|
||||
gpsStatus->observe(&gps->newStatus);
|
||||
} else {
|
||||
LOG_DEBUG("Running without GPS.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gps) {
|
||||
gpsStatus->observe(&gps->newStatus);
|
||||
} else {
|
||||
LOG_DEBUG("Running without GPS.\n");
|
||||
}
|
||||
nodeStatus->observe(&nodeDB.newStatus);
|
||||
#endif
|
||||
|
||||
nodeStatus->observe(&nodeDB->newStatus);
|
||||
|
||||
#ifdef HAS_I2S
|
||||
LOG_DEBUG("Starting audio thread\n");
|
||||
@@ -655,6 +697,12 @@ void setup()
|
||||
// Now that the mesh service is created, create any modules
|
||||
setupModules();
|
||||
|
||||
#ifdef LED_PIN
|
||||
// Turn LED off after boot, if heartbeat by config
|
||||
if (config.device.led_heartbeat_disabled)
|
||||
digitalWrite(LED_PIN, LOW ^ LED_INVERTED);
|
||||
#endif
|
||||
|
||||
// Do this after service.init (because that clears error_code)
|
||||
#ifdef HAS_PMU
|
||||
if (!pmu_found)
|
||||
@@ -663,7 +711,7 @@ void setup()
|
||||
|
||||
// Don't call screen setup until after nodedb is setup (because we need
|
||||
// the current region name)
|
||||
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS)
|
||||
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS)
|
||||
screen->setup();
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) {
|
||||
@@ -691,7 +739,8 @@ void setup()
|
||||
if (settingsMap[use_sx1262]) {
|
||||
if (!rIf) {
|
||||
LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str());
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
LockingArduinoHal *RadioLibHAL =
|
||||
new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
|
||||
rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
|
||||
settingsMap[busy]);
|
||||
if (!rIf->init()) {
|
||||
@@ -705,7 +754,8 @@ void setup()
|
||||
} else if (settingsMap[use_rf95]) {
|
||||
if (!rIf) {
|
||||
LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str());
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
LockingArduinoHal *RadioLibHAL =
|
||||
new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
|
||||
rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
|
||||
settingsMap[busy]);
|
||||
if (!rIf->init()) {
|
||||
@@ -732,6 +782,21 @@ void setup()
|
||||
LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n");
|
||||
}
|
||||
}
|
||||
} else if (settingsMap[use_sx1268]) {
|
||||
if (!rIf) {
|
||||
LOG_DEBUG("Attempting to activate sx1268 radio on SPI port %s\n", settingsStrings[spidev].c_str());
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
rIf = new SX1268Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
|
||||
settingsMap[busy]);
|
||||
if (!rIf->init()) {
|
||||
LOG_ERROR("Failed to find SX1268 radio\n");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(HW_SPI1_DEVICE)
|
||||
@@ -750,6 +815,7 @@ void setup()
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("STM32WL Radio init succeeded, using STM32WL radio\n");
|
||||
radioType = STM32WLx_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -763,6 +829,7 @@ void setup()
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("Using SIMULATED radio!\n");
|
||||
radioType = SIM_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -776,6 +843,7 @@ void setup()
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("RF95 Radio init succeeded, using RF95 radio\n");
|
||||
radioType = RF95_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -789,6 +857,7 @@ void setup()
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio\n");
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -802,6 +871,7 @@ void setup()
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n");
|
||||
radioType = SX1268_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -815,6 +885,7 @@ void setup()
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LLCC68 Radio init succeeded, using LLCC68 radio\n");
|
||||
radioType = LLCC68_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -828,6 +899,7 @@ void setup()
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n");
|
||||
radioType = SX1280_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -837,7 +909,7 @@ void setup()
|
||||
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) {
|
||||
LOG_WARN("Radio chip does not support 2.4GHz LoRa. Reverting to unset.\n");
|
||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
||||
nodeDB.saveToDisk(SEGMENT_CONFIG);
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
if (!rIf->reconfigure()) {
|
||||
LOG_WARN("Reconfigure failed, rebooting\n");
|
||||
screen->startRebootScreen();
|
||||
@@ -845,9 +917,12 @@ void setup()
|
||||
}
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
mqttInit();
|
||||
#endif
|
||||
|
||||
#ifndef ARCH_PORTDUINO
|
||||
|
||||
// Initialize Wifi
|
||||
#if HAS_WIFI
|
||||
initWifi();
|
||||
@@ -859,7 +934,7 @@ void setup()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
|
||||
// Start web server thread.
|
||||
webServerThread = new WebServerThread();
|
||||
#endif
|
||||
|
||||
@@ -22,6 +22,11 @@ extern NimbleBluetooth *nimbleBluetooth;
|
||||
extern NRF52Bluetooth *nrf52Bluetooth;
|
||||
#endif
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
extern HardwareSPI *DisplaySPI;
|
||||
extern HardwareSPI *LoraSPI;
|
||||
|
||||
#endif
|
||||
extern ScanI2C::DeviceAddress screen_found;
|
||||
extern ScanI2C::DeviceAddress cardkb_found;
|
||||
extern uint8_t kb_model;
|
||||
@@ -54,6 +59,7 @@ extern int TCPPort; // set by Portduino
|
||||
|
||||
// Global Screen singleton.
|
||||
extern graphics::Screen *screen;
|
||||
|
||||
// extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
||||
|
||||
// extern meshtastic::PowerStatus *powerStatus;
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
|
||||
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
|
||||
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
||||
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01};
|
||||
@@ -16,7 +20,9 @@ Channels channels;
|
||||
const char *Channels::adminChannel = "admin";
|
||||
const char *Channels::gpioChannel = "gpio";
|
||||
const char *Channels::serialChannel = "serial";
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
const char *Channels::mqttChannel = "mqtt";
|
||||
#endif
|
||||
|
||||
uint8_t xorHash(const uint8_t *p, size_t len)
|
||||
{
|
||||
@@ -193,6 +199,12 @@ void Channels::onConfigChanged()
|
||||
if (ch.role == meshtastic_Channel_Role_PRIMARY)
|
||||
primaryIndex = i;
|
||||
}
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
if (channels.anyMqttEnabled() && mqtt && !mqtt->isEnabled()) {
|
||||
LOG_DEBUG("MQTT is enabled on at least one channel, so set MQTT thread to run immediately\n");
|
||||
mqtt->start();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex)
|
||||
@@ -237,6 +249,16 @@ void Channels::setChannel(const meshtastic_Channel &c)
|
||||
old = c; // slam in the new settings/role
|
||||
}
|
||||
|
||||
bool Channels::anyMqttEnabled()
|
||||
{
|
||||
for (int i = 0; i < getNumChannels(); i++)
|
||||
if (channelFile.channels[i].role != meshtastic_Channel_Role_DISABLED && channelFile.channels[i].has_settings &&
|
||||
(channelFile.channels[i].settings.downlink_enabled || channelFile.channels[i].settings.uplink_enabled))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *Channels::getName(size_t chIndex)
|
||||
{
|
||||
// Convert the short "" representation for Default into a usable string
|
||||
@@ -274,40 +296,6 @@ bool Channels::hasDefaultChannel()
|
||||
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
|
||||
their nodes
|
||||
* aren't talking to each other.
|
||||
*
|
||||
* This string is of the form "#name-X".
|
||||
*
|
||||
* Where X is either:
|
||||
* (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together,
|
||||
*
|
||||
* This function will also need to be implemented in GUI apps that talk to the radio.
|
||||
*
|
||||
* https://github.com/meshtastic/firmware/issues/269
|
||||
*/
|
||||
const char *Channels::getPrimaryName()
|
||||
{
|
||||
static char buf[32];
|
||||
|
||||
char suffix;
|
||||
// auto channelSettings = getPrimary();
|
||||
// if (channelSettings.psk.size != 1) {
|
||||
// We have a standard PSK, so generate a letter based hash.
|
||||
uint8_t code = getHash(primaryIndex);
|
||||
|
||||
suffix = 'A' + (code % 26);
|
||||
/* } else {
|
||||
suffix = '0' + channelSettings.psk.bytes[0];
|
||||
} */
|
||||
|
||||
snprintf(buf, sizeof(buf), "#%s-%c", getName(primaryIndex), suffix);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/** Given a channel hash setup crypto for decoding that channel (or the primary channel if that channel is unsecured)
|
||||
*
|
||||
* This method is called before decoding inbound packets
|
||||
|
||||
@@ -61,25 +61,6 @@ class Channels
|
||||
|
||||
ChannelIndex getNumChannels() { return channelFile.channels_count; }
|
||||
|
||||
/**
|
||||
* 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 their nodes
|
||||
* aren't talking to each other.
|
||||
*
|
||||
* This string is of the form "#name-X".
|
||||
*
|
||||
* Where X is either:
|
||||
* (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together,
|
||||
* OR (for the standard minimially secure PSKs) a number from 0 to 9.
|
||||
*
|
||||
* This function will also need to be implemented in GUI apps that talk to the radio.
|
||||
*
|
||||
* https://github.com/meshtastic/firmware/issues/269
|
||||
*/
|
||||
const char *getPrimaryName();
|
||||
|
||||
/// Called by NodeDB on initial boot when the radio config settings are unset. Set a default single channel config.
|
||||
void initDefaults();
|
||||
|
||||
@@ -105,6 +86,9 @@ class Channels
|
||||
// Returns true if we can be reached via a channel with the default settings given a region and modem preset
|
||||
bool hasDefaultChannel();
|
||||
|
||||
// Returns true if any of our channels have enabled MQTT uplink or downlink
|
||||
bool anyMqttEnabled();
|
||||
|
||||
private:
|
||||
/** Given a channel index, change to use the crypto key specified by that index
|
||||
*
|
||||
|
||||
23
src/mesh/Default.cpp
Normal file
23
src/mesh/Default.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "Default.h"
|
||||
|
||||
uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval)
|
||||
{
|
||||
if (configuredInterval > 0)
|
||||
return configuredInterval * 1000;
|
||||
return default_broadcast_interval_secs * 1000;
|
||||
}
|
||||
|
||||
uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval)
|
||||
{
|
||||
if (configuredInterval > 0)
|
||||
return configuredInterval * 1000;
|
||||
return defaultInterval * 1000;
|
||||
}
|
||||
|
||||
uint32_t Default::getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue)
|
||||
{
|
||||
if (configured > 0)
|
||||
return configured;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
31
src/mesh/Default.h
Normal file
31
src/mesh/Default.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <NodeDB.h>
|
||||
#include <cstdint>
|
||||
#define ONE_DAY 24 * 60 * 60
|
||||
#define ONE_MINUTE_MS 60 * 1000
|
||||
|
||||
#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60)
|
||||
#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60)
|
||||
#define default_wait_bluetooth_secs IF_ROUTER(1, 60)
|
||||
#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep
|
||||
#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60)
|
||||
#define default_min_wake_secs 10
|
||||
#define default_screen_on_secs IF_ROUTER(1, 60 * 10)
|
||||
#define default_node_info_broadcast_secs 3 * 60 * 60
|
||||
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
|
||||
|
||||
#define default_mqtt_address "mqtt.meshtastic.org"
|
||||
#define default_mqtt_username "meshdev"
|
||||
#define default_mqtt_password "large4cats"
|
||||
#define default_mqtt_root "msh"
|
||||
|
||||
#define IF_ROUTER(routerVal, normalVal) \
|
||||
((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal))
|
||||
|
||||
class Default
|
||||
{
|
||||
public:
|
||||
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
|
||||
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
|
||||
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
|
||||
};
|
||||
@@ -35,11 +35,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
|
||||
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
||||
{
|
||||
bool isAck =
|
||||
((c && c->error_reason == meshtastic_Routing_Error_NONE)); // consider only ROUTING_APP message without error as ACK
|
||||
if (isAck && p->to != getNodeNum()) {
|
||||
// do not flood direct message that is ACKed
|
||||
LOG_DEBUG("Receiving an ACK not for me, but don't need to rebroadcast this direct message anymore.\n");
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0);
|
||||
if (isAckorReply && p->to != getNodeNum() && p->to != NODENUM_BROADCAST) {
|
||||
// do not flood direct message that is ACKed or replied to
|
||||
LOG_DEBUG("Receiving an ACK or reply not for me, but don't need to rebroadcast this direct message anymore.\n");
|
||||
Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
|
||||
}
|
||||
if ((p->to != getNodeNum()) && (p->hop_limit > 0) && (getFrom(p) != getNodeNum())) {
|
||||
@@ -49,15 +48,6 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
||||
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
// If it is a traceRoute request, update the route that it went via me
|
||||
if (traceRouteModule && traceRouteModule->wantPacket(p))
|
||||
traceRouteModule->updateRoute(tosend);
|
||||
// If it is a neighborInfo packet, update last_sent_by_id
|
||||
if (neighborInfoModule && neighborInfoModule->wantPacket(p))
|
||||
neighborInfoModule->updateLastSentById(tosend);
|
||||
}
|
||||
|
||||
LOG_INFO("Rebroadcasting received floodmsg to neighbors\n");
|
||||
// Note: we are careful to resend using the original senders node id
|
||||
// We are careful not to call our hooked version of send() - because we don't want to check this again
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "PacketHistory.h"
|
||||
#include "Router.h"
|
||||
#include "modules/NeighborInfoModule.h"
|
||||
#include "modules/TraceRouteModule.h"
|
||||
|
||||
/**
|
||||
* This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense)
|
||||
|
||||
@@ -12,7 +12,7 @@ const meshtastic_MeshPacket *MeshModule::currentRequest;
|
||||
|
||||
/**
|
||||
* If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow
|
||||
* the RoutingPlugin to avoid sending redundant acks
|
||||
* the RoutingModule to avoid sending redundant acks
|
||||
*/
|
||||
meshtastic_MeshPacket *MeshModule::currentReply;
|
||||
|
||||
@@ -40,7 +40,7 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod
|
||||
c.error_reason = err;
|
||||
c.which_variant = meshtastic_Routing_error_reason_tag;
|
||||
|
||||
// Now that we have moded sendAckNak up one level into the class hierarchy we can no longer assume we are a RoutingPlugin
|
||||
// Now that we have moded sendAckNak up one level into the class hierarchy we can no longer assume we are a RoutingModule
|
||||
// So we manually call pb_encode_to_bytes and specify routing port number
|
||||
// auto p = allocDataProtobuf(c);
|
||||
meshtastic_MeshPacket *p = router->allocForSending();
|
||||
@@ -54,7 +54,8 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod
|
||||
p->to = to;
|
||||
p->decoded.request_id = idFrom;
|
||||
p->channel = chIndex;
|
||||
LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id);
|
||||
if (err != meshtastic_Routing_Error_NONE)
|
||||
LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id);
|
||||
|
||||
return p;
|
||||
}
|
||||
@@ -68,7 +69,7 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e
|
||||
return r;
|
||||
}
|
||||
|
||||
void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src)
|
||||
void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
|
||||
{
|
||||
// LOG_DEBUG("In call modules\n");
|
||||
bool moduleFound = false;
|
||||
@@ -81,7 +82,7 @@ void MeshModule::callPlugins(meshtastic_MeshPacket &mp, RxSource src)
|
||||
bool ignoreRequest = false; // No module asked to ignore the request yet
|
||||
|
||||
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
|
||||
auto ourNodeNum = nodeDB.getNodeNum();
|
||||
auto ourNodeNum = nodeDB->getNodeNum();
|
||||
bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum;
|
||||
|
||||
for (auto i = modules->begin(); i != modules->end(); ++i) {
|
||||
@@ -258,7 +259,7 @@ void MeshModule::observeUIEvents(Observer<const UIFrameEvent *> *observer)
|
||||
}
|
||||
}
|
||||
|
||||
AdminMessageHandleResult MeshModule::handleAdminMessageForAllPlugins(const meshtastic_MeshPacket &mp,
|
||||
AdminMessageHandleResult MeshModule::handleAdminMessageForAllModules(const meshtastic_MeshPacket &mp,
|
||||
meshtastic_AdminMessage *request,
|
||||
meshtastic_AdminMessage *response)
|
||||
{
|
||||
@@ -279,4 +280,4 @@ AdminMessageHandleResult MeshModule::handleAdminMessageForAllPlugins(const mesht
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
@@ -64,11 +64,11 @@ class MeshModule
|
||||
|
||||
/** For use only by MeshService
|
||||
*/
|
||||
static void callPlugins(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO);
|
||||
static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO);
|
||||
|
||||
static std::vector<MeshModule *> GetMeshModulesWithUIFrames();
|
||||
static void observeUIEvents(Observer<const UIFrameEvent *> *observer);
|
||||
static AdminMessageHandleResult handleAdminMessageForAllPlugins(const meshtastic_MeshPacket &mp,
|
||||
static AdminMessageHandleResult handleAdminMessageForAllModules(const meshtastic_MeshPacket &mp,
|
||||
meshtastic_AdminMessage *request,
|
||||
meshtastic_AdminMessage *response);
|
||||
#if HAS_SCREEN
|
||||
@@ -195,4 +195,4 @@ class MeshModule
|
||||
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
|
||||
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
|
||||
*/
|
||||
void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
|
||||
void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
|
||||
@@ -1,10 +1,11 @@
|
||||
#include "configuration.h"
|
||||
#include <assert.h>
|
||||
#include <string>
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "GPS.h"
|
||||
#endif
|
||||
|
||||
#include "../concurrency/Periodic.h"
|
||||
#include "BluetoothCommon.h" // needed for updateBatteryLevel, FIXME, eventually when we pull mesh out into a lib we shouldn't be whacking bluetooth from here
|
||||
#include "GPS.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
@@ -15,8 +16,10 @@
|
||||
#include "modules/NodeInfoModule.h"
|
||||
#include "modules/PositionModule.h"
|
||||
#include "power.h"
|
||||
#include <assert.h>
|
||||
#include <string>
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
#include "nimble/NimbleBluetooth.h"
|
||||
#endif
|
||||
|
||||
@@ -72,22 +75,23 @@ void MeshService::init()
|
||||
{
|
||||
// moved much earlier in boot (called from setup())
|
||||
// nodeDB.init();
|
||||
|
||||
#if HAS_GPS
|
||||
if (gps)
|
||||
gpsObserver.observe(&gps->newStatus);
|
||||
#endif
|
||||
}
|
||||
|
||||
int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
|
||||
{
|
||||
powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping
|
||||
|
||||
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
|
||||
nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
|
||||
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
|
||||
LOG_DEBUG(
|
||||
"Received telemetry response. Skip sending our NodeInfo because this potentially a Repeater which will ignore our "
|
||||
"request for its NodeInfo.\n");
|
||||
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB.getMeshNode(mp->from)->has_user &&
|
||||
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
|
||||
nodeInfoModule) {
|
||||
LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel);
|
||||
nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel);
|
||||
@@ -120,10 +124,10 @@ bool MeshService::reloadConfig(int saveWhat)
|
||||
// If we can successfully set this radio to these settings, save them to disk
|
||||
|
||||
// This will also update the region as needed
|
||||
bool didReset = nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings
|
||||
bool didReset = nodeDB->resetRadioConfig(); // Don't let the phone send us fatally bad settings
|
||||
|
||||
configChanged.notifyObservers(NULL); // This will cause radio hardware to change freqs etc
|
||||
nodeDB.saveToDisk(saveWhat);
|
||||
nodeDB->saveToDisk(saveWhat);
|
||||
|
||||
return didReset;
|
||||
}
|
||||
@@ -133,7 +137,7 @@ void MeshService::reloadOwner(bool shouldSave)
|
||||
{
|
||||
// LOG_DEBUG("reloadOwner()\n");
|
||||
// update our local data directly
|
||||
nodeDB.updateUser(nodeDB.getNodeNum(), owner);
|
||||
nodeDB->updateUser(nodeDB->getNodeNum(), owner);
|
||||
assert(nodeInfoModule);
|
||||
// update everyone else and save to disk
|
||||
if (nodeInfoModule && shouldSave) {
|
||||
@@ -188,12 +192,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (p.from != 0) { // We don't let phones assign nodenums to their sent messages
|
||||
LOG_WARN("phone tried to pick a nodenum, we don't allow that.\n");
|
||||
p.from = 0;
|
||||
} else {
|
||||
// p.from = nodeDB.getNodeNum();
|
||||
}
|
||||
p.from = 0; // We don't let phones assign nodenums to their sent messages
|
||||
|
||||
if (p.id == 0)
|
||||
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
||||
@@ -217,7 +216,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
||||
/** Attempt to cancel a previously sent packet from this _local_ node. Returns true if a packet was found we could cancel */
|
||||
bool MeshService::cancelSending(PacketId id)
|
||||
{
|
||||
return router->cancelSending(nodeDB.getNodeNum(), id);
|
||||
return router->cancelSending(nodeDB->getNodeNum(), id);
|
||||
}
|
||||
|
||||
ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id)
|
||||
@@ -245,7 +244,7 @@ ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs,
|
||||
void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPhone)
|
||||
{
|
||||
uint32_t mesh_packet_id = p->id;
|
||||
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
|
||||
nodeDB->updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
|
||||
|
||||
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
|
||||
ErrorCode res = router->sendLocal(p, src);
|
||||
@@ -258,28 +257,32 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
|
||||
LOG_DEBUG("Can't send status to phone");
|
||||
}
|
||||
|
||||
if (ccToPhone) {
|
||||
if (res == ERRNO_OK && ccToPhone) { // Check if p is not released in case it couldn't be sent
|
||||
sendToPhone(packetPool.allocCopy(*p));
|
||||
}
|
||||
}
|
||||
|
||||
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
|
||||
bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
assert(node);
|
||||
|
||||
if (hasValidPosition(node)) {
|
||||
#if HAS_GPS
|
||||
if (positionModule) {
|
||||
LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel);
|
||||
positionModule->sendOurPosition(dest, wantReplies, node->channel);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
#endif
|
||||
if (nodeInfoModule) {
|
||||
LOG_INFO("Sending nodeinfo ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel);
|
||||
nodeInfoModule->sendOurNodeInfo(dest, wantReplies, node->channel);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MeshService::sendToPhone(meshtastic_MeshPacket *p)
|
||||
@@ -320,7 +323,7 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage
|
||||
|
||||
meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode()
|
||||
{
|
||||
meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
assert(node);
|
||||
|
||||
// We might not have a position yet for our local node, in that case, at least try to send the time
|
||||
@@ -344,6 +347,7 @@ meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode()
|
||||
return node;
|
||||
}
|
||||
|
||||
#if HAS_GPS
|
||||
int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
|
||||
{
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
@@ -359,7 +363,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
|
||||
LOG_DEBUG("onGPSchanged() - lost validLocation\n");
|
||||
#endif
|
||||
}
|
||||
// Used fixed position if configured regalrdless of GPS lock
|
||||
// Used fixed position if configured regardless of GPS lock
|
||||
if (config.position.fixed_position) {
|
||||
LOG_WARN("Using fixed position\n");
|
||||
pos = TypeConversions::ConvertToPosition(node->position);
|
||||
@@ -373,11 +377,11 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
|
||||
pos.longitude_i, pos.altitude);
|
||||
|
||||
// Update our current position in the local DB
|
||||
nodeDB.updatePosition(nodeDB.getNodeNum(), pos, RX_SRC_LOCAL);
|
||||
nodeDB->updatePosition(nodeDB->getNodeNum(), pos, RX_SRC_LOCAL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
bool MeshService::isToPhoneQueueEmpty()
|
||||
{
|
||||
return toPhoneQueue.isEmpty();
|
||||
|
||||
@@ -23,9 +23,10 @@ extern Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool;
|
||||
*/
|
||||
class MeshService
|
||||
{
|
||||
#if HAS_GPS
|
||||
CallbackObserver<MeshService, const meshtastic::GPSStatus *> gpsObserver =
|
||||
CallbackObserver<MeshService, const meshtastic::GPSStatus *>(this, &MeshService::onGPSChanged);
|
||||
|
||||
#endif
|
||||
/// received packets waiting for the phone to process them
|
||||
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
|
||||
/// we never hang because android hasn't been there in a while
|
||||
@@ -107,8 +108,9 @@ class MeshService
|
||||
void reloadOwner(bool shouldSave = true);
|
||||
|
||||
/// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least
|
||||
/// sends our owner
|
||||
void sendNetworkPing(NodeNum dest, bool wantReplies = false);
|
||||
/// sends our nodeinfo
|
||||
/// returns true if we sent a position
|
||||
bool trySendPosition(NodeNum dest, bool wantReplies = false);
|
||||
|
||||
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after
|
||||
/// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb
|
||||
@@ -132,10 +134,11 @@ class MeshService
|
||||
ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id);
|
||||
|
||||
private:
|
||||
#if HAS_GPS
|
||||
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
|
||||
/// returns 0 to allow further processing
|
||||
int onGPSChanged(const meshtastic::GPSStatus *arg);
|
||||
|
||||
#endif
|
||||
/// Handle a packet that just arrived from the radio. This method does _ReliableRouternot_ free the provided packet. If it
|
||||
/// needs to keep the packet around it makes a copy
|
||||
int handleFromRadio(const meshtastic_MeshPacket *p);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "GPS.h"
|
||||
#endif
|
||||
#include "../detect/ScanI2C.h"
|
||||
#include "Channels.h"
|
||||
#include "CryptoEngine.h"
|
||||
#include "Default.h"
|
||||
#include "FSCommon.h"
|
||||
#include "GPS.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PacketHistory.h"
|
||||
@@ -17,11 +19,16 @@
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "modules/NeighborInfoModule.h"
|
||||
#include <ErriezCRC32.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#if !MESHTASTIC_EXCLUDE_WIFI
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
#endif
|
||||
#include "modules/esp32/StoreForwardModule.h"
|
||||
#include <Preferences.h>
|
||||
#include <nvs_flash.h>
|
||||
@@ -36,7 +43,7 @@
|
||||
#include <utility/bonding.h>
|
||||
#endif
|
||||
|
||||
NodeDB nodeDB;
|
||||
NodeDB *nodeDB = nullptr;
|
||||
|
||||
// we have plenty of ram so statically alloc this tempbuf (for now)
|
||||
EXT_RAM_ATTR meshtastic_DeviceState devicestate;
|
||||
@@ -46,6 +53,26 @@ meshtastic_LocalModuleConfig moduleConfig;
|
||||
meshtastic_ChannelFile channelFile;
|
||||
meshtastic_OEMStore oemStore;
|
||||
|
||||
bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
|
||||
{
|
||||
if (ostream) {
|
||||
std::vector<meshtastic_NodeInfoLite> const *vec = (std::vector<meshtastic_NodeInfoLite> *)field->pData;
|
||||
for (auto item : *vec) {
|
||||
if (!pb_encode_tag_for_field(ostream, field))
|
||||
return false;
|
||||
pb_encode_submessage(ostream, meshtastic_NodeInfoLite_fields, &item);
|
||||
}
|
||||
}
|
||||
if (istream) {
|
||||
meshtastic_NodeInfoLite node; // this gets good data
|
||||
std::vector<meshtastic_NodeInfoLite> *vec = (std::vector<meshtastic_NodeInfoLite> *)field->pData;
|
||||
|
||||
if (istream->bytes_left && pb_decode(istream, meshtastic_NodeInfoLite_fields, &node))
|
||||
vec->push_back(node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
|
||||
* might have changed is incremented. Allows others to detect they might now be on a new channel.
|
||||
*/
|
||||
@@ -68,7 +95,63 @@ uint32_t error_address = 0;
|
||||
|
||||
static uint8_t ourMacAddr[6];
|
||||
|
||||
NodeDB::NodeDB() : meshNodes(devicestate.node_db_lite), numMeshNodes(&devicestate.node_db_lite_count) {}
|
||||
NodeDB::NodeDB()
|
||||
{
|
||||
LOG_INFO("Initializing NodeDB\n");
|
||||
loadFromDisk();
|
||||
cleanupMeshDB();
|
||||
|
||||
uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate));
|
||||
uint32_t configCRC = crc32Buffer(&config, sizeof(config));
|
||||
uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile));
|
||||
|
||||
int saveWhat = 0;
|
||||
|
||||
// likewise - we always want the app requirements to come from the running appload
|
||||
myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00
|
||||
// Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
|
||||
// keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
|
||||
pickNewNodeNum();
|
||||
|
||||
// Set our board type so we can share it with others
|
||||
owner.hw_model = HW_VENDOR;
|
||||
// Ensure user (nodeinfo) role is set to whatever we're configured to
|
||||
owner.role = config.device.role;
|
||||
|
||||
// Include our owner in the node db under our nodenum
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
|
||||
info->user = owner;
|
||||
info->has_user = true;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
Preferences preferences;
|
||||
preferences.begin("meshtastic", false);
|
||||
myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0);
|
||||
preferences.end();
|
||||
LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count);
|
||||
#endif
|
||||
|
||||
resetRadioConfig(); // If bogus settings got saved, then fix them
|
||||
// nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, numMeshNodes);
|
||||
|
||||
if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate)))
|
||||
saveWhat |= SEGMENT_DEVICESTATE;
|
||||
if (configCRC != crc32Buffer(&config, sizeof(config)))
|
||||
saveWhat |= SEGMENT_CONFIG;
|
||||
if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile)))
|
||||
saveWhat |= SEGMENT_CHANNELS;
|
||||
|
||||
if (!devicestate.node_remote_hardware_pins) {
|
||||
meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default};
|
||||
memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty));
|
||||
}
|
||||
|
||||
if (config.position.gps_enabled) {
|
||||
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
|
||||
config.position.gps_enabled = 0;
|
||||
}
|
||||
saveToDisk(saveWhat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on
|
||||
@@ -76,7 +159,7 @@ NodeDB::NodeDB() : meshNodes(devicestate.node_db_lite), numMeshNodes(&devicestat
|
||||
*/
|
||||
NodeNum getFrom(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
return (p->from == 0) ? nodeDB.getNodeNum() : p->from;
|
||||
return (p->from == 0) ? nodeDB->getNodeNum() : p->from;
|
||||
}
|
||||
|
||||
bool NodeDB::resetRadioConfig(bool factory_reset)
|
||||
@@ -97,22 +180,6 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
|
||||
|
||||
channels.onConfigChanged();
|
||||
|
||||
// temp hack for quicker testing
|
||||
// devicestate.no_save = true;
|
||||
if (devicestate.no_save) {
|
||||
LOG_DEBUG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n");
|
||||
|
||||
// Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins
|
||||
config.display.screen_on_secs = 10;
|
||||
config.power.wait_bluetooth_secs = 10;
|
||||
config.position.position_broadcast_secs = 6 * 60;
|
||||
config.power.ls_secs = 60;
|
||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_TW;
|
||||
|
||||
// Enter super deep sleep soon and stay there not very long
|
||||
// radioConfig.preferences.sds_secs = 60;
|
||||
}
|
||||
|
||||
// Update the global myRegion
|
||||
initRegion();
|
||||
|
||||
@@ -131,7 +198,7 @@ bool NodeDB::factoryReset()
|
||||
// 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");
|
||||
LOG_ERROR("Could not remove rangetest.csv file\n");
|
||||
}
|
||||
// second, install default state (this will deal with the duplicate mac address issue)
|
||||
installDefaultDeviceState();
|
||||
@@ -166,7 +233,7 @@ void NodeDB::installDefaultConfig()
|
||||
config.has_position = true;
|
||||
config.has_power = true;
|
||||
config.has_network = true;
|
||||
config.has_bluetooth = true;
|
||||
config.has_bluetooth = (HAS_BLUETOOTH ? true : false);
|
||||
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
|
||||
|
||||
config.lora.sx126x_rx_boosted_gain = true;
|
||||
@@ -199,10 +266,10 @@ void NodeDB::installDefaultConfig()
|
||||
config.position.broadcast_smart_minimum_distance = 100;
|
||||
config.position.broadcast_smart_minimum_interval_secs = 30;
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER)
|
||||
config.device.node_info_broadcast_secs = 3 * 60 * 60;
|
||||
config.device.node_info_broadcast_secs = default_node_info_broadcast_secs;
|
||||
config.device.serial_enabled = true;
|
||||
resetRadioConfig();
|
||||
strncpy(config.network.ntp_server, "0.pool.ntp.org", 32);
|
||||
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
|
||||
// FIXME: Default to bluetooth capability of platform as default
|
||||
config.bluetooth.enabled = true;
|
||||
config.bluetooth.fixed_pin = defaultBLEPin;
|
||||
@@ -244,6 +311,12 @@ void NodeDB::initConfigIntervals()
|
||||
config.power.wait_bluetooth_secs = default_wait_bluetooth_secs;
|
||||
|
||||
config.display.screen_on_secs = default_screen_on_secs;
|
||||
|
||||
#if defined(T_WATCH_S3) || defined(T_DECK)
|
||||
config.power.is_power_saving = true;
|
||||
config.display.screen_on_secs = 30;
|
||||
config.power.wait_bluetooth_secs = 30;
|
||||
#endif
|
||||
}
|
||||
|
||||
void NodeDB::installDefaultModuleConfig()
|
||||
@@ -368,51 +441,67 @@ void NodeDB::installDefaultChannels()
|
||||
|
||||
void NodeDB::resetNodes()
|
||||
{
|
||||
devicestate.node_db_lite_count = 1;
|
||||
std::fill(&devicestate.node_db_lite[1], &devicestate.node_db_lite[MAX_NUM_NODES - 1], meshtastic_NodeInfoLite());
|
||||
numMeshNodes = 1;
|
||||
std::fill(devicestate.node_db_lite.begin() + 1, devicestate.node_db_lite.end(), meshtastic_NodeInfoLite());
|
||||
clearLocalPosition();
|
||||
saveDeviceStateToDisk();
|
||||
if (neighborInfoModule && moduleConfig.neighbor_info.enabled)
|
||||
neighborInfoModule->resetNeighbors();
|
||||
}
|
||||
|
||||
void NodeDB::removeNodeByNum(uint nodeNum)
|
||||
void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
||||
{
|
||||
int newPos = 0, removed = 0;
|
||||
for (int i = 0; i < *numMeshNodes; i++) {
|
||||
if (meshNodes[i].num != nodeNum)
|
||||
meshNodes[newPos++] = meshNodes[i];
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (meshNodes->at(i).num != nodeNum)
|
||||
meshNodes->at(newPos++) = meshNodes->at(i);
|
||||
else
|
||||
removed++;
|
||||
}
|
||||
*numMeshNodes -= removed;
|
||||
numMeshNodes -= removed;
|
||||
std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + 1,
|
||||
meshtastic_NodeInfoLite());
|
||||
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Saving changes...\n", removed);
|
||||
saveDeviceStateToDisk();
|
||||
}
|
||||
|
||||
void NodeDB::clearLocalPosition()
|
||||
{
|
||||
meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum());
|
||||
node->position.latitude_i = 0;
|
||||
node->position.longitude_i = 0;
|
||||
node->position.altitude = 0;
|
||||
node->position.time = 0;
|
||||
setLocalPosition(meshtastic_Position_init_default);
|
||||
}
|
||||
|
||||
void NodeDB::cleanupMeshDB()
|
||||
{
|
||||
int newPos = 0, removed = 0;
|
||||
for (int i = 0; i < *numMeshNodes; i++) {
|
||||
if (meshNodes[i].has_user)
|
||||
meshNodes[newPos++] = meshNodes[i];
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (meshNodes->at(i).has_user)
|
||||
meshNodes->at(newPos++) = meshNodes->at(i);
|
||||
else
|
||||
removed++;
|
||||
}
|
||||
*numMeshNodes -= removed;
|
||||
numMeshNodes -= removed;
|
||||
std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + removed,
|
||||
meshtastic_NodeInfoLite());
|
||||
LOG_DEBUG("cleanupMeshDB purged %d entries\n", removed);
|
||||
}
|
||||
|
||||
void NodeDB::installDefaultDeviceState()
|
||||
{
|
||||
LOG_INFO("Installing default DeviceState\n");
|
||||
memset(&devicestate, 0, sizeof(meshtastic_DeviceState));
|
||||
// memset(&devicestate, 0, sizeof(meshtastic_DeviceState));
|
||||
|
||||
*numMeshNodes = 0;
|
||||
numMeshNodes = 0;
|
||||
meshNodes = &devicestate.node_db_lite;
|
||||
|
||||
// init our devicestate with valid flags so protobuf writing/reading will work
|
||||
devicestate.has_my_node = true;
|
||||
devicestate.has_owner = true;
|
||||
devicestate.node_db_lite_count = 0;
|
||||
// devicestate.node_db_lite_count = 0;
|
||||
devicestate.version = DEVICESTATE_CUR_VER;
|
||||
devicestate.receive_queue_count = 0; // Not yet implemented FIXME
|
||||
|
||||
@@ -426,65 +515,6 @@ void NodeDB::installDefaultDeviceState()
|
||||
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
|
||||
}
|
||||
|
||||
void NodeDB::init()
|
||||
{
|
||||
LOG_INFO("Initializing NodeDB\n");
|
||||
loadFromDisk();
|
||||
cleanupMeshDB();
|
||||
|
||||
uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate));
|
||||
uint32_t configCRC = crc32Buffer(&config, sizeof(config));
|
||||
uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile));
|
||||
|
||||
int saveWhat = 0;
|
||||
|
||||
// likewise - we always want the app requirements to come from the running appload
|
||||
myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00
|
||||
// Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
|
||||
// keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
|
||||
pickNewNodeNum();
|
||||
|
||||
// Set our board type so we can share it with others
|
||||
owner.hw_model = HW_VENDOR;
|
||||
// Ensure user (nodeinfo) role is set to whatever we're configured to
|
||||
owner.role = config.device.role;
|
||||
|
||||
// Include our owner in the node db under our nodenum
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
|
||||
info->user = owner;
|
||||
info->has_user = true;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
Preferences preferences;
|
||||
preferences.begin("meshtastic", false);
|
||||
myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0);
|
||||
preferences.end();
|
||||
LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count);
|
||||
#endif
|
||||
|
||||
resetRadioConfig(); // If bogus settings got saved, then fix them
|
||||
LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, *numMeshNodes);
|
||||
|
||||
if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate)))
|
||||
saveWhat |= SEGMENT_DEVICESTATE;
|
||||
if (configCRC != crc32Buffer(&config, sizeof(config)))
|
||||
saveWhat |= SEGMENT_CONFIG;
|
||||
if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile)))
|
||||
saveWhat |= SEGMENT_CHANNELS;
|
||||
|
||||
if (!devicestate.node_remote_hardware_pins) {
|
||||
meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default};
|
||||
memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty));
|
||||
}
|
||||
|
||||
if (config.position.gps_enabled) {
|
||||
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
|
||||
config.position.gps_enabled = 0;
|
||||
}
|
||||
|
||||
saveToDisk(saveWhat);
|
||||
}
|
||||
|
||||
// We reserve a few nodenums for future use
|
||||
#define NUM_RESERVED 4
|
||||
|
||||
@@ -493,11 +523,12 @@ void NodeDB::init()
|
||||
*/
|
||||
void NodeDB::pickNewNodeNum()
|
||||
{
|
||||
|
||||
NodeNum nodeNum = myNodeInfo.my_node_num;
|
||||
getMacAddr(ourMacAddr); // Make sure ourMacAddr is set
|
||||
|
||||
// Pick an initial nodenum based on the macaddr
|
||||
NodeNum nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
||||
if (nodeNum == 0) {
|
||||
// Pick an initial nodenum based on the macaddr
|
||||
nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite *found;
|
||||
while ((nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED) ||
|
||||
@@ -506,7 +537,7 @@ void NodeDB::pickNewNodeNum()
|
||||
LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, so trying for 0x%x\n", nodeNum, candidate);
|
||||
nodeNum = candidate;
|
||||
}
|
||||
LOG_WARN("Using nodenum 0x%x \n", nodeNum);
|
||||
LOG_DEBUG("Using nodenum 0x%x \n", nodeNum);
|
||||
|
||||
myNodeInfo.my_node_num = nodeNum;
|
||||
}
|
||||
@@ -517,12 +548,17 @@ static const char *moduleConfigFileName = "/prefs/module.proto";
|
||||
static const char *channelFileName = "/prefs/channels.proto";
|
||||
static const char *oemConfigFile = "/oem/oem.proto";
|
||||
|
||||
/** Load a protobuf from a file, return true for success */
|
||||
bool NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct)
|
||||
/** Load a protobuf from a file, return LoadFileResult */
|
||||
LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
|
||||
void *dest_struct)
|
||||
{
|
||||
bool okay = false;
|
||||
LoadFileResult state = LoadFileResult::OTHER_FAILURE;
|
||||
#ifdef FSCom
|
||||
// static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
|
||||
|
||||
if (!FSCom.exists(filename)) {
|
||||
LOG_INFO("File %s not found\n", filename);
|
||||
return LoadFileResult::NOT_FOUND;
|
||||
}
|
||||
|
||||
auto f = FSCom.open(filename, FILE_O_READ);
|
||||
|
||||
@@ -530,42 +566,49 @@ bool NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, c
|
||||
LOG_INFO("Loading %s\n", filename);
|
||||
pb_istream_t stream = {&readcb, &f, protoSize};
|
||||
|
||||
// LOG_DEBUG("Preload channel name=%s\n", channelSettings.name);
|
||||
|
||||
memset(dest_struct, 0, objSize);
|
||||
if (!pb_decode(&stream, fields, dest_struct)) {
|
||||
LOG_ERROR("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
|
||||
state = LoadFileResult::DECODE_FAILED;
|
||||
} else {
|
||||
okay = true;
|
||||
LOG_INFO("Loaded %s successfully\n", filename);
|
||||
state = LoadFileResult::SUCCESS;
|
||||
}
|
||||
|
||||
f.close();
|
||||
} else {
|
||||
LOG_INFO("No %s preferences found\n", filename);
|
||||
LOG_ERROR("Could not open / read %s\n", filename);
|
||||
}
|
||||
#else
|
||||
LOG_ERROR("ERROR: Filesystem not implemented\n");
|
||||
state = LoadFileState::NO_FILESYSTEM;
|
||||
#endif
|
||||
return okay;
|
||||
return state;
|
||||
}
|
||||
|
||||
void NodeDB::loadFromDisk()
|
||||
{
|
||||
// static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
|
||||
if (!loadProto(prefFileName, meshtastic_DeviceState_size, sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg,
|
||||
&devicestate)) {
|
||||
auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo),
|
||||
sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate);
|
||||
|
||||
if (state != LoadFileResult::SUCCESS) {
|
||||
installDefaultDeviceState(); // Our in RAM copy might now be corrupt
|
||||
} else {
|
||||
if (devicestate.version < DEVICESTATE_MIN_VER) {
|
||||
LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version);
|
||||
factoryReset();
|
||||
} else {
|
||||
LOG_INFO("Loaded saved devicestate version %d\n", devicestate.version);
|
||||
LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version,
|
||||
devicestate.node_db_lite.size());
|
||||
meshNodes = &devicestate.node_db_lite;
|
||||
numMeshNodes = devicestate.node_db_lite.size();
|
||||
}
|
||||
}
|
||||
meshNodes->resize(MAX_NUM_NODES);
|
||||
|
||||
if (!loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg,
|
||||
&config)) {
|
||||
state = loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg,
|
||||
&config);
|
||||
if (state != LoadFileResult::SUCCESS) {
|
||||
installDefaultConfig(); // Our in RAM copy might now be corrupt
|
||||
} else {
|
||||
if (config.version < DEVICESTATE_MIN_VER) {
|
||||
@@ -576,8 +619,9 @@ void NodeDB::loadFromDisk()
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig),
|
||||
&meshtastic_LocalModuleConfig_msg, &moduleConfig)) {
|
||||
state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig),
|
||||
&meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
||||
if (state != LoadFileResult::SUCCESS) {
|
||||
installDefaultModuleConfig(); // Our in RAM copy might now be corrupt
|
||||
} else {
|
||||
if (moduleConfig.version < DEVICESTATE_MIN_VER) {
|
||||
@@ -588,8 +632,9 @@ void NodeDB::loadFromDisk()
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg,
|
||||
&channelFile)) {
|
||||
state = loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg,
|
||||
&channelFile);
|
||||
if (state != LoadFileResult::SUCCESS) {
|
||||
installDefaultChannels(); // Our in RAM copy might now be corrupt
|
||||
} else {
|
||||
if (channelFile.version < DEVICESTATE_MIN_VER) {
|
||||
@@ -600,7 +645,8 @@ void NodeDB::loadFromDisk()
|
||||
}
|
||||
}
|
||||
|
||||
if (loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore)) {
|
||||
state = loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore);
|
||||
if (state == LoadFileResult::SUCCESS) {
|
||||
LOG_INFO("Loaded OEMStore\n");
|
||||
}
|
||||
}
|
||||
@@ -639,9 +685,13 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
|
||||
static uint8_t failedCounter = 0;
|
||||
failedCounter++;
|
||||
if (failedCounter >= 2) {
|
||||
FSCom.format();
|
||||
// After formatting, the device needs to be restarted
|
||||
nodeDB.resetRadioConfig(true);
|
||||
LOG_ERROR("Failed to save file twice. Rebooting...\n");
|
||||
delay(100);
|
||||
NVIC_SystemReset();
|
||||
// We used to blow away the filesystem here, but that's a bit extreme
|
||||
// FSCom.format();
|
||||
// // After formatting, the device needs to be restarted
|
||||
// nodeDB->resetRadioConfig(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -653,68 +703,68 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
|
||||
|
||||
void NodeDB::saveChannelsToDisk()
|
||||
{
|
||||
if (!devicestate.no_save) {
|
||||
#ifdef FSCom
|
||||
FSCom.mkdir("/prefs");
|
||||
FSCom.mkdir("/prefs");
|
||||
#endif
|
||||
saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile);
|
||||
}
|
||||
saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile);
|
||||
}
|
||||
|
||||
void NodeDB::saveDeviceStateToDisk()
|
||||
{
|
||||
if (!devicestate.no_save) {
|
||||
#ifdef FSCom
|
||||
FSCom.mkdir("/prefs");
|
||||
FSCom.mkdir("/prefs");
|
||||
#endif
|
||||
saveProto(prefFileName, meshtastic_DeviceState_size, &meshtastic_DeviceState_msg, &devicestate);
|
||||
}
|
||||
saveProto(prefFileName, sizeof(devicestate) + numMeshNodes * meshtastic_NodeInfoLite_size, &meshtastic_DeviceState_msg,
|
||||
&devicestate);
|
||||
}
|
||||
|
||||
void NodeDB::saveToDisk(int saveWhat)
|
||||
{
|
||||
if (!devicestate.no_save) {
|
||||
#ifdef FSCom
|
||||
FSCom.mkdir("/prefs");
|
||||
FSCom.mkdir("/prefs");
|
||||
#endif
|
||||
if (saveWhat & SEGMENT_DEVICESTATE) {
|
||||
saveDeviceStateToDisk();
|
||||
}
|
||||
if (saveWhat & SEGMENT_DEVICESTATE) {
|
||||
saveDeviceStateToDisk();
|
||||
}
|
||||
|
||||
if (saveWhat & SEGMENT_CONFIG) {
|
||||
config.has_device = true;
|
||||
config.has_display = true;
|
||||
config.has_lora = true;
|
||||
config.has_position = true;
|
||||
config.has_power = true;
|
||||
config.has_network = true;
|
||||
config.has_bluetooth = true;
|
||||
saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config);
|
||||
}
|
||||
if (saveWhat & SEGMENT_CONFIG) {
|
||||
config.has_device = true;
|
||||
config.has_display = true;
|
||||
config.has_lora = true;
|
||||
config.has_position = true;
|
||||
config.has_power = true;
|
||||
config.has_network = true;
|
||||
config.has_bluetooth = true;
|
||||
|
||||
if (saveWhat & SEGMENT_MODULECONFIG) {
|
||||
moduleConfig.has_canned_message = true;
|
||||
moduleConfig.has_external_notification = true;
|
||||
moduleConfig.has_mqtt = true;
|
||||
moduleConfig.has_range_test = true;
|
||||
moduleConfig.has_serial = true;
|
||||
moduleConfig.has_store_forward = true;
|
||||
moduleConfig.has_telemetry = true;
|
||||
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
||||
}
|
||||
saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config);
|
||||
}
|
||||
|
||||
if (saveWhat & SEGMENT_CHANNELS) {
|
||||
saveChannelsToDisk();
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG("***** DEVELOPMENT MODE - DO NOT RELEASE - not saving to flash *****\n");
|
||||
if (saveWhat & SEGMENT_MODULECONFIG) {
|
||||
moduleConfig.has_canned_message = true;
|
||||
moduleConfig.has_external_notification = true;
|
||||
moduleConfig.has_mqtt = true;
|
||||
moduleConfig.has_range_test = true;
|
||||
moduleConfig.has_serial = true;
|
||||
moduleConfig.has_store_forward = true;
|
||||
moduleConfig.has_telemetry = true;
|
||||
moduleConfig.has_neighbor_info = true;
|
||||
moduleConfig.has_detection_sensor = true;
|
||||
moduleConfig.has_ambient_lighting = true;
|
||||
moduleConfig.has_audio = true;
|
||||
moduleConfig.has_paxcounter = true;
|
||||
|
||||
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
||||
}
|
||||
|
||||
if (saveWhat & SEGMENT_CHANNELS) {
|
||||
saveChannelsToDisk();
|
||||
}
|
||||
}
|
||||
|
||||
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
||||
{
|
||||
if (readIndex < *numMeshNodes)
|
||||
return &meshNodes[readIndex++];
|
||||
if (readIndex < numMeshNodes)
|
||||
return &meshNodes->at(readIndex++);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
@@ -749,10 +799,10 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
||||
size_t numseen = 0;
|
||||
|
||||
// FIXME this implementation is kinda expensive
|
||||
for (int i = 0; i < *numMeshNodes; i++) {
|
||||
if (localOnly && meshNodes[i].via_mqtt)
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (localOnly && meshNodes->at(i).via_mqtt)
|
||||
continue;
|
||||
if (sinceLastSeen(&meshNodes[i]) < NUM_ONLINE_SECS)
|
||||
if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS)
|
||||
numseen++;
|
||||
}
|
||||
|
||||
@@ -760,6 +810,7 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
||||
}
|
||||
|
||||
#include "MeshModule.h"
|
||||
#include "Throttle.h"
|
||||
|
||||
/** Update position info for this node based on received position data
|
||||
*/
|
||||
@@ -854,8 +905,10 @@ bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t chann
|
||||
powerFSM.trigger(EVENT_NODEDB_UPDATED);
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
|
||||
// We just changed something important about the user, store our DB
|
||||
saveToDisk(SEGMENT_DEVICESTATE);
|
||||
// We just changed something about the user, store our DB
|
||||
Throttle::execute(
|
||||
&lastNodeDbSave, ONE_MINUTE_MS, []() { nodeDB->saveToDisk(SEGMENT_DEVICESTATE); },
|
||||
[]() { LOG_DEBUG("Deferring NodeDB saveToDisk for now, since we saved less than a minute ago\n"); });
|
||||
}
|
||||
|
||||
return changed;
|
||||
@@ -900,9 +953,9 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
||||
/// NOTE: This function might be called from an ISR
|
||||
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
|
||||
{
|
||||
for (int i = 0; i < *numMeshNodes; i++)
|
||||
if (meshNodes[i].num == n)
|
||||
return &meshNodes[i];
|
||||
for (int i = 0; i < numMeshNodes; i++)
|
||||
if (meshNodes->at(i).num == n)
|
||||
return &meshNodes->at(i);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -913,27 +966,27 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(n);
|
||||
|
||||
if (!lite) {
|
||||
if ((*numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) {
|
||||
if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) {
|
||||
if (screen)
|
||||
screen->print("Warn: node database full!\nErasing oldest entry\n");
|
||||
LOG_INFO("Warn: node database full!\nErasing oldest entry\n");
|
||||
LOG_WARN("Node database full! Erasing oldest entry\n");
|
||||
// look for oldest node and erase it
|
||||
uint32_t oldest = UINT32_MAX;
|
||||
int oldestIndex = -1;
|
||||
for (int i = 1; i < *numMeshNodes; i++) {
|
||||
if (meshNodes[i].last_heard < oldest) {
|
||||
oldest = meshNodes[i].last_heard;
|
||||
for (int i = 1; i < numMeshNodes; i++) {
|
||||
if (!meshNodes->at(i).is_favorite && meshNodes->at(i).last_heard < oldest) {
|
||||
oldest = meshNodes->at(i).last_heard;
|
||||
oldestIndex = i;
|
||||
}
|
||||
}
|
||||
// Shove the remaining nodes down the chain
|
||||
for (int i = oldestIndex; i < *numMeshNodes - 1; i++) {
|
||||
meshNodes[i] = meshNodes[i + 1];
|
||||
for (int i = oldestIndex; i < numMeshNodes - 1; i++) {
|
||||
meshNodes->at(i) = meshNodes->at(i + 1);
|
||||
}
|
||||
(*numMeshNodes)--;
|
||||
(numMeshNodes)--;
|
||||
}
|
||||
// add the node at the end
|
||||
lite = &meshNodes[(*numMeshNodes)++];
|
||||
lite = &meshNodes->at((numMeshNodes)++);
|
||||
|
||||
// everything is missing except the nodenum
|
||||
memset(lite, 0, sizeof(*lite));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Observer.h"
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
|
||||
#include "MeshTypes.h"
|
||||
#include "NodeStatus.h"
|
||||
@@ -37,6 +38,19 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
|
||||
/// Given a packet, return how many seconds in the past (vs now) it was received
|
||||
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
|
||||
|
||||
enum LoadFileResult {
|
||||
// Successfully opened the file
|
||||
SUCCESS = 1,
|
||||
// File does not exist
|
||||
NOT_FOUND = 2,
|
||||
// Device does not have a filesystem
|
||||
NO_FILESYSTEM = 3,
|
||||
// File exists, but could not decode protobufs
|
||||
DECODE_FAILED = 4,
|
||||
// File exists, but open failed for some reason
|
||||
OTHER_FAILURE = 5
|
||||
};
|
||||
|
||||
class NodeDB
|
||||
{
|
||||
// NodeNum provisionalNodeNum; // if we are trying to find a node num this is our current attempt
|
||||
@@ -45,21 +59,18 @@ class NodeDB
|
||||
// Eventually use a smarter datastructure
|
||||
// HashMap<NodeNum, NodeInfo> nodes;
|
||||
// Note: these two references just point into our static array we serialize to/from disk
|
||||
meshtastic_NodeInfoLite *meshNodes;
|
||||
pb_size_t *numMeshNodes;
|
||||
|
||||
public:
|
||||
std::vector<meshtastic_NodeInfoLite> *meshNodes;
|
||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||
meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||
pb_size_t numMeshNodes;
|
||||
|
||||
/// don't do mesh based algorithm for node id assignment (initially)
|
||||
/// instead just store in flash - possibly even in the initial alpha release do this hack
|
||||
NodeDB();
|
||||
|
||||
/// Called from service after app start, to do init which can only be done after OS load
|
||||
void init();
|
||||
|
||||
/// write to flash
|
||||
void saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS),
|
||||
saveChannelsToDisk(), saveDeviceStateToDisk();
|
||||
@@ -113,11 +124,12 @@ class NodeDB
|
||||
*/
|
||||
size_t getNumOnlineMeshNodes(bool localOnly = false);
|
||||
|
||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum);
|
||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum);
|
||||
|
||||
bool factoryReset();
|
||||
|
||||
bool loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct);
|
||||
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
|
||||
void *dest_struct);
|
||||
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct);
|
||||
|
||||
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
|
||||
@@ -126,21 +138,30 @@ class NodeDB
|
||||
|
||||
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
|
||||
{
|
||||
assert(x < *numMeshNodes);
|
||||
return &meshNodes[x];
|
||||
assert(x < numMeshNodes);
|
||||
return &meshNodes->at(x);
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
||||
size_t getNumMeshNodes() { return *numMeshNodes; }
|
||||
size_t getNumMeshNodes() { return numMeshNodes; }
|
||||
|
||||
void setLocalPosition(meshtastic_Position position)
|
||||
void clearLocalPosition();
|
||||
|
||||
void setLocalPosition(meshtastic_Position position, bool timeOnly = false)
|
||||
{
|
||||
LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%i\n", position.latitude_i, position.longitude_i,
|
||||
position.time);
|
||||
if (timeOnly) {
|
||||
LOG_DEBUG("Setting local position time only: time=%u timestamp=%u\n", position.time, position.timestamp);
|
||||
localPosition.time = position.time;
|
||||
localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time;
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%u, timestamp=%u\n", position.latitude_i,
|
||||
position.longitude_i, position.time, position.timestamp);
|
||||
localPosition = position;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
||||
/// Find a node in our DB, create an empty NodeInfoLite if missing
|
||||
meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n);
|
||||
|
||||
@@ -162,7 +183,7 @@ class NodeDB
|
||||
void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(), installDefaultModuleConfig();
|
||||
};
|
||||
|
||||
extern NodeDB nodeDB;
|
||||
extern NodeDB *nodeDB;
|
||||
|
||||
/*
|
||||
If is_router is set, we use a number of different default values
|
||||
@@ -186,46 +207,6 @@ extern NodeDB nodeDB;
|
||||
// Our delay functions check for this for times that should never expire
|
||||
#define NODE_DELAY_FOREVER 0xffffffff
|
||||
|
||||
#define IF_ROUTER(routerVal, normalVal) \
|
||||
((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal))
|
||||
|
||||
#define ONE_DAY 24 * 60 * 60
|
||||
|
||||
#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60)
|
||||
#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60)
|
||||
#define default_wait_bluetooth_secs IF_ROUTER(1, 60)
|
||||
#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep
|
||||
#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60)
|
||||
#define default_min_wake_secs 10
|
||||
#define default_screen_on_secs IF_ROUTER(1, 60 * 10)
|
||||
|
||||
#define default_mqtt_address "mqtt.meshtastic.org"
|
||||
#define default_mqtt_username "meshdev"
|
||||
#define default_mqtt_password "large4cats"
|
||||
#define default_mqtt_root "msh"
|
||||
|
||||
inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval)
|
||||
{
|
||||
if (configuredInterval > 0)
|
||||
return configuredInterval * 1000;
|
||||
return default_broadcast_interval_secs * 1000;
|
||||
}
|
||||
|
||||
inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval)
|
||||
{
|
||||
if (configuredInterval > 0)
|
||||
return configuredInterval * 1000;
|
||||
return defaultInterval * 1000;
|
||||
}
|
||||
|
||||
inline uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue)
|
||||
{
|
||||
if (configured > 0)
|
||||
return configured;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// Sometimes we will have Position objects that only have a time, so check for
|
||||
/// valid lat/lon
|
||||
static inline bool hasValidPosition(const meshtastic_NodeInfoLite *n)
|
||||
@@ -249,3 +230,5 @@ extern uint32_t error_address;
|
||||
(ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \
|
||||
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
|
||||
ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
|
||||
|
||||
// Please do not remove this comment, it makes trunk and compiler happy at the same time.
|
||||
@@ -2,6 +2,10 @@
|
||||
#include "configuration.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
PacketHistory::PacketHistory()
|
||||
{
|
||||
recentPackets.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#include "PhoneAPI.h"
|
||||
#include "Channels.h"
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "GPS.h"
|
||||
#endif
|
||||
|
||||
#include "Channels.h"
|
||||
#include "Default.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PhoneAPI.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "TypeConversions.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "xmodem.h"
|
||||
|
||||
@@ -17,8 +21,9 @@
|
||||
#if ToRadio_size > MAX_TO_FROM_RADIO_SIZE
|
||||
#error ToRadio is too big
|
||||
#endif
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
|
||||
PhoneAPI::PhoneAPI()
|
||||
{
|
||||
@@ -103,12 +108,21 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
|
||||
LOG_INFO("Got xmodem packet\n");
|
||||
xModem.handlePacket(toRadioScratch.xmodemPacket);
|
||||
break;
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
case meshtastic_ToRadio_mqttClientProxyMessage_tag:
|
||||
LOG_INFO("Got MqttClientProxy message\n");
|
||||
if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled) {
|
||||
if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled &&
|
||||
(channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) {
|
||||
mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage);
|
||||
} else {
|
||||
LOG_WARN("MqttClientProxy received but proxy is not enabled, no channels have up/downlink, or map reporting "
|
||||
"not enabled\n");
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case meshtastic_ToRadio_heartbeat_tag:
|
||||
LOG_DEBUG("Got client heartbeat\n");
|
||||
break;
|
||||
default:
|
||||
// Ignore nop messages
|
||||
// LOG_DEBUG("Error: unexpected ToRadio variant\n");
|
||||
@@ -413,9 +427,12 @@ bool PhoneAPI::available()
|
||||
|
||||
case STATE_SEND_NODEINFO:
|
||||
if (nodeInfoForPhone.num == 0) {
|
||||
auto nextNode = nodeDB.readNextMeshNode(readIndex);
|
||||
auto nextNode = nodeDB->readNextMeshNode(readIndex);
|
||||
if (nextNode) {
|
||||
nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode);
|
||||
nodeInfoForPhone.hops_away = nodeInfoForPhone.num == nodeDB->getNodeNum() ? 0 : nodeInfoForPhone.hops_away;
|
||||
nodeInfoForPhone.is_favorite =
|
||||
nodeInfoForPhone.is_favorite || nodeInfoForPhone.num == nodeDB->getNodeNum(); // Our node is always a favorite
|
||||
}
|
||||
}
|
||||
return true; // Always say we have something, because we might need to advance our state machine
|
||||
|
||||
@@ -56,7 +56,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
*/
|
||||
const char *getSenderShortName(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
auto node = nodeDB.getMeshNode(getFrom(&mp));
|
||||
auto node = nodeDB->getMeshNode(getFrom(&mp));
|
||||
const char *sender = (node) ? node->user.short_name : "???";
|
||||
return sender;
|
||||
}
|
||||
@@ -95,12 +95,11 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
*/
|
||||
virtual void alterReceived(meshtastic_MeshPacket &mp) override
|
||||
{
|
||||
auto &p = mp.decoded;
|
||||
|
||||
T scratch;
|
||||
T *decoded = NULL;
|
||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == ourPortNum) {
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
auto &p = mp.decoded;
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) {
|
||||
decoded = &scratch;
|
||||
} else {
|
||||
@@ -108,8 +107,8 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
// if we can't decode it, nobody can process it!
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return alterReceivedProtobuf(mp, decoded);
|
||||
return alterReceivedProtobuf(mp, decoded);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -19,7 +19,7 @@ RF95Interface::RF95Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIO
|
||||
RADIOLIB_PIN_TYPE busy)
|
||||
: RadioLibInterface(hal, cs, irq, rst, busy)
|
||||
{
|
||||
LOG_WARN("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
|
||||
LOG_DEBUG("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
|
||||
}
|
||||
|
||||
/** Some boards require GPIO control of tx vs rx paths */
|
||||
@@ -128,12 +128,18 @@ bool RF95Interface::reconfigure()
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
err = lora->setSyncWord(syncWord);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting RF95 setSyncWord!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
err = lora->setCurrentLimit(currentLimit);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting RF95 setCurrentLimit!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
err = lora->setPreambleLength(preambleLength);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting RF95 setPreambleLength!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
err = lora->setFrequency(getFreq());
|
||||
@@ -164,6 +170,8 @@ void RF95Interface::addReceiveMetadata(meshtastic_MeshPacket *mp)
|
||||
void RF95Interface::setStandby()
|
||||
{
|
||||
int err = lora->standby();
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting RF95 standby!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
isReceiving = false; // If we were receiving, not any more
|
||||
@@ -185,6 +193,8 @@ void RF95Interface::startReceive()
|
||||
setTransmitEnable(false);
|
||||
setStandby();
|
||||
int err = lora->startReceive();
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting RF95 startReceive!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
isReceiving = true;
|
||||
@@ -205,6 +215,8 @@ bool RF95Interface::isChannelActive()
|
||||
// LOG_DEBUG("Channel is busy!\n");
|
||||
return true;
|
||||
}
|
||||
if (result != RADIOLIB_CHANNEL_FREE)
|
||||
LOG_ERROR("Radiolib error %d when attempting RF95 isChannelActive!\n", result);
|
||||
assert(result != RADIOLIB_ERR_WRONG_MODEM);
|
||||
|
||||
// LOG_DEBUG("Channel is free!\n");
|
||||
|
||||
@@ -76,9 +76,12 @@ const RegionInfo regions[] = {
|
||||
RDEF(KR, 920.0f, 923.0f, 100, 0, 0, true, false, false),
|
||||
|
||||
/*
|
||||
???
|
||||
Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor.
|
||||
5.8.1 in the Low-power Radio-frequency Devices Technical Regulations
|
||||
https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF
|
||||
https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283
|
||||
*/
|
||||
RDEF(TW, 920.0f, 925.0f, 100, 0, 0, true, false, false),
|
||||
RDEF(TW, 920.0f, 925.0f, 100, 0, 27, true, false, false),
|
||||
|
||||
/*
|
||||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||||
@@ -151,10 +154,16 @@ static uint8_t bytes[MAX_RHPACKETLEN];
|
||||
void initRegion()
|
||||
{
|
||||
const RegionInfo *r = regions;
|
||||
#ifdef LORA_REGIONCODE
|
||||
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != LORA_REGIONCODE; r++)
|
||||
;
|
||||
LOG_INFO("Wanted region %d, regulatory override to %s\n", config.lora.region, r->name);
|
||||
#else
|
||||
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != config.lora.region; r++)
|
||||
;
|
||||
myRegion = r;
|
||||
LOG_INFO("Wanted region %d, using %s\n", config.lora.region, r->name);
|
||||
#endif
|
||||
myRegion = r;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,8 +343,8 @@ bool RadioInterface::init()
|
||||
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
|
||||
|
||||
// we now expect interfaces to operate in promiscuous mode
|
||||
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
|
||||
// time.
|
||||
// radioIf.setThisAddress(nodeDB->getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at
|
||||
// constructor time.
|
||||
|
||||
applyModemConfig();
|
||||
|
||||
@@ -486,7 +495,7 @@ void RadioInterface::applyModemConfig()
|
||||
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
|
||||
const char *channelName = channels.getName(channels.getPrimaryIndex());
|
||||
// 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;
|
||||
uint32_t 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 =
|
||||
@@ -564,6 +573,8 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
|
||||
h->to = p->to;
|
||||
h->id = p->id;
|
||||
h->channel = p->channel;
|
||||
h->next_hop = 0; // *** For future use ***
|
||||
h->relay_node = 0; // *** For future use ***
|
||||
if (p->hop_limit > HOP_MAX) {
|
||||
LOG_WARN("hop limit %d is too high, setting to %d\n", p->hop_limit, HOP_RELIABLE);
|
||||
p->hop_limit = HOP_RELIABLE;
|
||||
@@ -578,4 +589,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
|
||||
|
||||
sendingPacket = p;
|
||||
return p->encrypted.size + sizeof(PacketHeader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ typedef struct {
|
||||
|
||||
/** The channel hash - used as a hint for the decoder to limit which channels we consider */
|
||||
uint8_t channel;
|
||||
|
||||
// ***For future use*** Last byte of the NodeNum of the next-hop for this packet
|
||||
uint8_t next_hop;
|
||||
|
||||
// ***For future use*** Last byte of the NodeNum of the node that will relay/relayed this packet
|
||||
uint8_t relay_node;
|
||||
} PacketHeader;
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,36 @@ void LockingArduinoHal::spiEndTransaction()
|
||||
|
||||
ArduinoHal::spiEndTransaction();
|
||||
}
|
||||
#if ARCH_PORTDUINO
|
||||
void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
|
||||
{
|
||||
if (busy == RADIOLIB_NC) {
|
||||
spi->transfer(out, in, len);
|
||||
} else {
|
||||
uint16_t offset = 0;
|
||||
|
||||
while (len) {
|
||||
uint8_t block_size = (len < 20 ? len : 20);
|
||||
spi->transfer((out != NULL ? out + offset : NULL), (in != NULL ? in + offset : NULL), block_size);
|
||||
if (block_size == len)
|
||||
return;
|
||||
|
||||
// ensure GPIO is low
|
||||
|
||||
uint32_t start = millis();
|
||||
while (digitalRead(busy)) {
|
||||
if (millis() - start >= 2000) {
|
||||
LOG_ERROR("GPIO mid-transfer timeout, is it connected?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
offset += block_size;
|
||||
len -= block_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
|
||||
RADIOLIB_PIN_TYPE busy, PhysicalLayer *_iface)
|
||||
@@ -313,7 +343,7 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
// when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race
|
||||
// Condition?
|
||||
if (!isReceiving) {
|
||||
LOG_DEBUG("*** WAS_ASSERT *** handleReceiveInterrupt called when not in receive mode\n");
|
||||
LOG_ERROR("handleReceiveInterrupt called when not in receive mode, which shouldn't happen.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,21 @@
|
||||
class LockingArduinoHal : public ArduinoHal
|
||||
{
|
||||
public:
|
||||
LockingArduinoHal(SPIClass &spi, SPISettings spiSettings) : ArduinoHal(spi, spiSettings){};
|
||||
LockingArduinoHal(SPIClass &spi, SPISettings spiSettings, RADIOLIB_PIN_TYPE _busy = RADIOLIB_NC)
|
||||
: ArduinoHal(spi, spiSettings)
|
||||
{
|
||||
#if ARCH_PORTDUINO
|
||||
busy = _busy;
|
||||
#endif
|
||||
};
|
||||
|
||||
void spiBeginTransaction() override;
|
||||
void spiEndTransaction() override;
|
||||
#if ARCH_PORTDUINO
|
||||
RADIOLIB_PIN_TYPE busy;
|
||||
void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override;
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
#if defined(USE_STM32WLx)
|
||||
@@ -176,4 +187,4 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) = 0;
|
||||
|
||||
virtual void setStandby() = 0;
|
||||
};
|
||||
};
|
||||
@@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
* 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. */
|
||||
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()) {
|
||||
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
|
||||
|
||||
@@ -8,12 +8,9 @@
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "modules/RoutingModule.h"
|
||||
extern "C" {
|
||||
#include "mesh/compression/unishox2.h"
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
#include "mqtt/MQTT.h"
|
||||
|
||||
#endif
|
||||
/**
|
||||
* Router todo
|
||||
*
|
||||
@@ -119,7 +116,7 @@ meshtastic_MeshPacket *Router::allocForSending()
|
||||
meshtastic_MeshPacket *p = packetPool.allocZeroed();
|
||||
|
||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // Assume payload is decoded at start.
|
||||
p->from = nodeDB.getNodeNum();
|
||||
p->from = nodeDB->getNodeNum();
|
||||
p->to = NODENUM_BROADCAST;
|
||||
p->hop_limit = (config.lora.hop_limit >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit;
|
||||
p->id = generatePacketId();
|
||||
@@ -165,7 +162,7 @@ meshtastic_QueueStatus Router::getQueueStatus()
|
||||
ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
|
||||
{
|
||||
// No need to deliver externally if the destination is the local node
|
||||
if (p->to == nodeDB.getNodeNum()) {
|
||||
if (p->to == nodeDB->getNodeNum()) {
|
||||
printPacket("Enqueued local", p);
|
||||
enqueueReceivedMessage(p);
|
||||
return ERRNO_OK;
|
||||
@@ -182,7 +179,7 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
|
||||
}
|
||||
|
||||
if (!p->channel) { // don't override if a channel was requested
|
||||
p->channel = nodeDB.getMeshNodeChannel(p->to);
|
||||
p->channel = nodeDB->getMeshNodeChannel(p->to);
|
||||
LOG_DEBUG("localSend to channel %d\n", p->channel);
|
||||
}
|
||||
|
||||
@@ -205,7 +202,7 @@ void printBytes(const char *label, const uint8_t *p, size_t numbytes)
|
||||
*/
|
||||
ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (p->to == nodeDB.getNodeNum()) {
|
||||
if (p->to == nodeDB->getNodeNum()) {
|
||||
LOG_ERROR("BUG! send() called with packet destined for local node!\n");
|
||||
packetPool.release(p);
|
||||
return meshtastic_Routing_Error_BAD_REQUEST;
|
||||
@@ -220,7 +217,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes);
|
||||
#endif
|
||||
meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT;
|
||||
if (getFrom(p) == nodeDB.getNodeNum()) { // only send NAK to API, not to the mesh
|
||||
if (getFrom(p) == nodeDB->getNodeNum()) { // only send NAK to API, not to the mesh
|
||||
abortSendAndNak(err, p);
|
||||
} else {
|
||||
packetPool.release(p);
|
||||
@@ -261,11 +258,12 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
abortSendAndNak(encodeResult, p);
|
||||
return encodeResult; // FIXME - this isn't a valid ErrorCode
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
// Only publish to MQTT if we're the original transmitter of the packet
|
||||
if (moduleConfig.mqtt.enabled && p->from == nodeDB.getNodeNum() && mqtt) {
|
||||
if (moduleConfig.mqtt.enabled && p->from == nodeDB->getNodeNum() && mqtt) {
|
||||
mqtt->onSend(*p, *p_decoded, chIndex);
|
||||
}
|
||||
#endif
|
||||
packetPool.release(p_decoded);
|
||||
}
|
||||
|
||||
@@ -297,8 +295,8 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
|
||||
return false;
|
||||
|
||||
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
|
||||
(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);
|
||||
(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;
|
||||
}
|
||||
|
||||
@@ -331,6 +329,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
|
||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
||||
p->channel = chIndex; // change to store the index instead of the hash
|
||||
|
||||
/* Not actually ever used.
|
||||
// Decompress if needed. jm
|
||||
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) {
|
||||
// Decompress the payload
|
||||
@@ -348,7 +347,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
|
||||
|
||||
// Switch the port from PortNum_TEXT_MESSAGE_COMPRESSED_APP to PortNum_TEXT_MESSAGE_APP
|
||||
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
||||
}
|
||||
} */
|
||||
|
||||
printPacket("decoded message", p);
|
||||
return true;
|
||||
@@ -370,6 +369,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
|
||||
|
||||
/* Not actually used, so save the cycles
|
||||
// Only allow encryption on the text message app.
|
||||
// TODO: Allow modules to opt into compression.
|
||||
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
|
||||
@@ -403,7 +403,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
|
||||
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP;
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
if (numbytes > MAX_RHPACKETLEN)
|
||||
return meshtastic_Routing_Error_TOO_LARGE;
|
||||
@@ -431,7 +431,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
|
||||
NodeNum Router::getNodeNum()
|
||||
{
|
||||
return nodeDB.getNodeNum();
|
||||
return nodeDB->getNodeNum();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -465,19 +465,22 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
||||
cancelSending(p->from, p->id);
|
||||
skipHandle = true;
|
||||
}
|
||||
|
||||
// Publish received message to MQTT if we're not the original transmitter of the packet
|
||||
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);
|
||||
}
|
||||
|
||||
packetPool.release(p_encrypted); // Release the encrypted packet
|
||||
|
||||
// call modules here
|
||||
if (!skipHandle)
|
||||
MeshModule::callPlugins(*p, src);
|
||||
if (!skipHandle) {
|
||||
MeshModule::callModules(*p, src);
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
// After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
|
||||
if (decoded && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB->getNodeNum() && mqtt)
|
||||
mqtt->onSend(*p_encrypted, *p, p->channel);
|
||||
#endif
|
||||
}
|
||||
|
||||
packetPool.release(p_encrypted); // Release the encrypted packet
|
||||
}
|
||||
|
||||
void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
|
||||
|
||||
@@ -17,7 +17,7 @@ SX126xInterface<T>::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs
|
||||
RADIOLIB_PIN_TYPE busy)
|
||||
: RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
|
||||
{
|
||||
LOG_WARN("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
|
||||
LOG_DEBUG("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
|
||||
}
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
@@ -181,12 +181,18 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
err = lora.setSyncWord(syncWord);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX126X setSyncWord!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
err = lora.setCurrentLimit(currentLimit);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX126X setCurrentLimit!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
err = lora.setPreambleLength(preambleLength);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX126X setPreambleLength!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
err = lora.setFrequency(getFreq());
|
||||
@@ -197,6 +203,8 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
|
||||
power = SX126X_MAX_POWER;
|
||||
|
||||
err = lora.setOutputPower(power);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX126X setOutputPower!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
startReceive(); // restart receiving
|
||||
@@ -215,10 +223,8 @@ template <typename T> void SX126xInterface<T>::setStandby()
|
||||
|
||||
int err = lora.standby();
|
||||
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_DEBUG("SX126x standby failed with error %d\n", err);
|
||||
}
|
||||
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
isReceiving = false; // If we were receiving, not any more
|
||||
@@ -260,6 +266,8 @@ template <typename T> void SX126xInterface<T>::startReceive()
|
||||
int err = lora.startReceiveDutyCycleAuto(preambleLength, 8,
|
||||
RADIOLIB_SX126X_IRQ_RX_DEFAULT | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED |
|
||||
RADIOLIB_SX126X_IRQ_HEADER_VALID);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
isReceiving = true;
|
||||
@@ -279,7 +287,8 @@ template <typename T> bool SX126xInterface<T>::isChannelActive()
|
||||
result = lora.scanChannel();
|
||||
if (result == RADIOLIB_LORA_DETECTED)
|
||||
return true;
|
||||
|
||||
if (result != RADIOLIB_CHANNEL_FREE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX126X scanChannel!\n", result);
|
||||
assert(result != RADIOLIB_ERR_WRONG_MODEM);
|
||||
|
||||
return false;
|
||||
|
||||
@@ -17,7 +17,7 @@ SX128xInterface<T>::SX128xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs
|
||||
RADIOLIB_PIN_TYPE busy)
|
||||
: RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
|
||||
{
|
||||
LOG_WARN("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
|
||||
LOG_DEBUG("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
|
||||
}
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
@@ -71,7 +71,7 @@ template <typename T> bool SX128xInterface<T>::init()
|
||||
if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) {
|
||||
LOG_WARN("Radio chip only supports 2.4GHz LoRa. Adjusting Region and rebooting.\n");
|
||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24;
|
||||
nodeDB.saveToDisk(SEGMENT_CONFIG);
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
delay(2000);
|
||||
#if defined(ARCH_ESP32)
|
||||
ESP.restart();
|
||||
@@ -126,9 +126,13 @@ template <typename T> bool SX128xInterface<T>::reconfigure()
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
err = lora.setSyncWord(syncWord);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX128X setSyncWord!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
err = lora.setPreambleLength(preambleLength);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX128X setPreambleLength!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
err = lora.setFrequency(getFreq());
|
||||
@@ -139,6 +143,8 @@ template <typename T> bool SX128xInterface<T>::reconfigure()
|
||||
power = SX128X_MAX_POWER;
|
||||
|
||||
err = lora.setOutputPower(power);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX128X setOutputPower!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
startReceive(); // restart receiving
|
||||
@@ -162,10 +168,8 @@ template <typename T> void SX128xInterface<T>::setStandby()
|
||||
|
||||
int err = lora.standby();
|
||||
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("SX128x standby failed with error %d\n", err);
|
||||
}
|
||||
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
#if ARCH_PORTDUINO
|
||||
if (settingsMap[rxen] != RADIOLIB_NC) {
|
||||
@@ -251,10 +255,12 @@ template <typename T> void SX128xInterface<T>::startReceive()
|
||||
#endif
|
||||
|
||||
// We use the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving
|
||||
int err = lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT |
|
||||
RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED |
|
||||
RADIOLIB_SX128X_IRQ_HEADER_VALID);
|
||||
int err =
|
||||
lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT | RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED |
|
||||
RADIOLIB_SX128X_IRQ_HEADER_VALID);
|
||||
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
isReceiving = true;
|
||||
@@ -274,7 +280,8 @@ template <typename T> bool SX128xInterface<T>::isChannelActive()
|
||||
result = lora.scanChannel();
|
||||
if (result == RADIOLIB_LORA_DETECTED)
|
||||
return true;
|
||||
|
||||
if (result != RADIOLIB_CHANNEL_FREE)
|
||||
LOG_ERROR("Radiolib error %d when attempting SX128X scanChannel!\n", result);
|
||||
assert(result != RADIOLIB_ERR_WRONG_MODEM);
|
||||
|
||||
return false;
|
||||
@@ -327,4 +334,4 @@ template <typename T> bool SX128xInterface<T>::sleep()
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
27
src/mesh/Throttle.cpp
Normal file
27
src/mesh/Throttle.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "Throttle.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
/// @brief Execute a function throttled to a minimum interval
|
||||
/// @param lastExecutionMs Pointer to the last execution time in milliseconds
|
||||
/// @param minumumIntervalMs Minimum execution interval in milliseconds
|
||||
/// @param throttleFunc Function to execute if the execution is not deferred
|
||||
/// @param onDefer Default to NULL, execute the function if the execution is deferred
|
||||
/// @return true if the function was executed, false if it was deferred
|
||||
bool Throttle::execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*throttleFunc)(void), void (*onDefer)(void))
|
||||
{
|
||||
if (*lastExecutionMs == 0) {
|
||||
*lastExecutionMs = millis();
|
||||
throttleFunc();
|
||||
return true;
|
||||
}
|
||||
uint32_t now = millis();
|
||||
|
||||
if ((now - *lastExecutionMs) >= minumumIntervalMs) {
|
||||
throttleFunc();
|
||||
*lastExecutionMs = now;
|
||||
return true;
|
||||
} else if (onDefer != NULL) {
|
||||
onDefer();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
9
src/mesh/Throttle.h
Normal file
9
src/mesh/Throttle.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
class Throttle
|
||||
{
|
||||
public:
|
||||
static bool execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*func)(void), void (*onDefer)(void) = NULL);
|
||||
};
|
||||
@@ -12,6 +12,7 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo
|
||||
info.channel = lite->channel;
|
||||
info.via_mqtt = lite->via_mqtt;
|
||||
info.hops_away = lite->hops_away;
|
||||
info.is_favorite = lite->is_favorite;
|
||||
|
||||
if (lite->has_position) {
|
||||
info.has_position = true;
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "concurrency/Periodic.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "mesh/api/ethServerAPI.h"
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
#include "target_specific.h"
|
||||
#include <RAK13800_W5100S.h>
|
||||
#include <SPI.h>
|
||||
@@ -66,11 +69,12 @@ static int32_t reconnectETH()
|
||||
|
||||
ethStartupComplete = true;
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
// FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected'
|
||||
if (mqtt && !moduleConfig.mqtt.proxy_to_client_enabled && !mqtt->isConnectedDirectly()) {
|
||||
mqtt->reconnect();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef DISABLE_NTP
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated nanopb constant definitions */
|
||||
/* Generated by nanopb-0.4.7 */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#include "meshtastic/admin.pb.h"
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated nanopb header */
|
||||
/* Generated by nanopb-0.4.7 */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#ifndef PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED
|
||||
#define PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "meshtastic/channel.pb.h"
|
||||
#include "meshtastic/config.pb.h"
|
||||
#include "meshtastic/connection_status.pb.h"
|
||||
#include "meshtastic/deviceonly.pb.h"
|
||||
#include "meshtastic/mesh.pb.h"
|
||||
#include "meshtastic/module_config.pb.h"
|
||||
|
||||
@@ -154,6 +153,14 @@ typedef struct _meshtastic_AdminMessage {
|
||||
char set_ringtone_message[231];
|
||||
/* Remove the node by the specified node-num from the NodeDB on the device */
|
||||
uint32_t remove_by_nodenum;
|
||||
/* Set specified node-num to be favorited on the NodeDB on the device */
|
||||
uint32_t set_favorite_node;
|
||||
/* Set specified node-num to be un-favorited on the NodeDB on the device */
|
||||
uint32_t remove_favorite_node;
|
||||
/* Set fixed position data on the node and then set the position.fixed_position = true */
|
||||
meshtastic_Position set_fixed_position;
|
||||
/* Clear fixed position coordinates and then set position.fixed_position = false */
|
||||
bool remove_fixed_position;
|
||||
/* Begins an edit transaction for config, module config, owner, and channel settings changes
|
||||
This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
|
||||
bool begin_edit_settings;
|
||||
@@ -238,6 +245,10 @@ extern "C" {
|
||||
#define meshtastic_AdminMessage_set_canned_message_module_messages_tag 36
|
||||
#define meshtastic_AdminMessage_set_ringtone_message_tag 37
|
||||
#define meshtastic_AdminMessage_remove_by_nodenum_tag 38
|
||||
#define meshtastic_AdminMessage_set_favorite_node_tag 39
|
||||
#define meshtastic_AdminMessage_remove_favorite_node_tag 40
|
||||
#define meshtastic_AdminMessage_set_fixed_position_tag 41
|
||||
#define meshtastic_AdminMessage_remove_fixed_position_tag 42
|
||||
#define meshtastic_AdminMessage_begin_edit_settings_tag 64
|
||||
#define meshtastic_AdminMessage_commit_edit_settings_tag 65
|
||||
#define meshtastic_AdminMessage_reboot_ota_seconds_tag 95
|
||||
@@ -277,6 +288,10 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_module_config,set_module
|
||||
X(a, STATIC, ONEOF, STRING, (payload_variant,set_canned_message_module_messages,set_canned_message_module_messages), 36) \
|
||||
X(a, STATIC, ONEOF, STRING, (payload_variant,set_ringtone_message,set_ringtone_message), 37) \
|
||||
X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_by_nodenum,remove_by_nodenum), 38) \
|
||||
X(a, STATIC, ONEOF, UINT32, (payload_variant,set_favorite_node,set_favorite_node), 39) \
|
||||
X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_favorite_node,remove_favorite_node), 40) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed_position), 41) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \
|
||||
X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \
|
||||
@@ -299,6 +314,7 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset),
|
||||
#define meshtastic_AdminMessage_payload_variant_set_channel_MSGTYPE meshtastic_Channel
|
||||
#define meshtastic_AdminMessage_payload_variant_set_config_MSGTYPE meshtastic_Config
|
||||
#define meshtastic_AdminMessage_payload_variant_set_module_config_MSGTYPE meshtastic_ModuleConfig
|
||||
#define meshtastic_AdminMessage_payload_variant_set_fixed_position_MSGTYPE meshtastic_Position
|
||||
|
||||
#define meshtastic_HamParameters_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, STRING, call_sign, 1) \
|
||||
@@ -324,6 +340,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg;
|
||||
#define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size
|
||||
#define meshtastic_AdminMessage_size 500
|
||||
#define meshtastic_HamParameters_size 32
|
||||
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated nanopb constant definitions */
|
||||
/* Generated by nanopb-0.4.7 */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#include "meshtastic/apponly.pb.h"
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated nanopb header */
|
||||
/* Generated by nanopb-0.4.7 */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#ifndef PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED
|
||||
#define PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED
|
||||
@@ -54,7 +54,8 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
|
||||
#define meshtastic_ChannelSet_fields &meshtastic_ChannelSet_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define meshtastic_ChannelSet_size 658
|
||||
#define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
|
||||
#define meshtastic_ChannelSet_size 674
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated nanopb constant definitions */
|
||||
/* Generated by nanopb-0.4.7 */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#include "meshtastic/atak.pb.h"
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated nanopb header */
|
||||
/* Generated by nanopb-0.4.7 */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#ifndef PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED
|
||||
#define PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED
|
||||
@@ -260,6 +260,7 @@ extern const pb_msgdesc_t meshtastic_PLI_msg;
|
||||
#define meshtastic_PLI_fields &meshtastic_PLI_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacket_size
|
||||
#define meshtastic_Contact_size 242
|
||||
#define meshtastic_GeoChat_size 323
|
||||
#define meshtastic_Group_size 4
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated nanopb constant definitions */
|
||||
/* Generated by nanopb-0.4.7 */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#include "meshtastic/cannedmessages.pb.h"
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Automatically generated nanopb header */
|
||||
/* Generated by nanopb-0.4.7 */
|
||||
/* Generated by nanopb-0.4.8 */
|
||||
|
||||
#ifndef PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED
|
||||
#define PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED
|
||||
@@ -40,6 +40,7 @@ extern const pb_msgdesc_t meshtastic_CannedMessageModuleConfig_msg;
|
||||
#define meshtastic_CannedMessageModuleConfig_fields &meshtastic_CannedMessageModuleConfig_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_MAX_SIZE meshtastic_CannedMessageModuleConfig_size
|
||||
#define meshtastic_CannedMessageModuleConfig_size 203
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user