mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-14 14:52:32 +00:00
Compare commits
304 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dd50df810 | ||
|
|
14c4022c18 | ||
|
|
a5d7bacdbf | ||
|
|
0b3c25f6d9 | ||
|
|
ad7a474a52 | ||
|
|
430186ec53 | ||
|
|
227c6fc27e | ||
|
|
a37844d7e5 | ||
|
|
ff20b29c3c | ||
|
|
64c29c4a35 | ||
|
|
d4df3f8a7e | ||
|
|
a16c3af30a | ||
|
|
3061860dab | ||
|
|
2450d98b59 | ||
|
|
a371592ad9 | ||
|
|
f7aaf48ae9 | ||
|
|
1fb604ebc8 | ||
|
|
df2733a3b5 | ||
|
|
8fd3cb1aac | ||
|
|
485c476f17 | ||
|
|
7dd4ce32d2 | ||
|
|
7f12af73d4 | ||
|
|
63113d57b3 | ||
|
|
32850ff39d | ||
|
|
2901f773a4 | ||
|
|
a7c54e4ad7 | ||
|
|
c2a1141dfa | ||
|
|
5b4472ab56 | ||
|
|
3262f732d8 | ||
|
|
cff21ca130 | ||
|
|
81ce04d3da | ||
|
|
f4d2b10840 | ||
|
|
2370cb8aac | ||
|
|
59ec87f5b0 | ||
|
|
0d9481b6ea | ||
|
|
8f0105ccd9 | ||
|
|
05ca3c3d56 | ||
|
|
ba549d8fcd | ||
|
|
b9df2c00fa | ||
|
|
d9dcb33576 | ||
|
|
f698231be7 | ||
|
|
8414f4a6a3 | ||
|
|
f3b93d55fb | ||
|
|
2b373048c6 | ||
|
|
b32e3f1269 | ||
|
|
68ddb712f5 | ||
|
|
2fb5cd8c1c | ||
|
|
79aea8231f | ||
|
|
b0837c10c6 | ||
|
|
cd811951b1 | ||
|
|
df2976dad0 | ||
|
|
4ccbe6ff71 | ||
|
|
038ddb887f | ||
|
|
9134faaed1 | ||
|
|
649a120fe0 | ||
|
|
4db0c4a563 | ||
|
|
3b2f5fa5e3 | ||
|
|
97adb598b6 | ||
|
|
7ef2cc8623 | ||
|
|
b41a32c6b6 | ||
|
|
1ebd7b0c3e | ||
|
|
5457541244 | ||
|
|
02e3438d5e | ||
|
|
02b1ece6ac | ||
|
|
9fdef366f7 | ||
|
|
284816229e | ||
|
|
10008d4eef | ||
|
|
58cfd1317c | ||
|
|
3d3f7869d4 | ||
|
|
ef325289eb | ||
|
|
40c63c0615 | ||
|
|
a9de8b9bb3 | ||
|
|
66a7f896c8 | ||
|
|
45a36f5571 | ||
|
|
876d32c9ee | ||
|
|
b9ce75b09c | ||
|
|
62493efc40 | ||
|
|
2848b76cc9 | ||
|
|
ef8bea478d | ||
|
|
a8e4bbbe65 | ||
|
|
9a414d9c77 | ||
|
|
48e6a60a07 | ||
|
|
ca48079545 | ||
|
|
76b4be3b87 | ||
|
|
d39cc3d57b | ||
|
|
b17a8d7a6a | ||
|
|
3d21794039 | ||
|
|
beac614e65 | ||
|
|
87f2673fc4 | ||
|
|
999b292717 | ||
|
|
c16acb904e | ||
|
|
5b777219be | ||
|
|
32ea11d2af | ||
|
|
8330c3270e | ||
|
|
0c8e0efed2 | ||
|
|
c44d8a0433 | ||
|
|
49b4ed2a89 | ||
|
|
db8faa9faf | ||
|
|
4b9ea4f808 | ||
|
|
c3beca3e23 | ||
|
|
95cb6b06e4 | ||
|
|
c46a884558 | ||
|
|
2044427e97 | ||
|
|
514ebdf013 | ||
|
|
10f64590a9 | ||
|
|
4a70ba1f7a | ||
|
|
dd6a402ea0 | ||
|
|
bed7d8a619 | ||
|
|
6d178ebc91 | ||
|
|
f75a256631 | ||
|
|
4f659b7563 | ||
|
|
1b6e8e36d3 | ||
|
|
7a4b8cde11 | ||
|
|
113859e791 | ||
|
|
a6b82ccfd9 | ||
|
|
e8b8ec69f1 | ||
|
|
023f1c24fb | ||
|
|
f00d07baa3 | ||
|
|
62c228b986 | ||
|
|
1a3cc40c7e | ||
|
|
bdcd5c3981 | ||
|
|
fc82e872d6 | ||
|
|
dffcea1f4d | ||
|
|
b47c54b5b6 | ||
|
|
c0c83ad389 | ||
|
|
23aecbdc38 | ||
|
|
eca7242a1f | ||
|
|
ef899425b8 | ||
|
|
269f90c510 | ||
|
|
7a5832ab8a | ||
|
|
044cc26340 | ||
|
|
4ccd03623f | ||
|
|
7854a22fbf | ||
|
|
943d5cb08d | ||
|
|
7480eb1826 | ||
|
|
c32c97c389 | ||
|
|
ef146fc0b5 | ||
|
|
f6861a8fe2 | ||
|
|
736642455f | ||
|
|
3c1c11e439 | ||
|
|
b072eec4ac | ||
|
|
ff9b49ddaa | ||
|
|
b8863c8a07 | ||
|
|
71cdbb1a73 | ||
|
|
200aa27cc0 | ||
|
|
f7752e4f9d | ||
|
|
7f0e8a8d6b | ||
|
|
1f6877606f | ||
|
|
1907873831 | ||
|
|
bacc6caf04 | ||
|
|
56d4250197 | ||
|
|
d66cede7fc | ||
|
|
f7ffd196e3 | ||
|
|
3a638090a2 | ||
|
|
4342ae74fb | ||
|
|
5a7962896d | ||
|
|
cfb9a600e4 | ||
|
|
7f3217d69e | ||
|
|
d94be0f534 | ||
|
|
17a3e6e975 | ||
|
|
423cbc2c6d | ||
|
|
124a82888d | ||
|
|
fec7a6bf17 | ||
|
|
bc50b39a3b | ||
|
|
158e3edbe7 | ||
|
|
116fe6d109 | ||
|
|
6a4ef7e1d1 | ||
|
|
a0fd83428f | ||
|
|
e5d4fbb164 | ||
|
|
154dd3990c | ||
|
|
78fe41710b | ||
|
|
1f38404e60 | ||
|
|
848760e5bf | ||
|
|
5ebac0cd54 | ||
|
|
9b4079317b | ||
|
|
0f64332f93 | ||
|
|
fd62edbcab | ||
|
|
46abb9ae3f | ||
|
|
b5361ef89f | ||
|
|
bf808f57fe | ||
|
|
648589ed16 | ||
|
|
28ec0e310d | ||
|
|
956d9e96f2 | ||
|
|
266ba03bb7 | ||
|
|
04c54840f4 | ||
|
|
db33200468 | ||
|
|
d7fbcf89bf | ||
|
|
a8b1bc735a | ||
|
|
ba8c640d6e | ||
|
|
d88d2780f4 | ||
|
|
b0bbf95b03 | ||
|
|
7ca150bf07 | ||
|
|
9c7aa02db8 | ||
|
|
6f444ed4b5 | ||
|
|
28119bf1bf | ||
|
|
7fdb5b594d | ||
|
|
6013fceb10 | ||
|
|
2996c7c8e2 | ||
|
|
d1c3078698 | ||
|
|
3e5f81bf2a | ||
|
|
772d045166 | ||
|
|
1a064a4666 | ||
|
|
e81c89dcae | ||
|
|
1f36139e99 | ||
|
|
6f77244af3 | ||
|
|
05351ce3e4 | ||
|
|
a79aff4778 | ||
|
|
86d6f88787 | ||
|
|
5fbeaee0b0 | ||
|
|
fed4dfd410 | ||
|
|
0d3b8bdb22 | ||
|
|
3c4f56f3bf | ||
|
|
d3c00584a2 | ||
|
|
ce7fa65595 | ||
|
|
9c0a0ad220 | ||
|
|
22d0ef36b8 | ||
|
|
829c5f493c | ||
|
|
2d8eb8e205 | ||
|
|
52a3927585 | ||
|
|
b39f6c96bd | ||
|
|
a96c2e0eac | ||
|
|
10b24c0269 | ||
|
|
4140883684 | ||
|
|
3352fae64c | ||
|
|
55cb0c52ee | ||
|
|
626d82614c | ||
|
|
d890068acb | ||
|
|
468ad39a94 | ||
|
|
5cbe06c2b0 | ||
|
|
3412ecfe7b | ||
|
|
adb16a334c | ||
|
|
377f0bda5d | ||
|
|
51ab853658 | ||
|
|
464a42258f | ||
|
|
9e9c50e6d8 | ||
|
|
945f726b65 | ||
|
|
7c44daf8f4 | ||
|
|
c57a9a8613 | ||
|
|
3c0429deee | ||
|
|
5d8f541e70 | ||
|
|
bd126b866c | ||
|
|
036a1991b8 | ||
|
|
6e3b22c624 | ||
|
|
82fbedbf41 | ||
|
|
0929b86d62 | ||
|
|
65fc1cf4a6 | ||
|
|
6f753799fd | ||
|
|
4d72afebe6 | ||
|
|
b1643e6036 | ||
|
|
362d8cb831 | ||
|
|
b203c95dd1 | ||
|
|
8d4672964c | ||
|
|
6e5e5822aa | ||
|
|
5fb0bf2575 | ||
|
|
9af2045dc1 | ||
|
|
9624cc3798 | ||
|
|
3541228c1f | ||
|
|
cc95361fdc | ||
|
|
9b1d1ad0a5 | ||
|
|
7e467f1466 | ||
|
|
d3e28e3e2c | ||
|
|
1a1a0fbfbe | ||
|
|
91305c2c84 | ||
|
|
48dd6d388d | ||
|
|
64710a6a04 | ||
|
|
c83ff03d66 | ||
|
|
73b47a78aa | ||
|
|
493b25f23e | ||
|
|
004f1f625b | ||
|
|
fc20f658e6 | ||
|
|
8e988cc926 | ||
|
|
8c240b59f6 | ||
|
|
f847e30a3c | ||
|
|
7050ae4ba1 | ||
|
|
3e64d8439d | ||
|
|
435c955acd | ||
|
|
c656a95a84 | ||
|
|
27ad8472c1 | ||
|
|
3fcd4a61aa | ||
|
|
c6d93d1a28 | ||
|
|
b0c82dcb5b | ||
|
|
7d4058f49d | ||
|
|
31fc8fafec | ||
|
|
313cee9a3f | ||
|
|
066d9d48a4 | ||
|
|
16de4a0d2e | ||
|
|
621fcb598e | ||
|
|
b2a6a4000b | ||
|
|
f5c939fb10 | ||
|
|
e508306395 | ||
|
|
d859700497 | ||
|
|
f129b458ad | ||
|
|
92df77f228 | ||
|
|
46ba36511a | ||
|
|
f3d38d84c9 | ||
|
|
f8bb6bbcb4 | ||
|
|
e0d5b9dce1 | ||
|
|
94e4b30125 | ||
|
|
2a067e7f6b | ||
|
|
97ab07e05c | ||
|
|
b8d39845cf | ||
|
|
fefd3d78f3 | ||
|
|
6a475d8288 | ||
|
|
c629b94333 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -23,4 +23,4 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
pip install -U adafruit-nrfutil
|
pip install -U adafruit-nrfutil
|
||||||
- name: Build
|
- name: Build
|
||||||
run: platformio run -e tbeam -e heltec -e lora-relay-v1
|
run: platformio run -e tbeam -e heltec -e lora-relay-v1 -e linux
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -48,13 +48,17 @@
|
|||||||
"optional": "cpp",
|
"optional": "cpp",
|
||||||
"string_view": "cpp",
|
"string_view": "cpp",
|
||||||
"cassert": "cpp",
|
"cassert": "cpp",
|
||||||
"iterator": "cpp"
|
"iterator": "cpp",
|
||||||
|
"shared_mutex": "cpp"
|
||||||
},
|
},
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"Blox",
|
"Blox",
|
||||||
|
"EINK",
|
||||||
"HFSR",
|
"HFSR",
|
||||||
"Meshtastic",
|
"Meshtastic",
|
||||||
"NEMAGPS",
|
"NEMAGPS",
|
||||||
|
"NMEAGPS",
|
||||||
|
"RDEF",
|
||||||
"Ublox",
|
"Ublox",
|
||||||
"bkpt",
|
"bkpt",
|
||||||
"cfsr",
|
"cfsr",
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -25,7 +25,7 @@ We currently support three models of radios.
|
|||||||
- TTGO T-Beam (usually the recommended choice)
|
- TTGO T-Beam (usually the recommended choice)
|
||||||
- [T-Beam V1.1 w/ NEO-6M - special Meshtastic version](https://www.aliexpress.com/item/4001178678568.html) (Includes built-in OLED display and they have **preinstalled** the meshtastic software)
|
- [T-Beam V1.1 w/ NEO-6M - special Meshtastic version](https://www.aliexpress.com/item/4001178678568.html) (Includes built-in OLED display and they have **preinstalled** the meshtastic software)
|
||||||
- [T-Beam V1.1 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (slightly better GPS)
|
- [T-Beam V1.1 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (slightly better GPS)
|
||||||
- [T-Beam V1.1 w/ NEO-M8N /w SX1262](https://de.aliexpress.com/item/4001287221970.html) (slightly better GPS + LoRa)
|
- [T-Beam V1.1 w/ NEO-M8N /w SX1262](https://www.aliexpress.com/item/4001287221970.html) (slightly better GPS + LoRa)
|
||||||
- board labels "TTGO T22_V1.1 20191212"
|
- board labels "TTGO T22_V1.1 20191212"
|
||||||
- [T-Beam V0.7 w/ NEO-6M](https://www.aliexpress.com/item/4000574335430.html) (will work but **you must use the tbeam0.7 firmware ** - but the T-Beam V1.0 or later are better!)
|
- [T-Beam V0.7 w/ NEO-6M](https://www.aliexpress.com/item/4000574335430.html) (will work but **you must use the tbeam0.7 firmware ** - but the T-Beam V1.0 or later are better!)
|
||||||
- board labels "TTGO T22_V07 20180711"
|
- board labels "TTGO T22_V07 20180711"
|
||||||
@@ -103,10 +103,10 @@ Hard resetting via RTS pin...
|
|||||||
```
|
```
|
||||||
|
|
||||||
5. cd into the directory where the release zip file was expanded.
|
5. cd into the directory where the release zip file was expanded.
|
||||||
6. Install the correct firmware for your board with `device-install.sh firmware-_board_-_country_.bin`.
|
6. Install the correct firmware for your board with `device-install.sh -f firmware-_board_-_country_.bin`.
|
||||||
- Example: `./device-install.sh firmware-HELTEC-US-0.0.3.bin`.
|
- Example: `./device-install.sh -f firmware-HELTEC-US-0.0.3.bin`.
|
||||||
7. To update run `device-update.sh firmware-_board_-_country_.bin`
|
7. To update run `device-update.sh -f firmware-_board_-_country_.bin`
|
||||||
- Example: `./device-update.sh firmware-HELTEC-US-0.0.3.bin`.
|
- Example: `./device-update.sh -f firmware-HELTEC-US-0.0.3.bin`.
|
||||||
|
|
||||||
Note: If you have previously installed meshtastic, you don't need to run this full script instead just run `esptool.py --baud 921600 write_flash 0x10000 firmware-_board_-_country_-_version_.bin`. This will be faster, also all of your current preferences will be preserved.
|
Note: If you have previously installed meshtastic, you don't need to run this full script instead just run `esptool.py --baud 921600 write_flash 0x10000 firmware-_board_-_country_-_version_.bin`. This will be faster, also all of your current preferences will be preserved.
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,15 @@ set -e
|
|||||||
|
|
||||||
source bin/version.sh
|
source bin/version.sh
|
||||||
|
|
||||||
COUNTRIES="US EU433 EU865 CN JP"
|
COUNTRIES="US EU433 EU865 CN JP ANZ KR"
|
||||||
#COUNTRIES=US
|
#COUNTRIES=US
|
||||||
#COUNTRIES=CN
|
#COUNTRIES=CN
|
||||||
|
|
||||||
BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
|
BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
|
||||||
|
# BOARDS_ESP32=tbeam
|
||||||
|
|
||||||
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
|
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
|
||||||
BOARDS_NRF52="lora-relay-v1"
|
BOARDS_NRF52="lora-relay-v1"
|
||||||
BOARDS="$BOARDS_ESP32 $BOARDS_NRF52"
|
|
||||||
#BOARDS=tbeam
|
|
||||||
|
|
||||||
OUTDIR=release/latest
|
OUTDIR=release/latest
|
||||||
|
|
||||||
@@ -22,22 +21,61 @@ ARCHIVEDIR=release/archive
|
|||||||
|
|
||||||
rm -f $OUTDIR/firmware*
|
rm -f $OUTDIR/firmware*
|
||||||
|
|
||||||
mkdir -p $OUTDIR/bins $OUTDIR/elfs
|
mkdir -p $OUTDIR/bins
|
||||||
rm -f $OUTDIR/bins/*
|
rm -r $OUTDIR/bins/*
|
||||||
|
mkdir -p $OUTDIR/bins/universal $OUTDIR/elfs/universal
|
||||||
|
|
||||||
# build the named environment and copy the bins to the release directory
|
# build the named environment and copy the bins to the release directory
|
||||||
function do_build {
|
function do_build() {
|
||||||
echo "Building for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
BOARD=$1
|
||||||
|
COUNTRY=$2
|
||||||
|
isNrf=$3
|
||||||
|
|
||||||
|
echo "Building $COUNTRY for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
||||||
rm -f .pio/build/$BOARD/firmware.*
|
rm -f .pio/build/$BOARD/firmware.*
|
||||||
|
|
||||||
# The shell vars the build tool expects to find
|
# The shell vars the build tool expects to find
|
||||||
export HW_VERSION="1.0-$COUNTRY"
|
|
||||||
export APP_VERSION=$VERSION
|
export APP_VERSION=$VERSION
|
||||||
export COUNTRY
|
|
||||||
|
# Are we building a universal/regionless rom?
|
||||||
|
if [ "x$COUNTRY" != "x" ]
|
||||||
|
then
|
||||||
|
export HW_VERSION="1.0-$COUNTRY"
|
||||||
|
export COUNTRY
|
||||||
|
basename=firmware-$BOARD-$COUNTRY-$VERSION
|
||||||
|
else
|
||||||
|
export HW_VERSION="1.0"
|
||||||
|
unset COUNTRY
|
||||||
|
basename=universal/firmware-$BOARD-$VERSION
|
||||||
|
fi
|
||||||
|
|
||||||
pio run --jobs 4 --environment $BOARD # -v
|
pio run --jobs 4 --environment $BOARD # -v
|
||||||
SRCELF=.pio/build/$BOARD/firmware.elf
|
SRCELF=.pio/build/$BOARD/firmware.elf
|
||||||
cp $SRCELF $OUTDIR/elfs/firmware-$BOARD-$COUNTRY-$VERSION.elf
|
cp $SRCELF $OUTDIR/elfs/$basename.elf
|
||||||
|
|
||||||
|
if [ "$isNrf" = "false" ]
|
||||||
|
then
|
||||||
|
echo "Copying ESP32 bin file"
|
||||||
|
SRCBIN=.pio/build/$BOARD/firmware.bin
|
||||||
|
cp $SRCBIN $OUTDIR/bins/$basename.bin
|
||||||
|
else
|
||||||
|
echo "Generating NRF52 uf2 file"
|
||||||
|
SRCHEX=.pio/build/$BOARD/firmware.hex
|
||||||
|
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/bins/$basename.uf2 -f 0xADA52840
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_boards() {
|
||||||
|
declare boards=$1
|
||||||
|
declare isNrf=$2
|
||||||
|
for board in $boards; do
|
||||||
|
for country in $COUNTRIES; do
|
||||||
|
do_build $board $country "$isNrf"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Build universal
|
||||||
|
do_build $board "" "$isNrf"
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Make sure our submodules are current
|
# Make sure our submodules are current
|
||||||
@@ -46,26 +84,16 @@ git submodule update
|
|||||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||||
platformio lib update
|
platformio lib update
|
||||||
|
|
||||||
for COUNTRY in $COUNTRIES; do
|
do_boards "$BOARDS_ESP32" "false"
|
||||||
for BOARD in $BOARDS; do
|
do_boards "$BOARDS_NRF52" "true"
|
||||||
do_build $BOARD
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Copying ESP32 bin files"
|
|
||||||
for BOARD in $BOARDS_ESP32; do
|
|
||||||
SRCBIN=.pio/build/$BOARD/firmware.bin
|
|
||||||
cp $SRCBIN $OUTDIR/bins/firmware-$BOARD-$COUNTRY-$VERSION.bin
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Generating NRF52 uf2 files"
|
|
||||||
for BOARD in $BOARDS_NRF52; do
|
|
||||||
SRCHEX=.pio/build/$BOARD/firmware.hex
|
|
||||||
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/bins/firmware-$BOARD-$COUNTRY-$VERSION.uf2 -f 0xADA52840
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
# keep the bins in archive also
|
# keep the bins in archive also
|
||||||
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $ARCHIVEDIR
|
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $OUTDIR/bins/universal/firmware* $OUTDIR/elfs/universal/firmware* $ARCHIVEDIR
|
||||||
|
|
||||||
|
echo Updating android bins $OUTDIR/forandroid
|
||||||
|
rm -rf $OUTDIR/forandroid
|
||||||
|
mkdir -p $OUTDIR/forandroid
|
||||||
|
cp -a $OUTDIR/bins/universal/*.bin $OUTDIR/forandroid/
|
||||||
|
|
||||||
cat >$OUTDIR/curfirmwareversion.xml <<XML
|
cat >$OUTDIR/curfirmwareversion.xml <<XML
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
@@ -79,6 +107,7 @@ Generated by bin/buildall.sh -->
|
|||||||
</resources>
|
</resources>
|
||||||
XML
|
XML
|
||||||
|
|
||||||
|
echo Generating $ARCHIVEDIR/firmware-$VERSION.zip
|
||||||
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
||||||
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
|
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
|
||||||
|
|
||||||
|
|||||||
22
bin/install-bootloader.sh
Executable file
22
bin/install-bootloader.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
# You probably don't want to use this script, it programs a custom bootloader build onto a nrf52 board
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader
|
||||||
|
|
||||||
|
nrfjprog --eraseall -f nrf52
|
||||||
|
|
||||||
|
# this generates an intel hex file that can be programmed into a NRF52 to tell the adafruit bootloader that the current app image is valid
|
||||||
|
# Bootloader settings are at BOOTLOADER_SETTINGS (rw) : ORIGIN = 0xFF000, LENGTH = 0x1000
|
||||||
|
# first 4 bytes should be 0x01 to indicate valid app image
|
||||||
|
# second 4 bytes should be 0x00 to indicate no CRC required for image
|
||||||
|
echo "01 00 00 00 00 00 00 00" | xxd -r -p - >/tmp/bootconf.bin
|
||||||
|
srec_cat /tmp/bootconf.bin -binary -offset 0xff000 -output /tmp/bootconf.hex -intel
|
||||||
|
|
||||||
|
echo Generating merged hex file
|
||||||
|
mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-124-g69bd8eb-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex
|
||||||
|
|
||||||
|
echo Telling bootloader app region is valid and telling CPU to run
|
||||||
|
nrfjprog --program ttgo_eink_full.hex -f nrf52 --reset
|
||||||
|
|
||||||
|
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
||||||
22
bin/install-eink.sh
Executable file
22
bin/install-eink.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
# You probably don't want to use this script, it programs a custom bootloader build onto a nrf52 board
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader
|
||||||
|
|
||||||
|
nrfjprog --eraseall -f nrf52
|
||||||
|
|
||||||
|
# this generates an intel hex file that can be programmed into a NRF52 to tell the adafruit bootloader that the current app image is valid
|
||||||
|
# Bootloader settings are at BOOTLOADER_SETTINGS (rw) : ORIGIN = 0xFF000, LENGTH = 0x1000
|
||||||
|
# first 4 bytes should be 0x01 to indicate valid app image
|
||||||
|
# second 4 bytes should be 0x00 to indicate no CRC required for image
|
||||||
|
echo "01 00 00 00 00 00 00 00" | xxd -r -p - >/tmp/bootconf.bin
|
||||||
|
srec_cat /tmp/bootconf.bin -binary -offset 0xff000 -output /tmp/bootconf.hex -intel
|
||||||
|
|
||||||
|
echo Generating merged hex file
|
||||||
|
mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-125-gf38f8f4-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex
|
||||||
|
|
||||||
|
echo Telling bootloader app region is valid and telling CPU to run
|
||||||
|
nrfjprog --program ttgo_eink_full.hex -f nrf52 --reset
|
||||||
|
|
||||||
|
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
||||||
6
bin/qspi-flash-test.sh
Executable file
6
bin/qspi-flash-test.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
# You probably don't need this - it is a basic test of the serial flash on the TTGO eink board
|
||||||
|
|
||||||
|
nrfjprog -qspiini nrf52/ttgo_eink_qpsi.ini --qspieraseall
|
||||||
|
nrfjprog --qspiini nrf52/ttgo_eink_qpsi.ini --memwr 0x12000000 --val 0xdeadbeef --verify
|
||||||
|
nrfjprog --qspiini nrf52/ttgo_eink_qpsi.ini --readqspi spi.hex
|
||||||
|
objdump -s spi.hex | less
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
export VERSION=1.0.0
|
export VERSION=1.1.6
|
||||||
61
boards/eink.json
Normal file
61
boards/eink.json
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "nrf52840_s140_v6.ld"
|
||||||
|
},
|
||||||
|
"core": "nRF5",
|
||||||
|
"cpu": "cortex-m4",
|
||||||
|
"extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V1 -DNRF52840_XXAA",
|
||||||
|
"f_cpu": "64000000L",
|
||||||
|
"hwids": [
|
||||||
|
[
|
||||||
|
"0x239A",
|
||||||
|
"0x4405"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"usb_product": "TTGO_eink",
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "eink",
|
||||||
|
"variants_dir": "variants",
|
||||||
|
"bsp": {
|
||||||
|
"name": "adafruit"
|
||||||
|
},
|
||||||
|
"softdevice": {
|
||||||
|
"sd_flags": "-DS140",
|
||||||
|
"sd_name": "s140",
|
||||||
|
"sd_version": "6.1.1",
|
||||||
|
"sd_fwid": "0x00B6"
|
||||||
|
},
|
||||||
|
"bootloader": {
|
||||||
|
"settings_addr": "0xFF000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"bluetooth"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"jlink_device": "nRF52840_xxAA",
|
||||||
|
"onboard_tools": [
|
||||||
|
"jlink"
|
||||||
|
],
|
||||||
|
"svd_path": "nrf52840.svd"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino"
|
||||||
|
],
|
||||||
|
"name": "TTGO eink (Adafruit BSP)",
|
||||||
|
"upload": {
|
||||||
|
"maximum_ram_size": 248832,
|
||||||
|
"maximum_size": 815104,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 115200,
|
||||||
|
"protocol": "jlink",
|
||||||
|
"protocols": [
|
||||||
|
"jlink",
|
||||||
|
"nrfjprog",
|
||||||
|
"stlink"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": "FIXME",
|
||||||
|
"vendor": "TTGO"
|
||||||
|
}
|
||||||
277
data/style.css
Normal file
277
data/style.css
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./Google.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #C5DDEB;
|
||||||
|
font: 14px/20px "Lato", Arial, sans-serif;
|
||||||
|
padding: 40px 0;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
1fr 4fr;
|
||||||
|
grid-template-areas:
|
||||||
|
"header header"
|
||||||
|
"sidebar content";
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 750px;
|
||||||
|
background: #444753;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {grid-area: header;}
|
||||||
|
.side {grid-area: sidebar;}
|
||||||
|
.main {grid-area: content;}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
border-bottom: 2px solid white;
|
||||||
|
}
|
||||||
|
.top-text {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side {
|
||||||
|
width: 260px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.side .side-header {
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 2px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .side-header .side-text {
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-list ul {
|
||||||
|
padding: 20px;
|
||||||
|
height: 570px;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
.channel-list ul li {
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-list .channel-name {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-list .message-count {
|
||||||
|
padding-left: 16px;
|
||||||
|
color: #92959E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
stroke-width: 0;
|
||||||
|
stroke: currentColor;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-map-marker {
|
||||||
|
width: 0.5714285714285714em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-circle {
|
||||||
|
width: 0.8571428571428571em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
/* width: 490px; */
|
||||||
|
float: left;
|
||||||
|
background: #F2F5F8;
|
||||||
|
/* border-top-right-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px; */
|
||||||
|
color: #434651;
|
||||||
|
}
|
||||||
|
.content .content-header {
|
||||||
|
flex-grow: 0;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 2px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content .content-header .content-from {
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.content .content-header .content-from .content-from-highlight {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.content .content-header .content-num-messages {
|
||||||
|
color: #92959E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content .content-history {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 20px 20px 20px;
|
||||||
|
border-bottom: 2px solid white;
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: 375px;
|
||||||
|
}
|
||||||
|
.content .content-history ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-inline-start: 10px;
|
||||||
|
}
|
||||||
|
.content .content-history .message-data {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.content .content-history .message-data-time {
|
||||||
|
color: #a8aab1;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
.content .content-history .message {
|
||||||
|
color: white;
|
||||||
|
padding: 8px 10px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 7px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
width: 90%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.content .content-history .message:after {
|
||||||
|
bottom: 100%;
|
||||||
|
left: 7%;
|
||||||
|
border: solid transparent;
|
||||||
|
content: " ";
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
border-bottom-color: #86BB71;
|
||||||
|
border-width: 10px;
|
||||||
|
margin-left: -10px;
|
||||||
|
}
|
||||||
|
.content .content-history .my-message {
|
||||||
|
background: #86BB71;
|
||||||
|
}
|
||||||
|
.content .content-history .other-message {
|
||||||
|
background: #94C2ED;
|
||||||
|
}
|
||||||
|
.content .content-history .other-message:after {
|
||||||
|
border-bottom-color: #94C2ED;
|
||||||
|
left: 93%;
|
||||||
|
}
|
||||||
|
.content .content-message {
|
||||||
|
flex-grow: 0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.content .content-message textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 10px;
|
||||||
|
font: 14px/22px "Lato", Arial, sans-serif;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content .content-message button {
|
||||||
|
float: right;
|
||||||
|
color: #94C2ED;
|
||||||
|
font-size: 16px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
background: #F2F5F8;
|
||||||
|
}
|
||||||
|
.content .content-message button:hover {
|
||||||
|
color: #75b1e8;
|
||||||
|
}
|
||||||
|
/* Tooltip container */
|
||||||
|
.tooltip {
|
||||||
|
color: #86BB71;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
|
||||||
|
}
|
||||||
|
/* Tooltip text */
|
||||||
|
.tooltip .tooltiptext {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 120px;
|
||||||
|
background-color: #444753;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
/* Position the tooltip text - see examples below! */
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the tooltip text when you mouse over the tooltip container */
|
||||||
|
.tooltip:hover .tooltiptext {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.online, .offline, .me {
|
||||||
|
margin-right: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.online {
|
||||||
|
color: #86BB71;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline {
|
||||||
|
color: #E38968;
|
||||||
|
}
|
||||||
|
|
||||||
|
.me {
|
||||||
|
color: #94C2ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix:after {
|
||||||
|
visibility: hidden;
|
||||||
|
display: block;
|
||||||
|
font-size: 0;
|
||||||
|
content: " ";
|
||||||
|
clear: both;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
BIN
data/style.css.gz
Normal file
BIN
data/style.css.gz
Normal file
Binary file not shown.
@@ -43,7 +43,8 @@ For an detailed walk-through aimed at beginners, we recommend [meshtastic.letsta
|
|||||||
|
|
||||||
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.
|
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.
|
||||||
|
|
||||||
- 06/24/2020 - 0.7.x Now with over 1000 android users, over 600 people using the radios and translated into 13 languages. Fairly stable and we are working through bugs to get to 1.0.
|
- 09/14/2020 - 1.0.0 Now with over 1700 android users, over 2000 nodes and translated into 15 languages. This project will always be a "beta" experiment, but now quite usable. We are currently selecting 1.1 features in our discussion forum.
|
||||||
|
- 06/24/2020 - 0.7.x Now with over 1000 android users, over 600 people using the radios and translated into 22 languages. Fairly stable and we are working through bugs to get to 1.0.
|
||||||
- 06/04/2020 - 0.6.7 Beta releases of both the application and the device code are released. Features are fairly solid now with a sizable number of users.
|
- 06/04/2020 - 0.6.7 Beta releases of both the application and the device code are released. Features are fairly solid now with a sizable number of users.
|
||||||
- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects.
|
- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects.
|
||||||
- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon.
|
- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon.
|
||||||
@@ -77,13 +78,14 @@ Make sure to buy the frequency range which is legal for your country. For the US
|
|||||||
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
|
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
|
||||||
|
|
||||||
For a nice looking cases:
|
For a nice looking cases:
|
||||||
- 3D printable cases
|
|
||||||
1. TTGO T-Beam V0 see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
|
- 3D printable cases
|
||||||
2. TTGO T_Beam V1 (SMA) see this [design](https://www.thingiverse.com/thing:3830711) by [rwanrooy](https://www.thingiverse.com/rwanrooy) or this [remix](https://www.thingiverse.com/thing:3949330) by [8ung](https://www.thingiverse.com/8ung)
|
1. TTGO T-Beam V0 see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
|
||||||
3. TTGO T_Beam V1 (IPEX) see this [design](https://www.thingiverse.com/thing:4587297) by [drewsed](https://www.thingiverse.com/drewsed)
|
2. TTGO T_Beam V1 (SMA) see this [design](https://www.thingiverse.com/thing:3830711) by [rwanrooy](https://www.thingiverse.com/rwanrooy) or this [remix](https://www.thingiverse.com/thing:3949330) by [8ung](https://www.thingiverse.com/8ung)
|
||||||
4. Heltec Lora32 see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
|
3. TTGO T_Beam V1 (IPEX) see this [design](https://www.thingiverse.com/thing:4587297) by [drewsed](https://www.thingiverse.com/drewsed)
|
||||||
- Laser-cut cases
|
4. Heltec Lora32 see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
|
||||||
1. TTGO T_Beam V1 (SMA) see this [design](https://www.thingiverse.com/thing:4552771) by [jefish](https://www.thingiverse.com/jefish)
|
- Laser-cut cases
|
||||||
|
1. TTGO T_Beam V1 (SMA) see this [design](https://www.thingiverse.com/thing:4552771) by [jefish](https://www.thingiverse.com/jefish)
|
||||||
|
|
||||||
# IMPORTANT DISCLAIMERS AND FAQ
|
# IMPORTANT DISCLAIMERS AND FAQ
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
theme: jekyll-theme-cayman
|
theme: jekyll-theme-cayman
|
||||||
|
|
||||||
title: Meshtastic
|
title: Meshtastic
|
||||||
description: An opensource hiking, pilot, skiing, Signal-App-extending GPS mesh communicator
|
description: An opensource hiking, pilot, skiing, secure GPS mesh communicator
|
||||||
google_analytics: G-DRZ5H5EXHV
|
google_analytics: G-DRZ5H5EXHV
|
||||||
|
|
||||||
include: [".well-known"]
|
include: [".well-known"]
|
||||||
|
|||||||
BIN
docs/hardware/WIFI_LoRa_32_V2(868-915).PDF
Normal file
BIN
docs/hardware/WIFI_LoRa_32_V2(868-915).PDF
Normal file
Binary file not shown.
BIN
docs/hardware/air530/Air530 GPS Manual Text English.pdf
Normal file
BIN
docs/hardware/air530/Air530 GPS Manual Text English.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/air530/Air530_GPS_User_Booklet.V1.7.pdf
Normal file
BIN
docs/hardware/air530/Air530_GPS_User_Booklet.V1.7.pdf
Normal file
Binary file not shown.
Binary file not shown.
38977
docs/hardware/u-blox8-M8_ReceiverDescrProtSpec_(UBX-13003221).pdf
Normal file
38977
docs/hardware/u-blox8-M8_ReceiverDescrProtSpec_(UBX-13003221).pdf
Normal file
File diff suppressed because one or more lines are too long
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
You probably don't care about this section - skip to the next one.
|
You probably don't care about this section - skip to the next one.
|
||||||
|
|
||||||
|
Threading tasks:
|
||||||
|
|
||||||
|
- Use https://github.com/ivanseidel/ArduinoThread? rather than full coroutines
|
||||||
|
- clean up main loop()
|
||||||
|
- check that we are mostly asleep, show which thread is causing us to wake
|
||||||
|
-
|
||||||
|
- use tickless idle on nrf52, and sleep X msec or until an interrupt occurs or the cooperative scheduling changes. https://devzone.nordicsemi.com/f/nordic-q-a/12363/nrf52-freertos-power-consumption-tickless-idle
|
||||||
|
- BAD IDEA: use vTaskDelay and https://www.freertos.org/xTaskAbortDelay.html if scheduling changes. (define INCLUDE_xTaskAbortDelay on ESP32 and NRF52 - seems impossible to find?)
|
||||||
|
- GOOD IDEA: use xSemaphoreTake to take a semaphore using a timeout. Expect semaphore to not be set, but set it to indicate scheduling has changed.
|
||||||
|
|
||||||
Nimble tasks:
|
Nimble tasks:
|
||||||
|
|
||||||
- readerror.txt stress test bug
|
- readerror.txt stress test bug
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ Expected sequence for initial download:
|
|||||||
- Read a RadioConfig from "radio" - used to get the channel and radio settings
|
- Read a RadioConfig from "radio" - used to get the channel and radio settings
|
||||||
- Read a User from "user" - to get the username for this node
|
- Read a User from "user" - to get the username for this node
|
||||||
- Read a MyNodeInfo from "mynode" to get information about this local device
|
- Read a MyNodeInfo from "mynode" to get information about this local device
|
||||||
- Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine
|
|
||||||
- Read a series of NodeInfo packets to build the phone's copy of the current NodeDB for the mesh
|
- Read a series of NodeInfo packets to build the phone's copy of the current NodeDB for the mesh
|
||||||
- Read a endConfig packet that indicates that the entire state you need has been sent.
|
- Read a endConfig packet that indicates that the entire state you need has been sent.
|
||||||
- Read a series of MeshPackets until it returns empty to get any messages that arrived for this node while the phone was away
|
- Read a series of MeshPackets until it returns empty to get any messages that arrived for this node while the phone was away
|
||||||
|
|||||||
17
docs/software/gps-todo.txt
Normal file
17
docs/software/gps-todo.txt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
You probably don't care about this ugly file of personal notes ;-)
|
||||||
|
|
||||||
|
for taiwan region:
|
||||||
|
bin/run.sh --set region 8
|
||||||
|
|
||||||
|
time only mode
|
||||||
|
./bin/run.sh --set gps_operation 3
|
||||||
|
|
||||||
|
ublox parsing failure
|
||||||
|
|
||||||
|
record power measurements and update spreadsheet
|
||||||
|
|
||||||
|
have loop methods return allowable sleep time (from their perspective)
|
||||||
|
increase main cpu sleep time
|
||||||
|
|
||||||
|
warn people about crummy gps antennas - add to faq
|
||||||
|
|
||||||
@@ -7,6 +7,10 @@
|
|||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
|
|
||||||
|
- enter SDS state at correct time (to protect battery or loss of phone contact)
|
||||||
|
- show screen on eink when we enter SDS state (with app info and say sleeping)
|
||||||
|
- require button press to pair
|
||||||
|
|
||||||
- shrink soft device RAM usage
|
- shrink soft device RAM usage
|
||||||
- get nrf52832 working again (currently OOM)
|
- get nrf52832 working again (currently OOM)
|
||||||
- i2c gps comms not quite right
|
- i2c gps comms not quite right
|
||||||
@@ -196,7 +200,7 @@ Nice ideas worth considering someday...
|
|||||||
- DONE neg 7 error code from receive
|
- DONE neg 7 error code from receive
|
||||||
- DONE remove unused sx1262 lib from github
|
- DONE remove unused sx1262 lib from github
|
||||||
- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug.
|
- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug.
|
||||||
- add a NEMA based GPS driver to test GPS
|
- add a NMEA based GPS driver to test GPS
|
||||||
- DONE use "variants" to get all gpio bindings
|
- DONE use "variants" to get all gpio bindings
|
||||||
- DONE plug in correct variants for the real board
|
- DONE plug in correct variants for the real board
|
||||||
- turn on DFU assistance in the appload using the nordic DFU helper lib call
|
- turn on DFU assistance in the appload using the nordic DFU helper lib call
|
||||||
|
|||||||
@@ -32,14 +32,18 @@ From lower to higher power consumption.
|
|||||||
onEntry: setBluetoothOn(true)
|
onEntry: setBluetoothOn(true)
|
||||||
onExit:
|
onExit:
|
||||||
|
|
||||||
- full on (ON) - Everything is on
|
|
||||||
onEntry: setBluetoothOn(true), screen.setOn(true)
|
|
||||||
onExit: screen.setOn(false)
|
|
||||||
|
|
||||||
- serial API usage (SERIAL) - Screen is on, device doesn't sleep, bluetooth off
|
- serial API usage (SERIAL) - Screen is on, device doesn't sleep, bluetooth off
|
||||||
onEntry: setBluetooth off, screen on
|
onEntry: setBluetooth off, screen on
|
||||||
onExit:
|
onExit:
|
||||||
|
|
||||||
|
- full on (ON) - Everything is on, can eventually timeout and lower to a lower power state
|
||||||
|
onEntry: setBluetoothOn(true), screen.setOn(true)
|
||||||
|
onExit: screen->setOn(false)
|
||||||
|
|
||||||
|
- has power (POWER) - Screen is on, device doesn't sleep, bluetooth on, will stay in this state as long as we have power
|
||||||
|
onEntry: setBluetooth off, screen on
|
||||||
|
onExit:
|
||||||
|
|
||||||
## Behavior
|
## Behavior
|
||||||
|
|
||||||
### events that increase CPU activity
|
### events that increase CPU activity
|
||||||
@@ -56,9 +60,11 @@ From lower to higher power consumption.
|
|||||||
- While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown)
|
- While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown)
|
||||||
- While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts)
|
- While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts)
|
||||||
- while in LS/NB/DARK: if SERIAL_CONNECTED, go to serial
|
- while in LS/NB/DARK: if SERIAL_CONNECTED, go to serial
|
||||||
|
- while in any state: if we have AC power, go to POWER
|
||||||
|
|
||||||
### events that decrease cpu activity
|
### events that decrease cpu activity
|
||||||
|
|
||||||
|
- While in POWER: if lose AC go to ON
|
||||||
- While in SERIAL: if SERIAL_DISCONNECTED, go to NB
|
- While in SERIAL: if SERIAL_DISCONNECTED, go to NB
|
||||||
- While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess
|
- While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess
|
||||||
- While in ON: If it has been more than screen_on_secs since a press, lower to DARK
|
- While in ON: If it has been more than screen_on_secs since a press, lower to DARK
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ This is a mini design doc for developing the meshtastic software.
|
|||||||
* Our [project board](https://github.com/orgs/meshtastic/projects/1) - shows what things we are currently working on and remaining work items for the current release.
|
* Our [project board](https://github.com/orgs/meshtastic/projects/1) - shows what things we are currently working on and remaining work items for the current release.
|
||||||
* [Power Management](power.md)
|
* [Power Management](power.md)
|
||||||
* [Mesh algorithm](mesh-alg.md)
|
* [Mesh algorithm](mesh-alg.md)
|
||||||
* [Bluetooth API](bluetooth-api.md) and porting guide for new clients (iOS, python, etc...)
|
* [Device API](device-api.md) and porting guide for new clients (iOS, python, etc...)
|
||||||
* TODO: how to port the device code to a new device.
|
* TODO: how to port the device code to a new device.
|
||||||
|
|||||||
69
nrf52/ttgo_eink_qpsi.ini
Normal file
69
nrf52/ttgo_eink_qpsi.ini
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
; nrfjprog.exe configuration file.
|
||||||
|
|
||||||
|
; Note: QSPI flash is mapped into memory at address 0x12000000
|
||||||
|
|
||||||
|
[DEFAULT_CONFIGURATION]
|
||||||
|
; Define the capacity of the flash memory device in bytes. Set to 0 if no external memory device is present in your board.
|
||||||
|
; MX25R1635F is 16Mbit/2Mbyte
|
||||||
|
MemSize = 0x200000
|
||||||
|
|
||||||
|
; Define the desired ReadMode. Valid options are FASTREAD, READ2O, READ2IO, READ4O and READ4IO
|
||||||
|
ReadMode = READ2IO
|
||||||
|
|
||||||
|
; Define the desired WriteMode. Valid options are PP, PP2O, PP4O and PP4IO
|
||||||
|
WriteMode = PP
|
||||||
|
|
||||||
|
; Define the desired AddressMode. Valid options are BIT24 and BIT32
|
||||||
|
AddressMode = BIT24
|
||||||
|
|
||||||
|
; Define the desired Frequency. Valid options are M2, M4, M8, M16 and M32
|
||||||
|
Frequency = M16
|
||||||
|
|
||||||
|
; Define the desired SPI mode. Valid options are MODE0 and MODE3
|
||||||
|
SpiMode = MODE0
|
||||||
|
|
||||||
|
; Define the desired SckDelay. Valid options are in the range 0 to 255
|
||||||
|
SckDelay = 0x80
|
||||||
|
|
||||||
|
; Define the desired IO level for DIO2 and DIO3 during a custom instruction. Valid options are LEVEL_HIGH and LEVEL_LOW
|
||||||
|
CustomInstructionIO2Level = LEVEL_LOW
|
||||||
|
CustomInstructionIO3Level = LEVEL_HIGH
|
||||||
|
|
||||||
|
; Define the assigned pins for the QSPI peripheral. Valid options are those existing in your device
|
||||||
|
CSNPin = 15
|
||||||
|
CSNPort = 1
|
||||||
|
SCKPin = 14
|
||||||
|
SCKPort = 1
|
||||||
|
DIO0Pin = 12
|
||||||
|
DIO0Port = 1
|
||||||
|
DIO1Pin = 13
|
||||||
|
DIO1Port = 1
|
||||||
|
|
||||||
|
;These two pins are not connected, but we must name something
|
||||||
|
DIO2Pin = 3
|
||||||
|
DIO2Port = 1
|
||||||
|
DIO3Pin = 5
|
||||||
|
DIO3Port = 1
|
||||||
|
|
||||||
|
; Define the Index of the Write In Progress (WIP) bit in the status register. Valid options are in the range of 0 to 7.
|
||||||
|
WIPIndex = 0
|
||||||
|
|
||||||
|
; Define page size for commands. Valid sizes are PAGE256 and PAGE512.
|
||||||
|
PPSize = PAGE256
|
||||||
|
|
||||||
|
; Custom instructions to send to the external memory after initialization. Format is instruction code plus data to send in between optional brakets.
|
||||||
|
; These instructions will be executed each time the qspi peripheral is initiated by nrfjprog.
|
||||||
|
; To improve execution speed on consecutive interations with QSPI, you can run nrfjprog once with custom initialization, and then comment out the lines below.
|
||||||
|
; Numbers can be given in decimal, hex (starting with either 0x or 0X) and binary (starting with either 0b or 0B) formats.
|
||||||
|
; The custom instructions will be executed in the order found.
|
||||||
|
|
||||||
|
; This example includes two commands, first a WREN (WRite ENable) and then a WRSR (WRite Satus Register) enabling the Quad Operation and the High Performance
|
||||||
|
; mode for the MX25R6435F memory present in the nRF52840 DK.
|
||||||
|
;InitializationCustomInstruction = 0x06
|
||||||
|
;InitializationCustomInstruction = 0x01, [0x40, 0, 0x2]
|
||||||
|
|
||||||
|
; For MX25R1635F on TTGO board, only two data lines are connected
|
||||||
|
; This example includes two commands, first a WREN (WRite ENable) and then a WRSR (WRite Satus Register) disabling Quad Operation and the High Performance
|
||||||
|
; mode. For normal operation you might want low power mode instead.
|
||||||
|
InitializationCustomInstruction = 0x06
|
||||||
|
InitializationCustomInstruction = 0x01, [0x00, 0, 0x2]
|
||||||
@@ -9,7 +9,8 @@
|
|||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
default_envs = tbeam # or if you'd like to change the default to something like lora-relay-v1 put that here
|
default_envs = tbeam # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||||
|
;default_envs = heltec # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||||
|
|
||||||
[common]
|
[common]
|
||||||
; common is not currently used
|
; common is not currently used
|
||||||
@@ -23,23 +24,22 @@ default_envs = tbeam # or if you'd like to change the default to something like
|
|||||||
|
|
||||||
[env]
|
[env]
|
||||||
|
|
||||||
framework = arduino
|
|
||||||
|
|
||||||
; customize the partition table
|
|
||||||
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
|
||||||
board_build.partitions = partition-table.csv
|
|
||||||
|
|
||||||
; note: we add src to our include search path so that lmic_project_config can override
|
; note: we add src to our include search path so that lmic_project_config can override
|
||||||
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
||||||
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map
|
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Wl,-Map,.pio/build/output.map
|
||||||
-DHW_VERSION_${sysenv.COUNTRY}
|
-DHW_VERSION_${sysenv.COUNTRY}
|
||||||
-DAPP_VERSION=${sysenv.APP_VERSION}
|
-DAPP_VERSION=${sysenv.APP_VERSION}
|
||||||
-DHW_VERSION=${sysenv.HW_VERSION}
|
-DHW_VERSION=${sysenv.HW_VERSION}
|
||||||
|
-DUSE_THREAD_NAMES
|
||||||
|
|
||||||
; leave this commented out to avoid breaking Windows
|
; leave this commented out to avoid breaking Windows
|
||||||
;upload_port = /dev/ttyUSB0
|
;upload_port = /dev/ttyUSB0
|
||||||
;monitor_port = /dev/ttyUSB0
|
;monitor_port = /dev/ttyUSB0
|
||||||
|
|
||||||
|
; geeksville: I think setting this should not be required - it breaks linux
|
||||||
|
;upload_port = /dev/cu.SLAB_USBtoUART
|
||||||
|
;monitor_port = /dev/cu.SLAB_USBtoUART
|
||||||
|
|
||||||
; the default is esptool
|
; the default is esptool
|
||||||
; upload_protocol = esp-prog
|
; upload_protocol = esp-prog
|
||||||
|
|
||||||
@@ -60,33 +60,54 @@ debug_tool = jlink
|
|||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||||
SPI
|
https://github.com/geeksville/OneButton.git ; OneButton library for non-blocking button debounce
|
||||||
1260 ; OneButton library for non-blocking button debounce
|
|
||||||
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
||||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
https://github.com/meshtastic/arduino-fsm.git#2f106146071fc7bc620e1e8d4b88dc4e0266ce39
|
||||||
https://github.com/meshtastic/arduino-fsm.git
|
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
|
||||||
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
|
https://github.com/meshtastic/RadioLib.git#8657380241bce681c33aab46598bbf13b11f876c
|
||||||
https://github.com/meshtastic/RadioLib.git#7989a269be590a5d4914ac04069b58f4930c45c1
|
|
||||||
https://github.com/meshtastic/TinyGPSPlus.git
|
https://github.com/meshtastic/TinyGPSPlus.git
|
||||||
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
|
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
|
||||||
|
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||||
|
SPI
|
||||||
|
https://github.com/geeksville/ArduinoThread.git#333ffd09b596977c217ba25da4258f588b462ac6
|
||||||
|
|
||||||
|
; Common settings for conventional (non Portduino) Ardino targets
|
||||||
|
[arduino_base]
|
||||||
|
|
||||||
|
framework = arduino
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
|
||||||
|
build_flags = ${env.build_flags} -Os
|
||||||
|
|
||||||
|
src_filter = ${env.src_filter} -<portduino/>
|
||||||
|
|
||||||
; Common settings for ESP targes, mixin with extends = esp32_base
|
; Common settings for ESP targes, mixin with extends = esp32_base
|
||||||
[esp32_base]
|
[esp32_base]
|
||||||
|
extends = arduino_base
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
src_filter =
|
src_filter =
|
||||||
${env.src_filter} -<nrf52/>
|
${arduino_base.src_filter} -<nrf52/>
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
debug_init_break = tbreak setup
|
debug_init_break = tbreak setup
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue -lnimble -std=c++11
|
${arduino_base.build_flags} -Wall -Wextra -Isrc/esp32 -Isrc/esp32-mfix-esp32-psram-cache-issue -lnimble -std=c++11
|
||||||
-DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
-DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||||
-DAXP_DEBUG_PORT=Serial
|
-DAXP_DEBUG_PORT=Serial
|
||||||
|
lib_deps =
|
||||||
|
${arduino_base.lib_deps}
|
||||||
|
https://github.com/meshtastic/esp32_https_server.git
|
||||||
# Hmm - this doesn't work yet
|
# Hmm - this doesn't work yet
|
||||||
# board_build.ldscript = linker/esp32.extram.bss.ld
|
# board_build.ldscript = linker/esp32.extram.bss.ld
|
||||||
lib_ignore = segger_rtt
|
lib_ignore = segger_rtt
|
||||||
platform_packages =
|
platform_packages =
|
||||||
framework-arduinoespressif32@https://github.com/meshtastic/arduino-esp32.git#2814f110aa618429bdd9a0a2d6a93c55f29f87a6
|
framework-arduinoespressif32@https://github.com/meshtastic/arduino-esp32.git#2814f110aa618429bdd9a0a2d6a93c55f29f87a6
|
||||||
|
|
||||||
|
; customize the partition table
|
||||||
|
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
||||||
|
board_build.partitions = partition-table.csv
|
||||||
|
|
||||||
; not needed included in ttgo-t-beam board file
|
; not needed included in ttgo-t-beam board file
|
||||||
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
|
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
|
||||||
; -DBOARD_HAS_PSRAM
|
; -DBOARD_HAS_PSRAM
|
||||||
@@ -99,7 +120,7 @@ platform_packages =
|
|||||||
extends = esp32_base
|
extends = esp32_base
|
||||||
board = ttgo-t-beam
|
board = ttgo-t-beam
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${esp32_base.lib_deps}
|
||||||
build_flags =
|
build_flags =
|
||||||
${esp32_base.build_flags} -D TBEAM_V10
|
${esp32_base.build_flags} -D TBEAM_V10
|
||||||
|
|
||||||
@@ -142,25 +163,26 @@ build_flags =
|
|||||||
platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
|
platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
|
||||||
board = cubecell_board_plus
|
board = cubecell_board_plus
|
||||||
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
|
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
|
||||||
build_flags = ${env.build_flags} -DARDUINO=100 -Isrc/cubecell
|
build_flags = ${arduino_base.build_flags} -DARDUINO=100 -Isrc/cubecell
|
||||||
src_filter =
|
src_filter =
|
||||||
${env.src_filter} -<esp32/> -<nrf52/>
|
${arduino_base.src_filter} -<esp32/> -<nrf52/>
|
||||||
|
|
||||||
; Common settings for NRF52 based targets
|
; Common settings for NRF52 based targets
|
||||||
[nrf52_base]
|
[nrf52_base]
|
||||||
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
|
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
|
||||||
; platform = nordicnrf52
|
; platform = nordicnrf52
|
||||||
platform = https://github.com/meshtastic/platform-nordicnrf52.git#1a2639a6b0f79b5df66bea3e3089f0d5285fdc63
|
platform = https://github.com/meshtastic/platform-nordicnrf52.git#1a2639a6b0f79b5df66bea3e3089f0d5285fdc63
|
||||||
|
extends = arduino_base
|
||||||
debug_tool = jlink
|
debug_tool = jlink
|
||||||
build_type = debug ; I'm debugging with ICE a lot now
|
build_type = debug ; I'm debugging with ICE a lot now
|
||||||
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
|
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags} -Wno-unused-variable
|
${arduino_base.build_flags} -Wno-unused-variable
|
||||||
-Isrc/nrf52
|
-Isrc/nrf52
|
||||||
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
|
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
|
||||||
;-DCFG_DEBUG=3
|
;-DCFG_DEBUG=3
|
||||||
src_filter =
|
src_filter =
|
||||||
${env.src_filter} -<esp32/> -<nimble/>
|
${arduino_base.src_filter} -<esp32/> -<nimble/> -<meshwifi/>
|
||||||
lib_ignore =
|
lib_ignore =
|
||||||
BluetoothOTA
|
BluetoothOTA
|
||||||
monitor_port = /dev/ttyACM1
|
monitor_port = /dev/ttyACM1
|
||||||
@@ -212,9 +234,23 @@ monitor_speed = 115200
|
|||||||
extends = nrf52_base
|
extends = nrf52_base
|
||||||
board = ppr
|
board = ppr
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${arduino_base.lib_deps}
|
||||||
UC1701
|
UC1701
|
||||||
|
|
||||||
|
; Prototype eink/nrf52840/sx1262 device
|
||||||
|
[env:eink]
|
||||||
|
extends = nrf52_base
|
||||||
|
board = eink
|
||||||
|
# add our variants files to the include and src paths
|
||||||
|
# define build flags for the TFT_eSPI library
|
||||||
|
build_flags = ${nrf52_base.build_flags} -Ivariants/eink
|
||||||
|
-DBUSY_PIN=3 -DRST_PIN=2 -DDC_PIN=28 -DCS_PIN=30
|
||||||
|
src_filter = ${nrf52_base.src_filter} +<../variants/eink>
|
||||||
|
lib_deps =
|
||||||
|
${arduino_base.lib_deps}
|
||||||
|
https://github.com/geeksville/EPD_Libraries.git
|
||||||
|
TFT_eSPI
|
||||||
|
|
||||||
; The https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay board by @BigCorvus
|
; The https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay board by @BigCorvus
|
||||||
[env:lora-relay-v1]
|
[env:lora-relay-v1]
|
||||||
extends = nrf52_base
|
extends = nrf52_base
|
||||||
@@ -233,11 +269,15 @@ build_flags = ${nrf52_base.build_flags} -Ivariants/lora_relay_v1
|
|||||||
-DSPI_FREQUENCY=27000000
|
-DSPI_FREQUENCY=27000000
|
||||||
src_filter = ${nrf52_base.src_filter} +<../variants/lora_relay_v1>
|
src_filter = ${nrf52_base.src_filter} +<../variants/lora_relay_v1>
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${arduino_base.lib_deps}
|
||||||
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
||||||
TFT_eSPI
|
TFT_eSPI
|
||||||
# Adafruit ST7735 and ST7789 Library
|
# Adafruit ST7735 and ST7789 Library
|
||||||
|
|
||||||
|
; The Portduino based sim environment on top of linux
|
||||||
|
[env:linux]
|
||||||
|
platform = https://github.com/geeksville/platform-portduino.git
|
||||||
|
src_filter = ${env.src_filter} -<esp32/> -<nimble/> -<nrf52/> -<meshwifi/>
|
||||||
|
build_flags = ${arduino_base.build_flags} -O0
|
||||||
|
framework = arduino
|
||||||
|
board = linux_x86_64
|
||||||
|
|||||||
2
proto
2
proto
Submodule proto updated: ce422b7c44...a0b8d88896
192
src/GPSStatus.h
192
src/GPSStatus.h
@@ -1,126 +1,98 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Arduino.h>
|
|
||||||
#include "Status.h"
|
#include "Status.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
namespace meshtastic {
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
/// Describes the state of the GPS system.
|
/// Describes the state of the GPS system.
|
||||||
class GPSStatus : public Status
|
class GPSStatus : public Status
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
CallbackObserver<GPSStatus, const GPSStatus *> statusObserver =
|
||||||
|
CallbackObserver<GPSStatus, const GPSStatus *>(this, &GPSStatus::updateStatus);
|
||||||
|
|
||||||
|
bool hasLock = false; // default to false, until we complete our first read
|
||||||
|
bool isConnected = false; // Do we have a GPS we are talking to
|
||||||
|
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
|
||||||
|
int32_t altitude = 0;
|
||||||
|
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
|
||||||
|
// scaling before use)
|
||||||
|
uint32_t heading = 0;
|
||||||
|
uint32_t numSatellites = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
GPSStatus() { statusType = STATUS_TYPE_GPS; }
|
||||||
|
GPSStatus(bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop,
|
||||||
|
uint32_t heading, uint32_t numSatellites)
|
||||||
|
: Status()
|
||||||
{
|
{
|
||||||
|
this->hasLock = hasLock;
|
||||||
|
this->isConnected = isConnected;
|
||||||
|
this->latitude = latitude;
|
||||||
|
this->longitude = longitude;
|
||||||
|
this->altitude = altitude;
|
||||||
|
this->dop = dop;
|
||||||
|
this->heading = heading;
|
||||||
|
this->numSatellites = numSatellites;
|
||||||
|
}
|
||||||
|
GPSStatus(const GPSStatus &);
|
||||||
|
GPSStatus &operator=(const GPSStatus &);
|
||||||
|
|
||||||
private:
|
void observe(Observable<const GPSStatus *> *source) { statusObserver.observe(source); }
|
||||||
CallbackObserver<GPSStatus, const GPSStatus *> statusObserver = CallbackObserver<GPSStatus, const GPSStatus *>(this, &GPSStatus::updateStatus);
|
|
||||||
|
|
||||||
bool hasLock = false; // default to false, until we complete our first read
|
bool getHasLock() const { return hasLock; }
|
||||||
bool isConnected = false; // Do we have a GPS we are talking to
|
|
||||||
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
|
|
||||||
int32_t altitude = 0;
|
|
||||||
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs scaling before use)
|
|
||||||
uint32_t heading = 0;
|
|
||||||
uint32_t numSatellites = 0;
|
|
||||||
|
|
||||||
public:
|
bool getIsConnected() const { return isConnected; }
|
||||||
|
|
||||||
GPSStatus() {
|
int32_t getLatitude() const { return latitude; }
|
||||||
statusType = STATUS_TYPE_GPS;
|
|
||||||
}
|
int32_t getLongitude() const { return longitude; }
|
||||||
GPSStatus( bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop, uint32_t heading, uint32_t numSatellites ) : Status()
|
|
||||||
|
int32_t getAltitude() const { return altitude; }
|
||||||
|
|
||||||
|
uint32_t getDOP() const { return dop; }
|
||||||
|
|
||||||
|
uint32_t getHeading() const { return heading; }
|
||||||
|
|
||||||
|
uint32_t getNumSatellites() const { return numSatellites; }
|
||||||
|
|
||||||
|
bool matches(const GPSStatus *newStatus) const
|
||||||
|
{
|
||||||
|
return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected || newStatus->latitude != latitude ||
|
||||||
|
newStatus->longitude != longitude || newStatus->altitude != altitude || newStatus->dop != dop ||
|
||||||
|
newStatus->heading != heading || newStatus->numSatellites != numSatellites);
|
||||||
|
}
|
||||||
|
int updateStatus(const GPSStatus *newStatus)
|
||||||
|
{
|
||||||
|
// Only update the status if values have actually changed
|
||||||
|
bool isDirty;
|
||||||
{
|
{
|
||||||
this->hasLock = hasLock;
|
isDirty = matches(newStatus);
|
||||||
this->isConnected = isConnected;
|
initialized = true;
|
||||||
this->latitude = latitude;
|
hasLock = newStatus->hasLock;
|
||||||
this->longitude = longitude;
|
isConnected = newStatus->isConnected;
|
||||||
this->altitude = altitude;
|
latitude = newStatus->latitude;
|
||||||
this->dop = dop;
|
longitude = newStatus->longitude;
|
||||||
this->heading = heading;
|
altitude = newStatus->altitude;
|
||||||
this->numSatellites = numSatellites;
|
dop = newStatus->dop;
|
||||||
|
heading = newStatus->heading;
|
||||||
|
numSatellites = newStatus->numSatellites;
|
||||||
}
|
}
|
||||||
GPSStatus(const GPSStatus &);
|
if (isDirty) {
|
||||||
GPSStatus &operator=(const GPSStatus &);
|
if (hasLock)
|
||||||
|
DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7,
|
||||||
void observe(Observable<const GPSStatus *> *source)
|
altitude, dop * 1e-2, heading * 1e-5, numSatellites);
|
||||||
{
|
else
|
||||||
statusObserver.observe(source);
|
DEBUG_MSG("No GPS lock\n");
|
||||||
|
onNewStatus.notifyObservers(this);
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool getHasLock() const
|
} // namespace meshtastic
|
||||||
{
|
|
||||||
return hasLock;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getIsConnected() const
|
|
||||||
{
|
|
||||||
return isConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t getLatitude() const
|
|
||||||
{
|
|
||||||
return latitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t getLongitude() const
|
|
||||||
{
|
|
||||||
return longitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t getAltitude() const
|
|
||||||
{
|
|
||||||
return altitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getDOP() const
|
|
||||||
{
|
|
||||||
return dop;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getHeading() const
|
|
||||||
{
|
|
||||||
return heading;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getNumSatellites() const
|
|
||||||
{
|
|
||||||
return numSatellites;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool matches(const GPSStatus *newStatus) const
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
newStatus->hasLock != hasLock ||
|
|
||||||
newStatus->isConnected != isConnected ||
|
|
||||||
newStatus->latitude != latitude ||
|
|
||||||
newStatus->longitude != longitude ||
|
|
||||||
newStatus->altitude != altitude ||
|
|
||||||
newStatus->dop != dop ||
|
|
||||||
newStatus->heading != heading ||
|
|
||||||
newStatus->numSatellites != numSatellites
|
|
||||||
);
|
|
||||||
}
|
|
||||||
int updateStatus(const GPSStatus *newStatus) {
|
|
||||||
// Only update the status if values have actually changed
|
|
||||||
bool isDirty;
|
|
||||||
{
|
|
||||||
isDirty = matches(newStatus);
|
|
||||||
initialized = true;
|
|
||||||
hasLock = newStatus->hasLock;
|
|
||||||
isConnected = newStatus->isConnected;
|
|
||||||
latitude = newStatus->latitude;
|
|
||||||
longitude = newStatus->longitude;
|
|
||||||
altitude = newStatus->altitude;
|
|
||||||
dop = newStatus->dop;
|
|
||||||
heading = newStatus->heading;
|
|
||||||
numSatellites = newStatus->numSatellites;
|
|
||||||
}
|
|
||||||
if(isDirty) {
|
|
||||||
DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5, numSatellites);
|
|
||||||
onNewStatus.notifyObservers(this);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extern meshtastic::GPSStatus *gpsStatus;
|
extern meshtastic::GPSStatus *gpsStatus;
|
||||||
149
src/Power.cpp
149
src/Power.cpp
@@ -18,6 +18,21 @@ Power *power;
|
|||||||
|
|
||||||
using namespace meshtastic;
|
using namespace meshtastic;
|
||||||
|
|
||||||
|
#if defined(NRF52_SERIES)
|
||||||
|
/*
|
||||||
|
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
|
||||||
|
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
|
||||||
|
*
|
||||||
|
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
|
||||||
|
* VDD/4, VDD/2 or VDD for the ADC levels.
|
||||||
|
*
|
||||||
|
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
|
||||||
|
*/
|
||||||
|
#define AREF_VOLTAGE 3.6
|
||||||
|
#else
|
||||||
|
#define AREF_VOLTAGE 3.3
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this board has a battery level sensor, set this to a valid implementation
|
* If this board has a battery level sensor, set this to a valid implementation
|
||||||
*/
|
*/
|
||||||
@@ -37,10 +52,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
{
|
{
|
||||||
float v = getBattVoltage() / 1000;
|
float v = getBattVoltage() / 1000;
|
||||||
|
|
||||||
if (v < 2.1)
|
if (v < noBatVolt)
|
||||||
return -1; // If voltage is super low assume no battery installed
|
return -1; // If voltage is super low assume no battery installed
|
||||||
|
|
||||||
return 100 * (v - 3.27) / (4.2 - 3.27);
|
if (v > chargingVolt)
|
||||||
|
return 0; // While charging we can't report % full on the battery
|
||||||
|
|
||||||
|
return 100 * (v - emptyVolt) / (fullVolt - emptyVolt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,9 +66,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
*/
|
*/
|
||||||
virtual float getBattVoltage()
|
virtual float getBattVoltage()
|
||||||
{
|
{
|
||||||
|
// Tested ttgo eink nrf52 board and the reported value is perfect
|
||||||
|
// DEBUG_MSG("raw val %u", raw);
|
||||||
return
|
return
|
||||||
#ifdef BATTERY_PIN
|
#ifdef BATTERY_PIN
|
||||||
1000.0 * analogRead(BATTERY_PIN) * 2.0 * (3.3 / 1024.0);
|
1000.0 * 2.0 * (AREF_VOLTAGE / 1024.0) * analogRead(BATTERY_PIN);
|
||||||
#else
|
#else
|
||||||
NAN;
|
NAN;
|
||||||
#endif
|
#endif
|
||||||
@@ -59,14 +79,40 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
/**
|
/**
|
||||||
* return true if there is a battery installed in this unit
|
* return true if there is a battery installed in this unit
|
||||||
*/
|
*/
|
||||||
virtual bool isBatteryConnect() { return getBattVoltage() != -1; }
|
virtual bool isBatteryConnect() { return getBattPercentage() != -1; }
|
||||||
|
|
||||||
|
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||||
|
/// in power
|
||||||
|
virtual bool isVBUSPlug() { return getBattVoltage() > chargingVolt; }
|
||||||
|
|
||||||
|
/// Assume charging if we have a battery and external power is connected.
|
||||||
|
/// we can't be smart enough to say 'full'?
|
||||||
|
virtual bool isChargeing() { return isBatteryConnect() && isVBUSPlug(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||||
|
/// in power
|
||||||
|
const float fullVolt = 4.2, emptyVolt = 3.27, chargingVolt = 4.3, noBatVolt = 2.1;
|
||||||
} analogLevel;
|
} analogLevel;
|
||||||
|
|
||||||
|
Power::Power() : OSThread("Power") {}
|
||||||
|
|
||||||
bool Power::analogInit()
|
bool Power::analogInit()
|
||||||
{
|
{
|
||||||
#ifdef BATTERY_PIN
|
#ifdef BATTERY_PIN
|
||||||
DEBUG_MSG("Using analog input for battery level\n");
|
DEBUG_MSG("Using analog input for battery level\n");
|
||||||
|
|
||||||
|
// disable any internal pullups
|
||||||
|
pinMode(BATTERY_PIN, INPUT);
|
||||||
|
|
||||||
|
#ifndef NO_ESP32
|
||||||
|
// ESP32 needs special analog stuff
|
||||||
adcAttachPin(BATTERY_PIN);
|
adcAttachPin(BATTERY_PIN);
|
||||||
|
#endif
|
||||||
|
#ifdef NRF52_SERIES
|
||||||
|
analogReference(AR_INTERNAL); // 3.6V
|
||||||
|
#endif
|
||||||
|
|
||||||
// adcStart(BATTERY_PIN);
|
// adcStart(BATTERY_PIN);
|
||||||
analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
|
analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
|
||||||
batteryLevel = &analogLevel;
|
batteryLevel = &analogLevel;
|
||||||
@@ -83,10 +129,7 @@ bool Power::setup()
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
found = analogInit();
|
found = analogInit();
|
||||||
}
|
}
|
||||||
if (found) {
|
enabled = found;
|
||||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
|
||||||
setPeriod(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
@@ -119,6 +162,8 @@ void Power::readPowerStatus()
|
|||||||
const PowerStatus powerStatus =
|
const PowerStatus powerStatus =
|
||||||
PowerStatus(hasBattery ? OptTrue : OptFalse, batteryLevel->isVBUSPlug() ? OptTrue : OptFalse,
|
PowerStatus(hasBattery ? OptTrue : OptFalse, batteryLevel->isVBUSPlug() ? OptTrue : OptFalse,
|
||||||
batteryLevel->isChargeing() ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
|
batteryLevel->isChargeing() ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
|
||||||
|
DEBUG_MSG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus.getHasUSB(),
|
||||||
|
powerStatus.getIsCharging(), powerStatus.getBatteryVoltageMv(), powerStatus.getBatteryChargePercent());
|
||||||
newStatus.notifyObservers(&powerStatus);
|
newStatus.notifyObservers(&powerStatus);
|
||||||
|
|
||||||
// If we have a battery at all and it is less than 10% full, force deep sleep
|
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||||
@@ -131,13 +176,47 @@ void Power::readPowerStatus()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Power::doTask()
|
int32_t Power::runOnce()
|
||||||
{
|
{
|
||||||
readPowerStatus();
|
readPowerStatus();
|
||||||
|
|
||||||
|
#ifdef TBEAM_V10
|
||||||
|
// WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll
|
||||||
|
// the IRQ status by reading the registers over I2C
|
||||||
|
axp.readIRQ();
|
||||||
|
|
||||||
|
if (axp.isVbusRemoveIRQ()) {
|
||||||
|
DEBUG_MSG("USB unplugged\n");
|
||||||
|
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
||||||
|
}
|
||||||
|
if (axp.isVbusPlugInIRQ()) {
|
||||||
|
DEBUG_MSG("USB plugged In\n");
|
||||||
|
powerFSM.trigger(EVENT_POWER_CONNECTED);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Other things we could check if we cared...
|
||||||
|
|
||||||
|
if (axp.isChargingIRQ()) {
|
||||||
|
DEBUG_MSG("Battery start charging\n");
|
||||||
|
}
|
||||||
|
if (axp.isChargingDoneIRQ()) {
|
||||||
|
DEBUG_MSG("Battery fully charged\n");
|
||||||
|
}
|
||||||
|
if (axp.isBattPlugInIRQ()) {
|
||||||
|
DEBUG_MSG("Battery inserted\n");
|
||||||
|
}
|
||||||
|
if (axp.isBattRemoveIRQ()) {
|
||||||
|
DEBUG_MSG("Battery removed\n");
|
||||||
|
}
|
||||||
|
if (axp.isPEKShortPressIRQ()) {
|
||||||
|
DEBUG_MSG("PEK short button press\n");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
axp.clearIRQ();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Only read once every 20 seconds once the power status for the app has been initialized
|
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||||
if (statusHandler && statusHandler->isInitialized())
|
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
||||||
setPeriod(1000 * 20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,7 +247,7 @@ bool Power::axp192Init()
|
|||||||
DEBUG_MSG("----------------------------------------\n");
|
DEBUG_MSG("----------------------------------------\n");
|
||||||
|
|
||||||
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
||||||
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
// axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power - now turned on in setGpsPower
|
||||||
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
||||||
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
||||||
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
||||||
@@ -200,9 +279,11 @@ bool Power::axp192Init()
|
|||||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||||
|
|
||||||
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
||||||
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
// we do not look for AXP202_CHARGING_FINISHED_IRQ & AXP202_CHARGING_IRQ because it occurs repeatedly while there is
|
||||||
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
|
// no battery also it could cause inadvertent waking from light sleep just because the battery filled
|
||||||
1);
|
// we don't look for AXP202_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed
|
||||||
|
// we don't look at AXP202_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus
|
||||||
|
axp.enableIRQ(AXP202_BATT_CONNECT_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, 1);
|
||||||
|
|
||||||
axp.clearIRQ();
|
axp.clearIRQ();
|
||||||
#endif
|
#endif
|
||||||
@@ -219,41 +300,3 @@ bool Power::axp192Init()
|
|||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Power::loop()
|
|
||||||
{
|
|
||||||
#ifdef PMU_IRQ
|
|
||||||
if (pmu_irq) {
|
|
||||||
pmu_irq = false;
|
|
||||||
axp.readIRQ();
|
|
||||||
|
|
||||||
DEBUG_MSG("pmu irq!\n");
|
|
||||||
|
|
||||||
if (axp.isChargingIRQ()) {
|
|
||||||
DEBUG_MSG("Battery start charging\n");
|
|
||||||
}
|
|
||||||
if (axp.isChargingDoneIRQ()) {
|
|
||||||
DEBUG_MSG("Battery fully charged\n");
|
|
||||||
}
|
|
||||||
if (axp.isVbusRemoveIRQ()) {
|
|
||||||
DEBUG_MSG("USB unplugged\n");
|
|
||||||
}
|
|
||||||
if (axp.isVbusPlugInIRQ()) {
|
|
||||||
DEBUG_MSG("USB plugged In\n");
|
|
||||||
}
|
|
||||||
if (axp.isBattPlugInIRQ()) {
|
|
||||||
DEBUG_MSG("Battery inserted\n");
|
|
||||||
}
|
|
||||||
if (axp.isBattRemoveIRQ()) {
|
|
||||||
DEBUG_MSG("Battery removed\n");
|
|
||||||
}
|
|
||||||
if (axp.isPEKShortPressIRQ()) {
|
|
||||||
DEBUG_MSG("PEK short button press\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
readPowerStatus();
|
|
||||||
axp.clearIRQ();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|||||||
145
src/PowerFSM.cpp
145
src/PowerFSM.cpp
@@ -4,25 +4,15 @@
|
|||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "main.h"
|
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
|
#include "main.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "target_specific.h"
|
#include "target_specific.h"
|
||||||
#include "timing.h"
|
|
||||||
|
|
||||||
static void sdsEnter()
|
static void sdsEnter()
|
||||||
{
|
{
|
||||||
/*
|
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
||||||
|
doDeepSleep(getPref_sds_secs() * 1000LL);
|
||||||
// Don't deepsleep if we have USB power or if the user as pressed a button recently
|
|
||||||
// !isUSBPowered <- doesn't work yet because the axp192 isn't letting the battery fully charge when we are awake - FIXME
|
|
||||||
if (timing::millis() - lastPressMs > radioConfig.preferences.mesh_sds_timeout_secs)
|
|
||||||
{
|
|
||||||
doDeepSleep(radioConfig.preferences.sds_secs);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
doDeepSleep(radioConfig.preferences.sds_secs * 1000LL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
@@ -31,8 +21,8 @@ static uint32_t secsSlept;
|
|||||||
|
|
||||||
static void lsEnter()
|
static void lsEnter()
|
||||||
{
|
{
|
||||||
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", getPref_ls_secs());
|
||||||
screen.setOn(false);
|
screen->setOn(false);
|
||||||
secsSlept = 0; // How long have we been sleeping this time
|
secsSlept = 0; // How long have we been sleeping this time
|
||||||
|
|
||||||
DEBUG_MSG("lsEnter end\n");
|
DEBUG_MSG("lsEnter end\n");
|
||||||
@@ -40,13 +30,13 @@ static void lsEnter()
|
|||||||
|
|
||||||
static void lsIdle()
|
static void lsIdle()
|
||||||
{
|
{
|
||||||
// DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
// DEBUG_MSG("lsIdle begin ls_secs=%u\n", getPref_ls_secs());
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
||||||
|
|
||||||
// Do we have more sleeping to do?
|
// Do we have more sleeping to do?
|
||||||
if (secsSlept < radioConfig.preferences.ls_secs) {
|
if (secsSlept < getPref_ls_secs()) {
|
||||||
// Briefly come out of sleep long enough to blink the led once every few seconds
|
// Briefly come out of sleep long enough to blink the led once every few seconds
|
||||||
uint32_t sleepTime = 30;
|
uint32_t sleepTime = 30;
|
||||||
|
|
||||||
@@ -55,7 +45,8 @@ static void lsIdle()
|
|||||||
setLed(false); // Never leave led on while in light sleep
|
setLed(false); // Never leave led on while in light sleep
|
||||||
wakeCause = doLightSleep(sleepTime * 1000LL);
|
wakeCause = doLightSleep(sleepTime * 1000LL);
|
||||||
|
|
||||||
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {
|
switch (wakeCause) {
|
||||||
|
case ESP_SLEEP_WAKEUP_TIMER:
|
||||||
// Normal case: timer expired, we should just go back to sleep ASAP
|
// Normal case: timer expired, we should just go back to sleep ASAP
|
||||||
|
|
||||||
setLed(true); // briefly turn on led
|
setLed(true); // briefly turn on led
|
||||||
@@ -63,12 +54,15 @@ static void lsIdle()
|
|||||||
|
|
||||||
secsSlept += sleepTime;
|
secsSlept += sleepTime;
|
||||||
// DEBUG_MSG("sleeping, flash led!\n");
|
// DEBUG_MSG("sleeping, flash led!\n");
|
||||||
}
|
break;
|
||||||
if (wakeCause == ESP_SLEEP_WAKEUP_UART) {
|
|
||||||
|
case ESP_SLEEP_WAKEUP_UART:
|
||||||
// Not currently used (because uart triggers in hw have problems)
|
// Not currently used (because uart triggers in hw have problems)
|
||||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||||
} else {
|
break;
|
||||||
// We woke for some other reason (button press, uart, device interrupt)
|
|
||||||
|
default:
|
||||||
|
// We woke for some other reason (button press, device interrupt)
|
||||||
// uint64_t status = esp_sleep_get_ext1_wakeup_status();
|
// uint64_t status = esp_sleep_get_ext1_wakeup_status();
|
||||||
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
||||||
|
|
||||||
@@ -82,8 +76,10 @@ static void lsIdle()
|
|||||||
powerFSM.trigger(EVENT_PRESS);
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
||||||
|
// we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code
|
||||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so
|
// Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so
|
||||||
@@ -101,12 +97,12 @@ static void lsIdle()
|
|||||||
static void lsExit()
|
static void lsExit()
|
||||||
{
|
{
|
||||||
// setGPSPower(true); // restore GPS power
|
// setGPSPower(true); // restore GPS power
|
||||||
gps->startLock();
|
gps->forceWake(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbEnter()
|
static void nbEnter()
|
||||||
{
|
{
|
||||||
screen.setOn(false);
|
screen->setOn(false);
|
||||||
setBluetoothEnable(false);
|
setBluetoothEnable(false);
|
||||||
|
|
||||||
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
|
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
|
||||||
@@ -115,36 +111,50 @@ static void nbEnter()
|
|||||||
static void darkEnter()
|
static void darkEnter()
|
||||||
{
|
{
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
screen.setOn(false);
|
screen->setOn(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void serialEnter()
|
static void serialEnter()
|
||||||
{
|
{
|
||||||
setBluetoothEnable(false);
|
setBluetoothEnable(false);
|
||||||
screen.setOn(true);
|
screen->setOn(true);
|
||||||
|
screen->print("Using API...\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void powerEnter()
|
||||||
|
{
|
||||||
|
screen->setOn(true);
|
||||||
|
setBluetoothEnable(true);
|
||||||
|
screen->print("Powered...\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void powerExit()
|
||||||
|
{
|
||||||
|
screen->setOn(true);
|
||||||
|
setBluetoothEnable(true);
|
||||||
|
screen->print("Unpowered...\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onEnter()
|
static void onEnter()
|
||||||
{
|
{
|
||||||
screen.setOn(true);
|
screen->setOn(true);
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
|
|
||||||
static uint32_t lastPingMs;
|
static uint32_t lastPingMs;
|
||||||
|
|
||||||
uint32_t now = timing::millis();
|
uint32_t now = millis();
|
||||||
|
|
||||||
if (now - lastPingMs > 30 * 1000) { // if more than a minute since our last press, ask other nodes to update their state
|
if (now - lastPingMs > 30 * 1000) { // if more than a minute since our last press, ask other nodes to update their state
|
||||||
if (displayedNodeNum)
|
if (displayedNodeNum)
|
||||||
service.sendNetworkPing(displayedNodeNum, true); // Refresh the currently displayed node
|
service.sendNetworkPing(displayedNodeNum, true); // Refresh the currently displayed node
|
||||||
lastPingMs = now;
|
lastPingMs = now;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void wakeForPing() {}
|
}
|
||||||
|
|
||||||
static void screenPress()
|
static void screenPress()
|
||||||
{
|
{
|
||||||
screen.onPress();
|
screen->onPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bootEnter() {}
|
static void bootEnter() {}
|
||||||
@@ -156,16 +166,25 @@ State stateDARK(darkEnter, NULL, NULL, "DARK");
|
|||||||
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
|
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
|
||||||
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
|
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
|
||||||
State stateON(onEnter, NULL, NULL, "ON");
|
State stateON(onEnter, NULL, NULL, "ON");
|
||||||
|
State statePOWER(powerEnter, NULL, powerExit, "POWER");
|
||||||
Fsm powerFSM(&stateBOOT);
|
Fsm powerFSM(&stateBOOT);
|
||||||
|
|
||||||
void PowerFSM_setup()
|
void PowerFSM_setup()
|
||||||
{
|
{
|
||||||
powerFSM.add_timed_transition(&stateBOOT, &stateON, 3 * 1000, NULL, "boot timeout");
|
// If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON
|
||||||
|
// We assume routers might be powered all the time, but from a low current (solar) source
|
||||||
|
bool isLowPower = radioConfig.preferences.is_low_power;
|
||||||
|
bool hasPower = !isLowPower && powerStatus && powerStatus->getHasUSB();
|
||||||
|
bool isRouter = radioConfig.preferences.is_router;
|
||||||
|
DEBUG_MSG("PowerFSM init, USB power=%d\n", hasPower);
|
||||||
|
powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing, "Wake timer");
|
// wake timer expired or a packet arrived
|
||||||
|
// if we are a router node, we go to NB (no need for bluetooth) otherwise we go to DARK (so we can send message to phone)
|
||||||
|
powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer");
|
||||||
|
|
||||||
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it
|
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB or dark and
|
||||||
// handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
// then it handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
|
powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
|
||||||
|
|
||||||
@@ -173,7 +192,10 @@ void PowerFSM_setup()
|
|||||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press");
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press");
|
||||||
|
powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_PRESS, screenPress, "Press");
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
|
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
|
||||||
|
powerFSM.add_transition(&stateSERIAL, &stateSERIAL, EVENT_PRESS, screenPress,
|
||||||
|
"Press"); // Allow button to work while in serial API
|
||||||
|
|
||||||
// Handle critically low power battery by forcing deep sleep
|
// Handle critically low power battery by forcing deep sleep
|
||||||
powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
||||||
@@ -186,42 +208,63 @@ void PowerFSM_setup()
|
|||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
// if we are a router we don't turn the screen on for these things
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
if (!isRouter) {
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
// show the latest node when we get a new node db update
|
||||||
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||||
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||||
|
powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
// Show the received text message
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||||
|
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
|
||||||
|
}
|
||||||
|
|
||||||
powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||||
powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||||
powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||||
powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||||
|
|
||||||
|
if (!isLowPower) {
|
||||||
|
powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||||
|
powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||||
|
powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||||
|
powerFSM.add_transition(&stateON, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||||
|
}
|
||||||
|
|
||||||
|
powerFSM.add_transition(&statePOWER, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
||||||
|
powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateSERIAL, &stateNB, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect");
|
powerFSM.add_transition(&stateSERIAL, &stateNB, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateON, &stateDARK, radioConfig.preferences.screen_on_secs * 1000, NULL, "Screen-on timeout");
|
powerFSM.add_timed_transition(&stateON, &stateDARK, getPref_screen_on_secs() * 1000, NULL, "Screen-on timeout");
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateDARK, &stateNB, radioConfig.preferences.phone_timeout_secs * 1000, NULL, "Phone timeout");
|
// On most boards we use light-sleep to be our main state, but on NRF52 we just stay in DARK
|
||||||
|
State *lowPowerState = &stateLS;
|
||||||
|
|
||||||
#ifndef NRF52_SERIES
|
#ifndef NRF52_SERIES
|
||||||
// We never enter light-sleep state on NRF52 (because the CPU uses so little power normally)
|
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
|
||||||
powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout");
|
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL,
|
lowPowerState = &stateDARK;
|
||||||
"Bluetooth timeout");
|
|
||||||
|
powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout");
|
||||||
|
|
||||||
|
powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout");
|
||||||
|
|
||||||
|
powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL,
|
auto meshSds = getPref_mesh_sds_timeout_secs();
|
||||||
"mesh timeout");
|
if (meshSds != UINT32_MAX)
|
||||||
|
powerFSM.add_timed_transition(lowPowerState, &stateSDS, meshSds * 1000, NULL, "mesh timeout");
|
||||||
// removing for now, because some users don't even have phones
|
// removing for now, because some users don't even have phones
|
||||||
// powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.phone_sds_timeout_sec * 1000, NULL, "phone
|
// powerFSM.add_timed_transition(lowPowerState, &stateSDS, getPref_phone_sds_timeout_sec() * 1000, NULL, "phone
|
||||||
// timeout");
|
// timeout");
|
||||||
|
|
||||||
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
||||||
|
|||||||
@@ -13,10 +13,13 @@
|
|||||||
#define EVENT_BLUETOOTH_PAIR 7
|
#define EVENT_BLUETOOTH_PAIR 7
|
||||||
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
||||||
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
|
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
|
||||||
#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep
|
#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep
|
||||||
#define EVENT_SERIAL_CONNECTED 11
|
#define EVENT_SERIAL_CONNECTED 11
|
||||||
#define EVENT_SERIAL_DISCONNECTED 12
|
#define EVENT_SERIAL_DISCONNECTED 12
|
||||||
|
#define EVENT_POWER_CONNECTED 13
|
||||||
|
#define EVENT_POWER_DISCONNECTED 14
|
||||||
|
|
||||||
extern Fsm powerFSM;
|
extern Fsm powerFSM;
|
||||||
|
extern State statePOWER, stateSERIAL;
|
||||||
|
|
||||||
void PowerFSM_setup();
|
void PowerFSM_setup();
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class PowerStatus : public Status
|
|||||||
isCharging = newStatus->isCharging;
|
isCharging = newStatus->isCharging;
|
||||||
}
|
}
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
// DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||||
onNewStatus.notifyObservers(this);
|
onNewStatus.notifyObservers(this);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ void SerialConsole::onConnectionChanged(bool connected)
|
|||||||
if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
|
if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
|
||||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||||
} else {
|
} else {
|
||||||
|
// FIXME, we get no notice of serial going away, we should instead automatically generate this event if we haven't
|
||||||
|
// received a packet in a while
|
||||||
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
|
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
39
src/concurrency/BinarySemaphoreFreeRTOS.cpp
Normal file
39
src/concurrency/BinarySemaphoreFreeRTOS.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include "concurrency/BinarySemaphoreFreeRTOS.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#ifdef HAS_FREE_RTOS
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
BinarySemaphoreFreeRTOS::BinarySemaphoreFreeRTOS()
|
||||||
|
{
|
||||||
|
semaphore = xSemaphoreCreateBinary();
|
||||||
|
}
|
||||||
|
|
||||||
|
BinarySemaphoreFreeRTOS::~BinarySemaphoreFreeRTOS()
|
||||||
|
{
|
||||||
|
vSemaphoreDelete(semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we were interrupted
|
||||||
|
*/
|
||||||
|
bool BinarySemaphoreFreeRTOS::take(uint32_t msec)
|
||||||
|
{
|
||||||
|
return xSemaphoreTake(semaphore, pdMS_TO_TICKS(msec));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BinarySemaphoreFreeRTOS::give()
|
||||||
|
{
|
||||||
|
xSemaphoreGive(semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
IRAM_ATTR void BinarySemaphoreFreeRTOS::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||||
|
{
|
||||||
|
xSemaphoreGiveFromISR(semaphore, pxHigherPriorityTaskWoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
|
|
||||||
|
#endif
|
||||||
31
src/concurrency/BinarySemaphoreFreeRTOS.h
Normal file
31
src/concurrency/BinarySemaphoreFreeRTOS.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "../freertosinc.h"
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef HAS_FREE_RTOS
|
||||||
|
|
||||||
|
class BinarySemaphoreFreeRTOS
|
||||||
|
{
|
||||||
|
SemaphoreHandle_t semaphore;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BinarySemaphoreFreeRTOS();
|
||||||
|
~BinarySemaphoreFreeRTOS();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we timed out
|
||||||
|
*/
|
||||||
|
bool take(uint32_t msec);
|
||||||
|
|
||||||
|
void give();
|
||||||
|
|
||||||
|
void giveFromISR(BaseType_t *pxHigherPriorityTaskWoken);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
36
src/concurrency/BinarySemaphorePosix.cpp
Normal file
36
src/concurrency/BinarySemaphorePosix.cpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#include "concurrency/BinarySemaphorePosix.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#ifndef HAS_FREE_RTOS
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
BinarySemaphorePosix::BinarySemaphorePosix()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
BinarySemaphorePosix::~BinarySemaphorePosix()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we timed out
|
||||||
|
*/
|
||||||
|
bool BinarySemaphorePosix::take(uint32_t msec)
|
||||||
|
{
|
||||||
|
delay(msec); // FIXME
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BinarySemaphorePosix::give()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
|
|
||||||
|
#endif
|
||||||
31
src/concurrency/BinarySemaphorePosix.h
Normal file
31
src/concurrency/BinarySemaphorePosix.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "../freertosinc.h"
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifndef HAS_FREE_RTOS
|
||||||
|
|
||||||
|
class BinarySemaphorePosix
|
||||||
|
{
|
||||||
|
// SemaphoreHandle_t semaphore;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BinarySemaphorePosix();
|
||||||
|
~BinarySemaphorePosix();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we timed out
|
||||||
|
*/
|
||||||
|
bool take(uint32_t msec);
|
||||||
|
|
||||||
|
void give();
|
||||||
|
|
||||||
|
void giveFromISR(BaseType_t *pxHigherPriorityTaskWoken);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
35
src/concurrency/InterruptableDelay.cpp
Normal file
35
src/concurrency/InterruptableDelay.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include "concurrency/InterruptableDelay.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
InterruptableDelay::InterruptableDelay() {}
|
||||||
|
|
||||||
|
InterruptableDelay::~InterruptableDelay() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we were interrupted
|
||||||
|
*/
|
||||||
|
bool InterruptableDelay::delay(uint32_t msec)
|
||||||
|
{
|
||||||
|
// DEBUG_MSG("delay %u ", msec);
|
||||||
|
|
||||||
|
// sem take will return false if we timed out (i.e. were not interrupted)
|
||||||
|
bool r = semaphore.take(msec);
|
||||||
|
|
||||||
|
// DEBUG_MSG("interrupt=%d\n", r);
|
||||||
|
return !r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterruptableDelay::interrupt()
|
||||||
|
{
|
||||||
|
semaphore.give();
|
||||||
|
}
|
||||||
|
|
||||||
|
IRAM_ATTR void InterruptableDelay::interruptFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||||
|
{
|
||||||
|
semaphore.giveFromISR(pxHigherPriorityTaskWoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
42
src/concurrency/InterruptableDelay.h
Normal file
42
src/concurrency/InterruptableDelay.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../freertosinc.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAS_FREE_RTOS
|
||||||
|
#include "concurrency/BinarySemaphoreFreeRTOS.h"
|
||||||
|
#define BinarySemaphore BinarySemaphoreFreeRTOS
|
||||||
|
#else
|
||||||
|
#include "concurrency/BinarySemaphorePosix.h"
|
||||||
|
#define BinarySemaphore BinarySemaphorePosix
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that provides delay(msec) like functionality, but can be interrupted by calling interrupt().
|
||||||
|
*
|
||||||
|
* Useful for they top level loop() delay call to keep the CPU powered down until our next scheduled event or some external event.
|
||||||
|
*
|
||||||
|
* This is implmented for FreeRTOS but should be easy to port to other operating systems.
|
||||||
|
*/
|
||||||
|
class InterruptableDelay
|
||||||
|
{
|
||||||
|
BinarySemaphore semaphore;
|
||||||
|
|
||||||
|
public:
|
||||||
|
InterruptableDelay();
|
||||||
|
~InterruptableDelay();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we were interrupted
|
||||||
|
*/
|
||||||
|
bool delay(uint32_t msec);
|
||||||
|
|
||||||
|
void interrupt();
|
||||||
|
|
||||||
|
void interruptFromISR(BaseType_t *pxHigherPriorityTaskWoken);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
#include "Lock.h"
|
#include "Lock.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
namespace concurrency {
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef HAS_FREE_RTOS
|
||||||
Lock::Lock()
|
Lock::Lock()
|
||||||
{
|
{
|
||||||
handle = xSemaphoreCreateBinary();
|
handle = xSemaphoreCreateBinary();
|
||||||
@@ -19,5 +21,12 @@ void Lock::unlock()
|
|||||||
{
|
{
|
||||||
assert(xSemaphoreGive(handle));
|
assert(xSemaphoreGive(handle));
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
Lock::Lock() {}
|
||||||
|
|
||||||
|
void Lock::lock() {}
|
||||||
|
|
||||||
|
void Lock::unlock() {}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace concurrency
|
} // namespace concurrency
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
#include "../freertosinc.h"
|
#include "../freertosinc.h"
|
||||||
|
|
||||||
namespace concurrency {
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Simple wrapper around FreeRTOS API for implementing a mutex lock
|
* @brief Simple wrapper around FreeRTOS API for implementing a mutex lock
|
||||||
@@ -26,8 +27,9 @@ class Lock
|
|||||||
void unlock();
|
void unlock();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#ifdef HAS_FREE_RTOS
|
||||||
SemaphoreHandle_t handle;
|
SemaphoreHandle_t handle;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace concurrency
|
} // namespace concurrency
|
||||||
|
|||||||
@@ -1,19 +1,85 @@
|
|||||||
#include "NotifiedWorkerThread.h"
|
#include "NotifiedWorkerThread.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
namespace concurrency {
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
static bool debugNotification;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify this thread so it can run
|
* Notify this thread so it can run
|
||||||
*/
|
*/
|
||||||
void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action)
|
bool NotifiedWorkerThread::notify(uint32_t v, bool overwrite)
|
||||||
{
|
{
|
||||||
xTaskNotify(taskHandle, v, action);
|
bool r = notifyCommon(v, overwrite);
|
||||||
|
|
||||||
|
if (r)
|
||||||
|
mainDelay.interrupt();
|
||||||
|
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifiedWorkerThread::block()
|
/**
|
||||||
|
* Notify this thread so it can run
|
||||||
|
*/
|
||||||
|
IRAM_ATTR bool NotifiedWorkerThread::notifyCommon(uint32_t v, bool overwrite)
|
||||||
{
|
{
|
||||||
xTaskNotifyWait(0, // don't clear notification on entry
|
if (overwrite || notification == 0) {
|
||||||
clearOnRead, ¬ification, portMAX_DELAY); // Wait forever
|
enabled = true;
|
||||||
|
setInterval(0); // Run ASAP
|
||||||
|
|
||||||
|
notification = v;
|
||||||
|
if (debugNotification)
|
||||||
|
DEBUG_MSG("setting notification %d\n", v);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (debugNotification)
|
||||||
|
DEBUG_MSG("dropping notification %d\n", v);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace concurrency
|
/**
|
||||||
|
* Notify from an ISR
|
||||||
|
*
|
||||||
|
* This must be inline or IRAM_ATTR on ESP32
|
||||||
|
*/
|
||||||
|
IRAM_ATTR bool NotifiedWorkerThread::notifyFromISR(BaseType_t *highPriWoken, uint32_t v, bool overwrite)
|
||||||
|
{
|
||||||
|
bool r = notifyCommon(v, overwrite);
|
||||||
|
if (r)
|
||||||
|
mainDelay.interruptFromISR(highPriWoken);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a notification to fire in delay msecs
|
||||||
|
*/
|
||||||
|
bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrite)
|
||||||
|
{
|
||||||
|
bool didIt = notify(v, overwrite);
|
||||||
|
|
||||||
|
if (didIt) { // If we didn't already have something queued, override the delay to be larger
|
||||||
|
setIntervalFromNow(delay); // a new version of setInterval relative to the current time
|
||||||
|
if (debugNotification)
|
||||||
|
DEBUG_MSG("delaying notification %u\n", delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return didIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t NotifiedWorkerThread::runOnce()
|
||||||
|
{
|
||||||
|
auto n = notification;
|
||||||
|
enabled = false; // Only run once per notification
|
||||||
|
notification = 0; // clear notification
|
||||||
|
if (n) {
|
||||||
|
onNotify(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RUN_SAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
@@ -1,47 +1,50 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "WorkerThread.h"
|
#include "OSThread.h"
|
||||||
|
|
||||||
namespace concurrency {
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A worker thread that waits on a freertos notification
|
* @brief A worker thread that waits on a freertos notification
|
||||||
*/
|
*/
|
||||||
class NotifiedWorkerThread : public WorkerThread
|
class NotifiedWorkerThread : public OSThread
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The notification that was most recently used to wake the thread. Read from runOnce()
|
||||||
|
*/
|
||||||
|
uint32_t notification = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
NotifiedWorkerThread(const char *name) : OSThread(name) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify this thread so it can run
|
* Notify this thread so it can run
|
||||||
*/
|
*/
|
||||||
void notify(uint32_t v = 0, eNotifyAction action = eNoAction);
|
bool notify(uint32_t v, bool overwrite);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify from an ISR
|
* Notify from an ISR
|
||||||
*
|
*
|
||||||
* This must be inline or IRAM_ATTR on ESP32
|
* This must be inline or IRAM_ATTR on ESP32
|
||||||
*/
|
*/
|
||||||
inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction)
|
bool notifyFromISR(BaseType_t *highPriWoken, uint32_t v, bool overwrite);
|
||||||
{
|
|
||||||
xTaskNotifyFromISR(taskHandle, v, action, highPriWoken);
|
/**
|
||||||
}
|
* Schedule a notification to fire in delay msecs
|
||||||
|
*/
|
||||||
|
bool notifyLater(uint32_t delay, uint32_t v, bool overwrite);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
virtual void onNotify(uint32_t notification) = 0;
|
||||||
* The notification that was most recently used to wake the thread. Read from loop()
|
|
||||||
*/
|
|
||||||
uint32_t notification = 0;
|
|
||||||
|
|
||||||
/**
|
virtual int32_t runOnce();
|
||||||
* What notification bits should be cleared just after we read and return them in notification?
|
|
||||||
*
|
|
||||||
* Defaults to clear all of them.
|
|
||||||
*/
|
|
||||||
uint32_t clearOnRead = UINT32_MAX;
|
|
||||||
|
|
||||||
|
private:
|
||||||
/**
|
/**
|
||||||
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
|
* Notify this thread so it can run
|
||||||
*/
|
*/
|
||||||
virtual void block();
|
bool notifyCommon(uint32_t v, bool overwrite);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace concurrency
|
} // namespace concurrency
|
||||||
|
|||||||
79
src/concurrency/OSThread.cpp
Normal file
79
src/concurrency/OSThread.cpp
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#include "OSThread.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Show debugging info for disabled threads
|
||||||
|
bool OSThread::showDisabled;
|
||||||
|
|
||||||
|
/// Show debugging info for threads when we run them
|
||||||
|
bool OSThread::showRun = false;
|
||||||
|
|
||||||
|
/// Show debugging info for threads we decide not to run;
|
||||||
|
bool OSThread::showWaiting = false;
|
||||||
|
|
||||||
|
ThreadController mainController, timerController;
|
||||||
|
InterruptableDelay mainDelay;
|
||||||
|
|
||||||
|
void OSThread::setup()
|
||||||
|
{
|
||||||
|
mainController.ThreadName = "mainController";
|
||||||
|
timerController.ThreadName = "timerController";
|
||||||
|
}
|
||||||
|
|
||||||
|
OSThread::OSThread(const char *_name, uint32_t period, ThreadController *_controller)
|
||||||
|
: Thread(NULL, period), controller(_controller)
|
||||||
|
{
|
||||||
|
ThreadName = _name;
|
||||||
|
|
||||||
|
if (controller)
|
||||||
|
controller->add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
OSThread::~OSThread()
|
||||||
|
{
|
||||||
|
if (controller)
|
||||||
|
controller->remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait a specified number msecs starting from the current time (rather than the last time we were run)
|
||||||
|
*/
|
||||||
|
void OSThread::setIntervalFromNow(unsigned long _interval)
|
||||||
|
{
|
||||||
|
// Save interval
|
||||||
|
interval = _interval;
|
||||||
|
|
||||||
|
// Cache the next run based on the last_run
|
||||||
|
_cached_next_run = millis() + interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OSThread::shouldRun(unsigned long time)
|
||||||
|
{
|
||||||
|
bool r = Thread::shouldRun(time);
|
||||||
|
|
||||||
|
if (showRun && r)
|
||||||
|
DEBUG_MSG("Thread %s: run\n", ThreadName.c_str());
|
||||||
|
|
||||||
|
if (showWaiting && enabled && !r)
|
||||||
|
DEBUG_MSG("Thread %s: wait %lu\n", ThreadName.c_str(), interval);
|
||||||
|
|
||||||
|
if (showDisabled && !enabled)
|
||||||
|
DEBUG_MSG("Thread %s: disabled\n", ThreadName.c_str());
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OSThread::run()
|
||||||
|
{
|
||||||
|
auto newDelay = runOnce();
|
||||||
|
|
||||||
|
runned();
|
||||||
|
|
||||||
|
if (newDelay >= 0)
|
||||||
|
setInterval(newDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
70
src/concurrency/OSThread.h
Normal file
70
src/concurrency/OSThread.h
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "Thread.h"
|
||||||
|
#include "ThreadController.h"
|
||||||
|
#include "concurrency/InterruptableDelay.h"
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
extern ThreadController mainController, timerController;
|
||||||
|
extern InterruptableDelay mainDelay;
|
||||||
|
|
||||||
|
#define RUN_SAME -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Base threading
|
||||||
|
*
|
||||||
|
* This is a pseudo threading layer that is super easy to port, well suited to our slow network and very ram & power efficient.
|
||||||
|
*
|
||||||
|
* TODO FIXME @geeksville
|
||||||
|
*
|
||||||
|
* move more things into OSThreads
|
||||||
|
* remove lock/lockguard
|
||||||
|
*
|
||||||
|
* move typedQueue into concurrency
|
||||||
|
* remove freertos from typedqueue
|
||||||
|
*/
|
||||||
|
class OSThread : public Thread
|
||||||
|
{
|
||||||
|
ThreadController *controller;
|
||||||
|
|
||||||
|
/// Show debugging info for disabled threads
|
||||||
|
static bool showDisabled;
|
||||||
|
|
||||||
|
/// Show debugging info for threads when we run them
|
||||||
|
static bool showRun;
|
||||||
|
|
||||||
|
/// Show debugging info for threads we decide not to run;
|
||||||
|
static bool showWaiting;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OSThread(const char *name, uint32_t period = 0, ThreadController *controller = &mainController);
|
||||||
|
|
||||||
|
virtual ~OSThread();
|
||||||
|
|
||||||
|
virtual bool shouldRun(unsigned long time);
|
||||||
|
|
||||||
|
static void setup();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait a specified number msecs starting from the current time (rather than the last time we were run)
|
||||||
|
*/
|
||||||
|
void setIntervalFromNow(unsigned long _interval);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* The method that will be called each time our thread gets a chance to run
|
||||||
|
*
|
||||||
|
* Returns desired period for next invocation (or RUN_SAME for no change)
|
||||||
|
*/
|
||||||
|
virtual int32_t runOnce() = 0;
|
||||||
|
|
||||||
|
// Do not override this
|
||||||
|
virtual void run();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
@@ -1,26 +1,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "PeriodicTask.h"
|
#include "concurrency/OSThread.h"
|
||||||
|
|
||||||
namespace concurrency {
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Periodically invoke a callback. This just provides C-style callback conventions
|
* @brief Periodically invoke a callback. This just provides C-style callback conventions
|
||||||
* rather than a virtual function - FIXME, remove?
|
* rather than a virtual function - FIXME, remove?
|
||||||
*/
|
*/
|
||||||
class Periodic : public PeriodicTask
|
class Periodic : public OSThread
|
||||||
{
|
{
|
||||||
uint32_t (*callback)();
|
int32_t (*callback)();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||||
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
|
Periodic(const char *name, int32_t (*_callback)()) : OSThread(name), callback(_callback) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void doTask() {
|
int32_t runOnce() { return callback(); }
|
||||||
uint32_t p = callback();
|
|
||||||
setPeriod(p);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace concurrency
|
} // namespace concurrency
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
#include "PeriodicScheduler.h"
|
|
||||||
#include "PeriodicTask.h"
|
|
||||||
#include "LockGuard.h"
|
|
||||||
#include "../timing.h"
|
|
||||||
|
|
||||||
namespace concurrency {
|
|
||||||
|
|
||||||
/// call this from loop
|
|
||||||
void PeriodicScheduler::loop()
|
|
||||||
{
|
|
||||||
LockGuard lg(&lock);
|
|
||||||
|
|
||||||
uint32_t now = timing::millis();
|
|
||||||
for (auto t : tasks) {
|
|
||||||
if (t->period && (now - t->lastMsec) >= t->period) {
|
|
||||||
|
|
||||||
t->doTask();
|
|
||||||
t->lastMsec = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeriodicScheduler::schedule(PeriodicTask *t)
|
|
||||||
{
|
|
||||||
LockGuard lg(&lock);
|
|
||||||
tasks.insert(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeriodicScheduler::unschedule(PeriodicTask *t)
|
|
||||||
{
|
|
||||||
LockGuard lg(&lock);
|
|
||||||
tasks.erase(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace concurrency
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Lock.h"
|
|
||||||
#include <cstdint>
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
namespace concurrency {
|
|
||||||
|
|
||||||
class PeriodicTask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Runs all PeriodicTasks in the system. Currently called from main loop()
|
|
||||||
* but eventually should be its own thread blocked on a freertos timer.
|
|
||||||
*/
|
|
||||||
class PeriodicScheduler
|
|
||||||
{
|
|
||||||
friend class PeriodicTask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This really should be some form of heap, and when the period gets changed on a task it should get
|
|
||||||
* rescheduled in that heap. Currently it is just a dumb array and everytime we run loop() we check
|
|
||||||
* _every_ tasks. If it was a heap we'd only have to check the first task.
|
|
||||||
*/
|
|
||||||
std::unordered_set<PeriodicTask *> tasks;
|
|
||||||
|
|
||||||
// Protects the above variables.
|
|
||||||
Lock lock;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Run any next tasks which are due for execution
|
|
||||||
void loop();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void schedule(PeriodicTask *t);
|
|
||||||
void unschedule(PeriodicTask *t);
|
|
||||||
};
|
|
||||||
|
|
||||||
extern PeriodicScheduler periodicScheduler;
|
|
||||||
|
|
||||||
} // namespace concurrency
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#include "PeriodicTask.h"
|
|
||||||
#include "Periodic.h"
|
|
||||||
#include "LockGuard.h"
|
|
||||||
|
|
||||||
namespace concurrency {
|
|
||||||
|
|
||||||
PeriodicScheduler periodicScheduler;
|
|
||||||
|
|
||||||
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
|
|
||||||
|
|
||||||
void PeriodicTask::setup()
|
|
||||||
{
|
|
||||||
periodicScheduler.schedule(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace concurrency
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "PeriodicScheduler.h"
|
|
||||||
#include "timing.h"
|
|
||||||
|
|
||||||
namespace concurrency {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A base class for tasks that want their doTask() method invoked periodically
|
|
||||||
*
|
|
||||||
* @todo currently just syntatic sugar for polling in loop (you must call .loop), but eventually
|
|
||||||
* generalize with the freertos scheduler so we can save lots of power by having everything either in
|
|
||||||
* something like this or triggered off of an irq.
|
|
||||||
*/
|
|
||||||
class PeriodicTask
|
|
||||||
{
|
|
||||||
friend class PeriodicScheduler;
|
|
||||||
|
|
||||||
uint32_t lastMsec = 0;
|
|
||||||
uint32_t period = 1; // call soon after creation
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual ~PeriodicTask() { periodicScheduler.unschedule(this); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor (will schedule with the global PeriodicScheduler)
|
|
||||||
*/
|
|
||||||
PeriodicTask(uint32_t initialPeriod = 1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MUST be be called once at startup (but after threading is running - i.e. not from a constructor)
|
|
||||||
*/
|
|
||||||
void setup();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
|
|
||||||
* While zero this task is disabled and will not run
|
|
||||||
*/
|
|
||||||
void setPeriod(uint32_t p)
|
|
||||||
{
|
|
||||||
lastMsec = timing::millis(); // reset starting from now
|
|
||||||
period = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getPeriod() const { return period; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Syntatic sugar for suspending tasks
|
|
||||||
*/
|
|
||||||
void disable() { setPeriod(0); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void doTask() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace concurrency
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#include "Thread.h"
|
|
||||||
#include "timing.h"
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
#include "esp_task_wdt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace concurrency
|
|
||||||
{
|
|
||||||
|
|
||||||
void Thread::start(const char *name, size_t stackSize, uint32_t priority)
|
|
||||||
{
|
|
||||||
auto r = xTaskCreate(callRun, name, stackSize, this, priority, &taskHandle);
|
|
||||||
assert(r == pdPASS);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Thread::callRun(void *_this)
|
|
||||||
{
|
|
||||||
((Thread *)_this)->doRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Thread::serviceWatchdog()
|
|
||||||
{
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
esp_task_wdt_reset();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Thread::startWatchdog()
|
|
||||||
{
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
auto r = esp_task_wdt_add(taskHandle);
|
|
||||||
assert(r == ESP_OK);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Thread::stopWatchdog()
|
|
||||||
{
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
auto r = esp_task_wdt_delete(taskHandle);
|
|
||||||
assert(r == ESP_OK);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace concurrency
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "freertosinc.h"
|
|
||||||
|
|
||||||
namespace concurrency {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Base threading
|
|
||||||
*/
|
|
||||||
class Thread
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
TaskHandle_t taskHandle = NULL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set this to true to ask thread to cleanly exit asap
|
|
||||||
*/
|
|
||||||
volatile bool wantExit = false;
|
|
||||||
|
|
||||||
public:
|
|
||||||
void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY);
|
|
||||||
|
|
||||||
virtual ~Thread() { vTaskDelete(taskHandle); }
|
|
||||||
|
|
||||||
uint32_t getStackHighwaterMark() { return uxTaskGetStackHighWaterMark(taskHandle); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* The method that will be called when start is called.
|
|
||||||
*/
|
|
||||||
virtual void doRun() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All thread run methods must periodically call serviceWatchdog, or the system will declare them hung and panic.
|
|
||||||
*
|
|
||||||
* this only applies after startWatchdog() has been called. If you need to sleep for a long time call stopWatchdog()
|
|
||||||
*/
|
|
||||||
void serviceWatchdog();
|
|
||||||
void startWatchdog();
|
|
||||||
void stopWatchdog();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void callRun(void *_this);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace concurrency
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#include "WorkerThread.h"
|
|
||||||
#include "timing.h"
|
|
||||||
|
|
||||||
namespace concurrency {
|
|
||||||
|
|
||||||
void WorkerThread::doRun()
|
|
||||||
{
|
|
||||||
startWatchdog();
|
|
||||||
|
|
||||||
while (!wantExit) {
|
|
||||||
stopWatchdog();
|
|
||||||
block();
|
|
||||||
startWatchdog();
|
|
||||||
|
|
||||||
// no need - startWatchdog is guaranteed to give us one full watchdog interval
|
|
||||||
// serviceWatchdog(); // Let our loop worker have one full watchdog interval (at least) to run
|
|
||||||
|
|
||||||
#ifdef DEBUG_STACK
|
|
||||||
static uint32_t lastPrint = 0;
|
|
||||||
if (timing::millis() - lastPrint > 10 * 1000L) {
|
|
||||||
lastPrint = timing::millis();
|
|
||||||
meshtastic::printThreadInfo("net");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
stopWatchdog();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace concurrency
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Thread.h"
|
|
||||||
|
|
||||||
namespace concurrency {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief This wraps threading (FreeRTOS for now) with a blocking API intended for efficiently converting
|
|
||||||
* old-school arduino loop() code. Use as a mixin base class for the classes you want to convert.
|
|
||||||
*
|
|
||||||
* @link https://www.freertos.org/RTOS_Task_Notification_As_Mailbox.html
|
|
||||||
*/
|
|
||||||
class WorkerThread : public Thread
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
|
|
||||||
*/
|
|
||||||
virtual void block() = 0;
|
|
||||||
|
|
||||||
virtual void loop() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The method that will be called when start is called.
|
|
||||||
*/
|
|
||||||
virtual void doRun();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace concurrency
|
|
||||||
@@ -55,24 +55,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
/// Convert a preprocessor name into a quoted string and if that string is empty use "unset"
|
/// Convert a preprocessor name into a quoted string and if that string is empty use "unset"
|
||||||
#define optstr(s) (xstr(s)[0] ? xstr(s) : "unset")
|
#define optstr(s) (xstr(s)[0] ? xstr(s) : "unset")
|
||||||
|
|
||||||
#ifdef NRF52_SERIES // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be
|
#ifdef PORTDUINO
|
||||||
// board specific
|
|
||||||
|
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
|
||||||
|
|
||||||
|
#elif defined(NRF52_SERIES) // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be
|
||||||
|
// board specific
|
||||||
|
|
||||||
//
|
//
|
||||||
// Standard definitions for NRF52 targets
|
// Standard definitions for NRF52 targets
|
||||||
//
|
//
|
||||||
|
|
||||||
// Nop definition for these attributes - not used on NRF52
|
|
||||||
#define EXT_RAM_ATTR
|
|
||||||
#define IRAM_ATTR
|
|
||||||
|
|
||||||
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
|
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
|
||||||
|
|
||||||
// We bind to the GPS using variant.h instead for this platform (Serial1)
|
// We bind to the GPS using variant.h instead for this platform (Serial1)
|
||||||
|
|
||||||
// FIXME, not yet ready for NRF52
|
|
||||||
#define RTC_DATA_ATTR
|
|
||||||
|
|
||||||
#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK
|
#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK
|
||||||
|
|
||||||
// If the variant filed defines as standard button
|
// If the variant filed defines as standard button
|
||||||
@@ -80,6 +77,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define BUTTON_PIN PIN_BUTTON1
|
#define BUTTON_PIN PIN_BUTTON1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PIN_BUTTON2
|
||||||
|
#define BUTTON_PIN_ALT PIN_BUTTON2
|
||||||
|
#endif
|
||||||
|
|
||||||
// FIXME, use variant.h defs for all of this!!! (even on the ESP32 targets)
|
// FIXME, use variant.h defs for all of this!!! (even on the ESP32 targets)
|
||||||
#elif defined(CubeCell_BoardPlus)
|
#elif defined(CubeCell_BoardPlus)
|
||||||
|
|
||||||
@@ -89,9 +90,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
|
#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth)
|
||||||
|
|
||||||
// FIXME, not yet ready for NRF52
|
|
||||||
#define RTC_DATA_ATTR
|
|
||||||
|
|
||||||
#define LED_PIN -1 // FIXME totally bogus
|
#define LED_PIN -1 // FIXME totally bogus
|
||||||
#define BUTTON_PIN -1
|
#define BUTTON_PIN -1
|
||||||
|
|
||||||
@@ -101,6 +99,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// Standard definitions for ESP32 targets
|
// Standard definitions for ESP32 targets
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#define HAS_WIFI
|
||||||
|
|
||||||
#define GPS_SERIAL_NUM 1
|
#define GPS_SERIAL_NUM 1
|
||||||
#define GPS_RX_PIN 34
|
#define GPS_RX_PIN 34
|
||||||
#ifdef USE_JTAG
|
#ifdef USE_JTAG
|
||||||
@@ -123,6 +123,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//
|
||||||
|
// Standard definitions for !ESP32 targets
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifdef NO_ESP32
|
||||||
|
// Nop definition for these attributes - not used on NRF52
|
||||||
|
#define EXT_RAM_ATTR
|
||||||
|
#define IRAM_ATTR
|
||||||
|
#define RTC_DATA_ATTR
|
||||||
|
#endif
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// OLED
|
// OLED
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -137,9 +148,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// devices. Comment this out to not rotate screen 180 degrees.
|
// devices. Comment this out to not rotate screen 180 degrees.
|
||||||
#define FLIP_SCREEN_VERTICALLY
|
#define FLIP_SCREEN_VERTICALLY
|
||||||
|
|
||||||
// DEBUG LED
|
|
||||||
#define LED_INVERTED 0 // define as 1 if LED is active low (on)
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// GPS
|
// GPS
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -158,6 +166,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define BUTTON_PIN 38 // The middle button GPIO on the T-Beam
|
#define BUTTON_PIN 38 // The middle button GPIO on the T-Beam
|
||||||
#define BUTTON_PIN_ALT 13 // Alternate GPIO for an external button if needed
|
#define BUTTON_PIN_ALT 13 // Alternate GPIO for an external button if needed
|
||||||
|
|
||||||
|
#define LED_INVERTED 1
|
||||||
|
#define LED_PIN 4 // Newer tbeams (1.1) have an extra led on GPIO4
|
||||||
|
|
||||||
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
|
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
|
||||||
// not found then probe for SX1262
|
// not found then probe for SX1262
|
||||||
#define USE_RF95
|
#define USE_RF95
|
||||||
@@ -179,8 +190,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// code)
|
// code)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Leave undefined to disable our PMU IRQ handler
|
// Leave undefined to disable our PMU IRQ handler. DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts
|
||||||
#define PMU_IRQ 35
|
// and waking from light sleep
|
||||||
|
// #define PMU_IRQ 35
|
||||||
#define AXP192_SLAVE_ADDRESS 0x34
|
#define AXP192_SLAVE_ADDRESS 0x34
|
||||||
|
|
||||||
#elif defined(TBEAM_V07)
|
#elif defined(TBEAM_V07)
|
||||||
@@ -195,8 +207,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define BUTTON_PIN 39
|
#define BUTTON_PIN 39
|
||||||
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||||
|
|
||||||
#define USE_RF95
|
|
||||||
|
|
||||||
#define USE_RF95
|
#define USE_RF95
|
||||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||||
#define LORA_RESET 23
|
#define LORA_RESET 23
|
||||||
@@ -338,6 +348,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
#define HW_VENDOR "nrf52unknown" // FIXME - unknown nrf52 board
|
#define HW_VENDOR "nrf52unknown" // FIXME - unknown nrf52 board
|
||||||
|
|
||||||
|
#elif PORTDUINO
|
||||||
|
|
||||||
|
#define HW_VENDOR "portduino"
|
||||||
|
|
||||||
|
#define USE_SIM_RADIO
|
||||||
|
|
||||||
|
#define USE_RF95
|
||||||
|
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||||
|
#define LORA_RESET 23
|
||||||
|
#define LORA_DIO1 33 // Not really used
|
||||||
|
#define LORA_DIO2 32 // Not really used
|
||||||
|
|
||||||
|
// Fake SPI device selections
|
||||||
|
#define RF95_SCK 5
|
||||||
|
#define RF95_MISO 19
|
||||||
|
#define RF95_MOSI 27
|
||||||
|
#define RF95_NSS 18
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// DEBUG LED
|
||||||
|
#ifndef LED_INVERTED
|
||||||
|
#define LED_INVERTED 0 // define as 1 if LED is active low (on)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_RF95
|
#ifdef USE_RF95
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#include "../concurrency/LockGuard.h"
|
#include "../concurrency/LockGuard.h"
|
||||||
#include "../timing.h"
|
|
||||||
#include "BluetoothSoftwareUpdate.h"
|
#include "BluetoothSoftwareUpdate.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "RadioLibInterface.h"
|
#include "RadioLibInterface.h"
|
||||||
@@ -73,7 +72,7 @@ int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
|||||||
crc.update(data, len);
|
crc.update(data, len);
|
||||||
Update.write(data, len);
|
Update.write(data, len);
|
||||||
updateActualSize += len;
|
updateActualSize += len;
|
||||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); // Not exactly correct, but we want to force the device to not sleep now
|
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -102,7 +101,7 @@ int update_crc32_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble
|
|||||||
} else {
|
} else {
|
||||||
if (Update.end()) {
|
if (Update.end()) {
|
||||||
DEBUG_MSG("OTA done, rebooting in 5 seconds!\n");
|
DEBUG_MSG("OTA done, rebooting in 5 seconds!\n");
|
||||||
rebootAtMsec = timing::millis() + 5000;
|
rebootAtMsec = millis() + 5000;
|
||||||
} else {
|
} else {
|
||||||
DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError());
|
DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError());
|
||||||
}
|
}
|
||||||
@@ -128,7 +127,7 @@ int update_result_callback(uint16_t conn_handle, uint16_t attr_handle, struct bl
|
|||||||
|
|
||||||
void bluetoothRebootCheck()
|
void bluetoothRebootCheck()
|
||||||
{
|
{
|
||||||
if (rebootAtMsec && timing::millis() > rebootAtMsec) {
|
if (rebootAtMsec && millis() > rebootAtMsec) {
|
||||||
DEBUG_MSG("Rebooting for update\n");
|
DEBUG_MSG("Rebooting for update\n");
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,25 +33,36 @@ void WiFiServerAPI::loop()
|
|||||||
if (client.connected()) {
|
if (client.connected()) {
|
||||||
StreamAPI::loop();
|
StreamAPI::loop();
|
||||||
} else {
|
} else {
|
||||||
DEBUG_MSG("Client dropped connection, closing UDP server\n");
|
DEBUG_MSG("Client dropped connection, closing TCP server\n");
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MESHTASTIC_PORTNUM 4403
|
#define MESHTASTIC_PORTNUM 4403
|
||||||
|
|
||||||
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM) {}
|
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM), concurrency::OSThread("ApiServer") {}
|
||||||
|
|
||||||
void WiFiServerPort::init()
|
void WiFiServerPort::init()
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Listening on TCP port %d\n", MESHTASTIC_PORTNUM);
|
DEBUG_MSG("API server sistening on TCP port %d\n", MESHTASTIC_PORTNUM);
|
||||||
begin();
|
begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiServerPort::loop()
|
int32_t WiFiServerPort::runOnce()
|
||||||
{
|
{
|
||||||
auto client = available();
|
auto client = available();
|
||||||
if (client) {
|
if (client) {
|
||||||
new WiFiServerAPI(client);
|
// Close any previous connection (see FIXME in header file)
|
||||||
|
if (openAPI)
|
||||||
|
delete openAPI;
|
||||||
|
|
||||||
|
openAPI = new WiFiServerAPI(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (openAPI) {
|
||||||
|
// Allow idle processing so the API can read from its incoming stream
|
||||||
|
openAPI->loop();
|
||||||
|
return 0; // run fast while our API server is running
|
||||||
|
} else
|
||||||
|
return 100; // only check occasionally for incoming connections
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "StreamAPI.h"
|
#include "StreamAPI.h"
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,12 +28,19 @@ class WiFiServerAPI : public StreamAPI
|
|||||||
/**
|
/**
|
||||||
* Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed
|
* Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed
|
||||||
*/
|
*/
|
||||||
class WiFiServerPort : public WiFiServer
|
class WiFiServerPort : public WiFiServer, private concurrency::OSThread
|
||||||
{
|
{
|
||||||
|
/** The currently open port
|
||||||
|
*
|
||||||
|
* FIXME: We currently only allow one open TCP connection at a time, because we depend on the loop() call in this class to
|
||||||
|
* delegate to the worker. Once coroutines are implemented we can relax this restriction.
|
||||||
|
*/
|
||||||
|
WiFiServerAPI *openAPI = NULL;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WiFiServerPort();
|
WiFiServerPort();
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void loop();
|
int32_t runOnce();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,22 +3,32 @@
|
|||||||
// The FreeRTOS includes are in a different directory on ESP32 and I can't figure out how to make that work with platformio gcc
|
// The FreeRTOS includes are in a different directory on ESP32 and I can't figure out how to make that work with platformio gcc
|
||||||
// options so this is my quick hack to make things work
|
// options so this is my quick hack to make things work
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
#define HAS_FREE_RTOS
|
#define HAS_FREE_RTOS
|
||||||
|
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/queue.h>
|
#include <freertos/queue.h>
|
||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
#else
|
#endif
|
||||||
// not yet supported on cubecell
|
|
||||||
#ifndef CubeCell_BoardPlus
|
#if defined(ARDUINO_NRF52_ADAFRUIT)
|
||||||
#define HAS_FREE_RTOS
|
#define HAS_FREE_RTOS
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
#include <queue.h>
|
#include <queue.h>
|
||||||
#include <semphr.h>
|
#include <semphr.h>
|
||||||
#include <task.h>
|
#include <task.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_FREE_RTOS
|
||||||
|
|
||||||
|
// Include real FreeRTOS defs above
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
// Include placeholder fake FreeRTOS defs
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
typedef uint32_t TickType_t;
|
typedef uint32_t TickType_t;
|
||||||
@@ -26,5 +36,12 @@ typedef uint32_t BaseType_t;
|
|||||||
|
|
||||||
#define portMAX_DELAY UINT32_MAX
|
#define portMAX_DELAY UINT32_MAX
|
||||||
|
|
||||||
|
#define tskIDLE_PRIORITY 0
|
||||||
|
#define configMAX_PRIORITIES 10 // Highest priority level
|
||||||
|
|
||||||
|
// Don't do anything on non free rtos platforms when done with the ISR
|
||||||
|
#define portYIELD_FROM_ISR(x)
|
||||||
|
|
||||||
|
enum eNotifyAction { eNoAction, eSetValueWithoutOverwrite, eSetValueWithOverwrite };
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
87
src/gps/Air530GPS.cpp
Normal file
87
src/gps/Air530GPS.cpp
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#include "Air530GPS.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Helpful translations from the Air530 GPS datasheet
|
||||||
|
|
||||||
|
Sat acquision mode
|
||||||
|
捕获电流值@3.3v 42.6 mA
|
||||||
|
|
||||||
|
sat tracking mode
|
||||||
|
跟踪电流值@3.3v 36.7 mA
|
||||||
|
|
||||||
|
Low power mode
|
||||||
|
低功耗模式@3.3V 0.85 mA
|
||||||
|
(发送指令:$PGKC051,0)
|
||||||
|
|
||||||
|
Super low power mode
|
||||||
|
超低功耗模式@3.3V 31 uA
|
||||||
|
(发送指令:$PGKC105,4)
|
||||||
|
|
||||||
|
To exit sleep use WAKE pin
|
||||||
|
|
||||||
|
Commands to enter sleep
|
||||||
|
6、Command: 105
|
||||||
|
进入周期性低功耗模式
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
Arg1: “0”,正常运行模式 (normal mode)
|
||||||
|
“1”,周期超低功耗跟踪模式,需要拉高 WAKE 来唤醒 (periodic low power tracking mode - keeps sat positions, use wake to wake up)
|
||||||
|
“2”,周期低功耗模式 (periodic low power mode)
|
||||||
|
“4”,直接进入超低功耗跟踪模式,需要拉高 WAKE 来唤醒 (super low power consumption mode immediately, need WAKE to resume)
|
||||||
|
“8”,自动低功耗模式,可以通过串口唤醒 (automatic low power mode, wake by sending characters to serial port)
|
||||||
|
“9”, 自动超低功耗跟踪模式,需要拉高 WAKE 来唤醒 (automatic low power tracking when possible, need wake pin to resume)
|
||||||
|
|
||||||
|
(Arg 2 & 3 only valid if Arg1 is "1" or "2")
|
||||||
|
Arg2:运行时间(毫秒),在 Arg1 为 1、2 的周期模式下,此参数起作用
|
||||||
|
ON time in msecs
|
||||||
|
|
||||||
|
Arg3:睡眠时间(毫秒),在 Arg1 为 1、2 的周期模式下,此参数起作用
|
||||||
|
Sleep time in msecs
|
||||||
|
|
||||||
|
Example:
|
||||||
|
$PGKC105,8*3F<CR><LF>
|
||||||
|
This will set automatic low power mode with waking when we send chars to the serial port. Possibly do this as soon as we get a
|
||||||
|
new location. When we wake again in a minute we send a character to wake up.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
void Air530GPS::sendCommand(const char *cmd) {
|
||||||
|
uint8_t sum = 0;
|
||||||
|
|
||||||
|
// Skip the $
|
||||||
|
assert(cmd[0] == '$');
|
||||||
|
const char *p = cmd + 1;
|
||||||
|
while(*p)
|
||||||
|
sum ^= *p++;
|
||||||
|
|
||||||
|
assert(_serial_gps);
|
||||||
|
|
||||||
|
_serial_gps->write(cmd);
|
||||||
|
_serial_gps->printf("*%02x\r\n", sum);
|
||||||
|
|
||||||
|
// DEBUG_MSG("xsum %02x\n", sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Air530GPS::sleep() {
|
||||||
|
#ifdef PIN_GPS_WAKE
|
||||||
|
digitalWrite(PIN_GPS_WAKE, 0);
|
||||||
|
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||||
|
sendCommand("$PGKC105,4");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// wake the GPS into normal operation mode
|
||||||
|
void Air530GPS::wake()
|
||||||
|
{
|
||||||
|
#if 1
|
||||||
|
#ifdef PIN_GPS_WAKE
|
||||||
|
digitalWrite(PIN_GPS_WAKE, 1);
|
||||||
|
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
// For power testing - keep GPS sleeping forever
|
||||||
|
sleep();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
22
src/gps/Air530GPS.h
Normal file
22
src/gps/Air530GPS.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "NMEAGPS.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A gps class thatreads from a NMEA GPS stream (and FIXME - eventually keeps the gps powered down except when reading)
|
||||||
|
*
|
||||||
|
* When new data is available it will notify observers.
|
||||||
|
*/
|
||||||
|
class Air530GPS : public NMEAGPS
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
/// If possible force the GPS into sleep/low power mode
|
||||||
|
virtual void sleep();
|
||||||
|
|
||||||
|
/// wake the GPS into normal operation mode
|
||||||
|
virtual void wake();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Send a NMEA cmd with checksum
|
||||||
|
void sendCommand(const char *str);
|
||||||
|
};
|
||||||
233
src/gps/GPS.cpp
233
src/gps/GPS.cpp
@@ -1,9 +1,10 @@
|
|||||||
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "timing.h"
|
#include "sleep.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
// If we have a serial GPS port it will not be null
|
// If we have a serial GPS port it will not be null
|
||||||
#ifdef GPS_RX_PIN
|
#ifdef GPS_RX_PIN
|
||||||
@@ -22,67 +23,209 @@ uint8_t GPS::i2cAddress = GPS_I2C_ADDRESS;
|
|||||||
uint8_t GPS::i2cAddress = 0;
|
uint8_t GPS::i2cAddress = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep
|
|
||||||
|
|
||||||
GPS *gps;
|
GPS *gps;
|
||||||
|
|
||||||
// stuff that really should be in in the instance instead...
|
bool GPS::setup()
|
||||||
static uint32_t
|
|
||||||
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
|
|
||||||
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
|
|
||||||
|
|
||||||
void readFromRTC()
|
|
||||||
{
|
{
|
||||||
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
setAwake(true); // Wake GPS power before doing any init
|
||||||
|
bool ok = setupGPS();
|
||||||
|
|
||||||
if (!gettimeofday(&tv, NULL)) {
|
if (ok)
|
||||||
uint32_t now = timing::millis();
|
notifySleepObserver.observe(¬ifySleep);
|
||||||
|
|
||||||
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
|
return ok;
|
||||||
timeStartMsec = now;
|
}
|
||||||
zeroOffsetSecs = tv.tv_sec;
|
|
||||||
|
/// Record that we have a GPS
|
||||||
|
void GPS::setConnected()
|
||||||
|
{
|
||||||
|
if (!hasGPS) {
|
||||||
|
hasGPS = true;
|
||||||
|
shouldPublish = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
void GPS::setNumSatellites(uint8_t n)
|
||||||
void perhapsSetRTC(const struct timeval *tv)
|
|
||||||
{
|
{
|
||||||
if (!timeSetFromGPS) {
|
if (n != numSatellites) {
|
||||||
timeSetFromGPS = true;
|
numSatellites = n;
|
||||||
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
shouldPublish = true;
|
||||||
#ifndef NO_ESP32
|
|
||||||
settimeofday(tv, NULL);
|
|
||||||
#else
|
|
||||||
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
|
|
||||||
#endif
|
|
||||||
readFromRTC();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void perhapsSetRTC(struct tm &t)
|
/**
|
||||||
|
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
||||||
|
*
|
||||||
|
* calls sleep/wake
|
||||||
|
*/
|
||||||
|
void GPS::setAwake(bool on)
|
||||||
{
|
{
|
||||||
/* Convert to unix time
|
if (!wakeAllowed && on) {
|
||||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
DEBUG_MSG("Inhibiting because !wakeAllowed\n");
|
||||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
on = false;
|
||||||
*/
|
}
|
||||||
time_t res = mktime(&t);
|
|
||||||
struct timeval tv;
|
|
||||||
tv.tv_sec = res;
|
|
||||||
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
|
|
||||||
|
|
||||||
// DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
if (isAwake != on) {
|
||||||
if (t.tm_year < 0 || t.tm_year >= 300)
|
DEBUG_MSG("WANT GPS=%d\n", on);
|
||||||
DEBUG_MSG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
if (on) {
|
||||||
else
|
lastWakeStartMsec = millis();
|
||||||
perhapsSetRTC(&tv);
|
wake();
|
||||||
|
} else {
|
||||||
|
lastSleepStartMsec = millis();
|
||||||
|
sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
isAwake = on;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t getTime()
|
GpsOperation GPS::getGpsOp() const
|
||||||
{
|
{
|
||||||
return ((timing::millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
auto op = radioConfig.preferences.gps_operation;
|
||||||
|
|
||||||
|
if (op == GpsOperation_GpsOpUnset)
|
||||||
|
op = (radioConfig.preferences.location_share == LocationSharing_LocDisabled) ? GpsOperation_GpsOpTimeOnly
|
||||||
|
: GpsOperation_GpsOpMobile;
|
||||||
|
|
||||||
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t getValidTime()
|
/** Get how long we should stay looking for each aquisition in msecs
|
||||||
|
*/
|
||||||
|
uint32_t GPS::getWakeTime() const
|
||||||
{
|
{
|
||||||
return timeSetFromGPS ? getTime() : 0;
|
uint32_t t = radioConfig.preferences.gps_attempt_time;
|
||||||
|
|
||||||
|
if (t == UINT32_MAX)
|
||||||
|
return t; // already maxint
|
||||||
|
|
||||||
|
if (t == 0)
|
||||||
|
t = 15 * 60; // Allow up to 5 mins for each attempt (probably will be much less if we can find sats)
|
||||||
|
|
||||||
|
t *= 1000; // msecs
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get how long we should sleep between aqusition attempts in msecs
|
||||||
|
*/
|
||||||
|
uint32_t GPS::getSleepTime() const
|
||||||
|
{
|
||||||
|
uint32_t t = radioConfig.preferences.gps_update_interval;
|
||||||
|
|
||||||
|
auto op = getGpsOp();
|
||||||
|
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
||||||
|
if ((gotTime && op == GpsOperation_GpsOpTimeOnly) || (op == GpsOperation_GpsOpDisabled))
|
||||||
|
t = UINT32_MAX; // Sleep forever now
|
||||||
|
|
||||||
|
if (t == UINT32_MAX)
|
||||||
|
return t; // already maxint
|
||||||
|
|
||||||
|
if (t == 0)
|
||||||
|
t = 2 * 60; // 2 mins
|
||||||
|
|
||||||
|
t *= 1000;
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPS::publishUpdate()
|
||||||
|
{
|
||||||
|
if (shouldPublish) {
|
||||||
|
shouldPublish = false;
|
||||||
|
|
||||||
|
DEBUG_MSG("publishing GPS lock=%d\n", hasLock());
|
||||||
|
|
||||||
|
// Notify any status instances that are observing us
|
||||||
|
const meshtastic::GPSStatus status =
|
||||||
|
meshtastic::GPSStatus(hasLock(), isConnected(), latitude, longitude, altitude, dop, heading, numSatellites);
|
||||||
|
newStatus.notifyObservers(&status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t GPS::runOnce()
|
||||||
|
{
|
||||||
|
if (whileIdle()) {
|
||||||
|
// if we have received valid NMEA claim we are connected
|
||||||
|
setConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are overdue for an update, turn on the GPS and at least publish the current status
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
auto sleepTime = getSleepTime();
|
||||||
|
if (!isAwake && sleepTime != UINT32_MAX && (now - lastSleepStartMsec) > sleepTime) {
|
||||||
|
// We now want to be awake - so wake up the GPS
|
||||||
|
setAwake(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we are awake
|
||||||
|
if (isAwake) {
|
||||||
|
// DEBUG_MSG("looking for location\n");
|
||||||
|
if ((now - lastWhileActiveMsec) > 5000) {
|
||||||
|
lastWhileActiveMsec = now;
|
||||||
|
whileActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've already set time from the GPS, no need to ask the GPS
|
||||||
|
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
||||||
|
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
||||||
|
gotTime = true;
|
||||||
|
shouldPublish = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gotLoc = lookForLocation();
|
||||||
|
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
|
||||||
|
hasValidLocation = true;
|
||||||
|
shouldPublish = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've been awake too long - force sleep
|
||||||
|
auto wakeTime = getWakeTime();
|
||||||
|
bool tooLong = wakeTime != UINT32_MAX && (now - lastWakeStartMsec) > wakeTime;
|
||||||
|
|
||||||
|
// Once we get a location we no longer desperately want an update
|
||||||
|
// or if we got a time and we are in GpsOpTimeOnly mode
|
||||||
|
// DEBUG_MSG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime);
|
||||||
|
if ((gotLoc && gotTime) || tooLong || (gotTime && getGpsOp() == GpsOperation_GpsOpTimeOnly)) {
|
||||||
|
|
||||||
|
if (tooLong) {
|
||||||
|
// we didn't get a location during this ack window, therefore declare loss of lock
|
||||||
|
hasValidLocation = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAwake(false);
|
||||||
|
shouldPublish = true; // publish our update for this just finished acquisition window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If state has changed do a publish
|
||||||
|
publishUpdate();
|
||||||
|
|
||||||
|
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
|
||||||
|
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
|
||||||
|
return isAwake ? 100 : 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPS::forceWake(bool on)
|
||||||
|
{
|
||||||
|
if (on) {
|
||||||
|
DEBUG_MSG("Allowing GPS lock\n");
|
||||||
|
// lastSleepStartMsec = 0; // Force an update ASAP
|
||||||
|
wakeAllowed = true;
|
||||||
|
} else {
|
||||||
|
wakeAllowed = false;
|
||||||
|
|
||||||
|
// Note: if the gps was already awake, we DO NOT shut it down, because we want to allow it to complete its lock
|
||||||
|
// attempt even if we are in light sleep. Once the attempt succeeds (or times out) we'll then shut it down.
|
||||||
|
// setAwake(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||||
|
int GPS::prepareSleep(void *unused)
|
||||||
|
{
|
||||||
|
forceWake(false);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
122
src/gps/GPS.h
122
src/gps/GPS.h
@@ -1,35 +1,36 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include "GPSStatus.h"
|
#include "GPSStatus.h"
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "sys/time.h"
|
#include "concurrency/OSThread.h"
|
||||||
|
|
||||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
|
||||||
void perhapsSetRTC(const struct timeval *tv);
|
|
||||||
void perhapsSetRTC(struct tm &t);
|
|
||||||
|
|
||||||
// Generate a string representation of DOP
|
// Generate a string representation of DOP
|
||||||
const char *getDOPString(uint32_t dop);
|
const char *getDOPString(uint32_t dop);
|
||||||
|
|
||||||
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
|
|
||||||
uint32_t getTime();
|
|
||||||
|
|
||||||
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
|
|
||||||
uint32_t getValidTime();
|
|
||||||
|
|
||||||
void readFromRTC();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
|
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
|
||||||
*
|
*
|
||||||
* When new data is available it will notify observers.
|
* When new data is available it will notify observers.
|
||||||
*/
|
*/
|
||||||
class GPS
|
class GPS : private concurrency::OSThread
|
||||||
{
|
{
|
||||||
protected:
|
private:
|
||||||
|
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastWhileActiveMsec = 0;
|
||||||
|
|
||||||
bool hasValidLocation = false; // default to false, until we complete our first read
|
bool hasValidLocation = false; // default to false, until we complete our first read
|
||||||
|
|
||||||
|
bool isAwake = false; // true if we want a location right now
|
||||||
|
|
||||||
|
bool wakeAllowed = true; // false if gps must be forced to sleep regardless of what time it is
|
||||||
|
|
||||||
|
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
|
||||||
|
|
||||||
|
bool hasGPS = false; // Do we have a GPS we are talking to
|
||||||
|
|
||||||
|
uint8_t numSatellites = 0;
|
||||||
|
|
||||||
|
CallbackObserver<GPS, void *> notifySleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareSleep);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** If !NULL we will use this serial port to construct our GPS */
|
/** If !NULL we will use this serial port to construct our GPS */
|
||||||
static HardwareSerial *_serial_gps;
|
static HardwareSerial *_serial_gps;
|
||||||
@@ -42,11 +43,10 @@ class GPS
|
|||||||
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
|
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
|
||||||
// scaling before use)
|
// scaling before use)
|
||||||
uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
|
uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
|
||||||
uint32_t numSatellites = 0;
|
|
||||||
|
|
||||||
bool isConnected = false; // Do we have a GPS we are talking to
|
GPS() : concurrency::OSThread("GPS") {}
|
||||||
|
|
||||||
virtual ~GPS() {}
|
virtual ~GPS() {} // FIXME, we really should unregister our sleep observer
|
||||||
|
|
||||||
/** We will notify this observable anytime GPS state has changed meaningfully */
|
/** We will notify this observable anytime GPS state has changed meaningfully */
|
||||||
Observable<const meshtastic::GPSStatus *> newStatus;
|
Observable<const meshtastic::GPSStatus *> newStatus;
|
||||||
@@ -54,18 +54,90 @@ class GPS
|
|||||||
/**
|
/**
|
||||||
* Returns true if we succeeded
|
* Returns true if we succeeded
|
||||||
*/
|
*/
|
||||||
virtual bool setup() { return true; }
|
virtual bool setup();
|
||||||
|
|
||||||
/// A loop callback for subclasses that need it. FIXME, instead just block on serial reads
|
|
||||||
virtual void loop() {}
|
|
||||||
|
|
||||||
/// Returns ture if we have acquired GPS lock.
|
/// Returns ture if we have acquired GPS lock.
|
||||||
bool hasLock() const { return hasValidLocation; }
|
bool hasLock() const { return hasValidLocation; }
|
||||||
|
|
||||||
|
/// Return true if we are connected to a GPS
|
||||||
|
bool isConnected() const { return hasGPS; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
||||||
* called after the CPU wakes from light-sleep state */
|
* called after the CPU wakes from light-sleep state
|
||||||
virtual void startLock() {}
|
*
|
||||||
|
* Or set to false, to disallow any sort of waking
|
||||||
|
* */
|
||||||
|
void forceWake(bool on);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Do gps chipset specific init, return true for success
|
||||||
|
virtual bool setupGPS() = 0;
|
||||||
|
|
||||||
|
/// If possible force the GPS into sleep/low power mode
|
||||||
|
virtual void sleep() {}
|
||||||
|
|
||||||
|
/// wake the GPS into normal operation mode
|
||||||
|
virtual void wake() {}
|
||||||
|
|
||||||
|
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||||
|
*
|
||||||
|
* Return true if we received a valid message from the GPS
|
||||||
|
*/
|
||||||
|
virtual bool whileIdle() = 0;
|
||||||
|
|
||||||
|
/** Idle processing while GPS is looking for lock, called once per secondish */
|
||||||
|
virtual void whileActive() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a time
|
||||||
|
*/
|
||||||
|
virtual bool lookForTime() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
virtual bool lookForLocation() = 0;
|
||||||
|
|
||||||
|
/// Record that we have a GPS
|
||||||
|
void setConnected();
|
||||||
|
|
||||||
|
void setNumSatellites(uint8_t n);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||||
|
/// always returns 0 to indicate okay to sleep
|
||||||
|
int prepareSleep(void *unused);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
||||||
|
*
|
||||||
|
* calls sleep/wake
|
||||||
|
*/
|
||||||
|
void setAwake(bool on);
|
||||||
|
|
||||||
|
/** Get how long we should stay looking for each aquisition
|
||||||
|
*/
|
||||||
|
uint32_t getWakeTime() const;
|
||||||
|
|
||||||
|
/** Get how long we should sleep between aqusition attempts
|
||||||
|
*/
|
||||||
|
uint32_t getSleepTime() const;
|
||||||
|
|
||||||
|
GpsOperation getGpsOp() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell users we have new GPS readings
|
||||||
|
*/
|
||||||
|
void publishUpdate();
|
||||||
|
|
||||||
|
virtual int32_t runOnce();
|
||||||
};
|
};
|
||||||
|
|
||||||
extern GPS *gps;
|
extern GPS *gps;
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
#include "NEMAGPS.h"
|
|
||||||
#include "configuration.h"
|
|
||||||
#include "timing.h"
|
|
||||||
|
|
||||||
static int32_t toDegInt(RawDegrees d)
|
|
||||||
{
|
|
||||||
int32_t degMult = 10000000; // 1e7
|
|
||||||
int32_t r = d.deg * degMult + d.billionths / 100;
|
|
||||||
if (d.negative)
|
|
||||||
r *= -1;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NEMAGPS::loop()
|
|
||||||
{
|
|
||||||
while (_serial_gps->available() > 0) {
|
|
||||||
int c = _serial_gps->read();
|
|
||||||
// Serial.write(c);
|
|
||||||
reader.encode(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t now = timing::millis();
|
|
||||||
if ((now - lastUpdateMsec) > 20 * 1000) { // Ugly hack for now - limit update checks to once every 20 secs (but still consume
|
|
||||||
// serial chars at whatever rate)
|
|
||||||
lastUpdateMsec = now;
|
|
||||||
|
|
||||||
auto ti = reader.time;
|
|
||||||
auto d = reader.date;
|
|
||||||
if (ti.isUpdated() && ti.isValid() && d.isValid()) {
|
|
||||||
/* Convert to unix time
|
|
||||||
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).
|
|
||||||
*/
|
|
||||||
struct tm t;
|
|
||||||
t.tm_sec = ti.second();
|
|
||||||
t.tm_min = ti.minute();
|
|
||||||
t.tm_hour = ti.hour();
|
|
||||||
t.tm_mday = d.day();
|
|
||||||
t.tm_mon = d.month() - 1;
|
|
||||||
t.tm_year = d.year() - 1900;
|
|
||||||
t.tm_isdst = false;
|
|
||||||
perhapsSetRTC(t);
|
|
||||||
|
|
||||||
isConnected = true; // we seem to have a real GPS (but not necessarily a lock)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reader.location.isUpdated()) {
|
|
||||||
if (reader.altitude.isValid())
|
|
||||||
altitude = reader.altitude.meters();
|
|
||||||
|
|
||||||
if (reader.location.isValid()) {
|
|
||||||
auto loc = reader.location.value();
|
|
||||||
latitude = toDegInt(loc.lat);
|
|
||||||
longitude = toDegInt(loc.lng);
|
|
||||||
}
|
|
||||||
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
|
|
||||||
if (reader.hdop.isValid()) {
|
|
||||||
dop = reader.hdop.value();
|
|
||||||
}
|
|
||||||
if (reader.course.isValid()) {
|
|
||||||
heading = reader.course.value() * 1e3; //Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
|
|
||||||
}
|
|
||||||
if (reader.satellites.isValid()) {
|
|
||||||
numSatellites = reader.satellites.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
|
|
||||||
DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d, hdop=%f, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify any status instances that are observing us
|
|
||||||
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
|
|
||||||
newStatus.notifyObservers(&status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GPS.h"
|
|
||||||
#include "Observer.h"
|
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include "TinyGPS++.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A gps class thatreads from a NEMA GPS stream (and FIXME - eventually keeps the gps powered down except when reading)
|
|
||||||
*
|
|
||||||
* When new data is available it will notify observers.
|
|
||||||
*/
|
|
||||||
class NEMAGPS : public GPS
|
|
||||||
{
|
|
||||||
TinyGPSPlus reader;
|
|
||||||
|
|
||||||
uint32_t lastUpdateMsec = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual void loop();
|
|
||||||
};
|
|
||||||
110
src/gps/NMEAGPS.cpp
Normal file
110
src/gps/NMEAGPS.cpp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
#include "NMEAGPS.h"
|
||||||
|
#include "RTC.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
static int32_t toDegInt(RawDegrees d)
|
||||||
|
{
|
||||||
|
int32_t degMult = 10000000; // 1e7
|
||||||
|
int32_t r = d.deg * degMult + d.billionths / 100;
|
||||||
|
if (d.negative)
|
||||||
|
r *= -1;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NMEAGPS::setupGPS()
|
||||||
|
{
|
||||||
|
#ifdef PIN_GPS_PPS
|
||||||
|
// pulse per second
|
||||||
|
// FIXME - move into shared GPS code
|
||||||
|
pinMode(PIN_GPS_PPS, INPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
bool NMEAGPS::lookForTime()
|
||||||
|
{
|
||||||
|
auto ti = reader.time;
|
||||||
|
auto d = reader.date;
|
||||||
|
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
|
||||||
|
/* Convert to unix time
|
||||||
|
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).
|
||||||
|
*/
|
||||||
|
struct tm t;
|
||||||
|
t.tm_sec = ti.second();
|
||||||
|
t.tm_min = ti.minute();
|
||||||
|
t.tm_hour = ti.hour();
|
||||||
|
t.tm_mday = d.day();
|
||||||
|
t.tm_mon = d.month() - 1;
|
||||||
|
t.tm_year = d.year() - 1900;
|
||||||
|
t.tm_isdst = false;
|
||||||
|
perhapsSetRTC(RTCQualityGPS, t);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
bool NMEAGPS::lookForLocation()
|
||||||
|
{
|
||||||
|
bool foundLocation = false;
|
||||||
|
|
||||||
|
// uint8_t fixtype = reader.fixQuality();
|
||||||
|
// hasValidLocation = ((fixtype >= 1) && (fixtype <= 5));
|
||||||
|
|
||||||
|
if (reader.satellites.isUpdated()) {
|
||||||
|
setNumSatellites(reader.satellites.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
|
||||||
|
if (reader.hdop.isUpdated()) {
|
||||||
|
dop = reader.hdop.value();
|
||||||
|
}
|
||||||
|
if (reader.course.isUpdated()) {
|
||||||
|
heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.altitude.isUpdated())
|
||||||
|
altitude = reader.altitude.meters();
|
||||||
|
|
||||||
|
if (reader.location.isUpdated()) {
|
||||||
|
|
||||||
|
auto loc = reader.location.value();
|
||||||
|
latitude = toDegInt(loc.lat);
|
||||||
|
longitude = toDegInt(loc.lng);
|
||||||
|
foundLocation = true;
|
||||||
|
|
||||||
|
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
|
||||||
|
DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude,
|
||||||
|
dop * 1e-2, heading * 1e-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NMEAGPS::whileIdle()
|
||||||
|
{
|
||||||
|
bool isValid = false;
|
||||||
|
|
||||||
|
// First consume any chars that have piled up at the receiver
|
||||||
|
while (_serial_gps->available() > 0) {
|
||||||
|
int c = _serial_gps->read();
|
||||||
|
//DEBUG_MSG("%c", c);
|
||||||
|
isValid |= reader.encode(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
41
src/gps/NMEAGPS.h
Normal file
41
src/gps/NMEAGPS.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GPS.h"
|
||||||
|
#include "Observer.h"
|
||||||
|
#include "TinyGPS++.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A gps class thatreads from a NMEA GPS stream (and FIXME - eventually keeps the gps powered down except when reading)
|
||||||
|
*
|
||||||
|
* When new data is available it will notify observers.
|
||||||
|
*/
|
||||||
|
class NMEAGPS : public GPS
|
||||||
|
{
|
||||||
|
TinyGPSPlus reader;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual bool setupGPS();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||||
|
*
|
||||||
|
* Return true if we received a valid message from the GPS
|
||||||
|
*/
|
||||||
|
virtual bool whileIdle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a time
|
||||||
|
*/
|
||||||
|
virtual bool lookForTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
virtual bool lookForLocation();
|
||||||
|
};
|
||||||
77
src/gps/RTC.cpp
Normal file
77
src/gps/RTC.cpp
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#include "RTC.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
static RTCQuality currentQuality = RTCQualityNone;
|
||||||
|
|
||||||
|
RTCQuality getRTCQuality()
|
||||||
|
{
|
||||||
|
return currentQuality;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stuff that really should be in in the instance instead...
|
||||||
|
static uint32_t
|
||||||
|
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
|
||||||
|
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
|
||||||
|
|
||||||
|
void readFromRTC()
|
||||||
|
{
|
||||||
|
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
||||||
|
|
||||||
|
if (!gettimeofday(&tv, NULL)) {
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
DEBUG_MSG("Read RTC time as %ld (cur millis %u) quality=%d\n", tv.tv_sec, now, currentQuality);
|
||||||
|
timeStartMsec = now;
|
||||||
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||||
|
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
|
||||||
|
{
|
||||||
|
if (q > currentQuality) {
|
||||||
|
currentQuality = q;
|
||||||
|
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
||||||
|
#ifndef NO_ESP32
|
||||||
|
settimeofday(tv, NULL);
|
||||||
|
#else
|
||||||
|
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
|
||||||
|
#endif
|
||||||
|
readFromRTC();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||||
|
{
|
||||||
|
/* Convert to unix time
|
||||||
|
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);
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = res;
|
||||||
|
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
|
||||||
|
|
||||||
|
// DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
||||||
|
if (t.tm_year < 0 || t.tm_year >= 300) {
|
||||||
|
// DEBUG_MSG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return perhapsSetRTC(q, &tv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getTime()
|
||||||
|
{
|
||||||
|
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getValidTime(RTCQuality minQuality)
|
||||||
|
{
|
||||||
|
return (currentQuality >= minQuality) ? getTime() : 0;
|
||||||
|
}
|
||||||
30
src/gps/RTC.h
Normal file
30
src/gps/RTC.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "sys/time.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
enum RTCQuality {
|
||||||
|
/// We haven't had our RTC set yet
|
||||||
|
RTCQualityNone = 0,
|
||||||
|
|
||||||
|
/// Some other node gave us a time we can use
|
||||||
|
RTCQualityFromNet = 1,
|
||||||
|
|
||||||
|
/// Our time is based on our own GPS
|
||||||
|
RTCQualityGPS = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
RTCQuality getRTCQuality();
|
||||||
|
|
||||||
|
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||||
|
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv);
|
||||||
|
bool perhapsSetRTC(RTCQuality q, struct tm &t);
|
||||||
|
|
||||||
|
/// Return time since 1970 in secs. While quality is RTCQualityNone we will be returning time based at zero
|
||||||
|
uint32_t getTime();
|
||||||
|
|
||||||
|
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
||||||
|
uint32_t getValidTime(RTCQuality minQuality);
|
||||||
|
|
||||||
|
void readFromRTC();
|
||||||
@@ -1,32 +1,33 @@
|
|||||||
#include "UBloxGPS.h"
|
#include "UBloxGPS.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
UBloxGPS::UBloxGPS() : concurrency::PeriodicTask()
|
UBloxGPS::UBloxGPS() {}
|
||||||
{
|
|
||||||
notifySleepObserver.observe(¬ifySleep);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UBloxGPS::tryConnect()
|
bool UBloxGPS::tryConnect()
|
||||||
{
|
{
|
||||||
isConnected = false;
|
bool c = false;
|
||||||
|
|
||||||
if (_serial_gps)
|
if (_serial_gps)
|
||||||
isConnected = ublox.begin(*_serial_gps);
|
c = ublox.begin(*_serial_gps);
|
||||||
|
|
||||||
if (!isConnected && i2cAddress) {
|
if (!c && i2cAddress) {
|
||||||
extern bool neo6M; // Super skanky - if we are talking to the device i2c we assume it is a neo7 on a RAK815, which
|
extern bool neo6M; // Super skanky - if we are talking to the device i2c we assume it is a neo7 on a RAK815, which
|
||||||
// supports the newer API
|
// supports the newer API
|
||||||
neo6M = true;
|
neo6M = true;
|
||||||
|
|
||||||
isConnected = ublox.begin(Wire, i2cAddress);
|
c = ublox.begin(Wire, i2cAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isConnected;
|
if (c)
|
||||||
|
setConnected();
|
||||||
|
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UBloxGPS::setup()
|
bool UBloxGPS::setupGPS()
|
||||||
{
|
{
|
||||||
if (_serial_gps) {
|
if (_serial_gps) {
|
||||||
#ifdef GPS_RX_PIN
|
#ifdef GPS_RX_PIN
|
||||||
@@ -34,15 +35,12 @@ bool UBloxGPS::setup()
|
|||||||
#else
|
#else
|
||||||
_serial_gps->begin(GPS_BAUDRATE);
|
_serial_gps->begin(GPS_BAUDRATE);
|
||||||
#endif
|
#endif
|
||||||
// _serial_gps.setRxBufferSize(1024); // the default is 256
|
#ifndef NO_ESP32
|
||||||
|
_serial_gps->setRxBufferSize(2048); // the default is 256
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef GPS_POWER_EN
|
// uncomment to see debug info
|
||||||
pinMode(GPS_POWER_EN, OUTPUT);
|
|
||||||
digitalWrite(GPS_POWER_EN, 1);
|
|
||||||
delay(200); // Give time for the GPS to startup after we gave power
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ublox.enableDebugging(Serial);
|
// ublox.enableDebugging(Serial);
|
||||||
|
|
||||||
// try a second time, the ublox lib serial parsing is buggy?
|
// try a second time, the ublox lib serial parsing is buggy?
|
||||||
@@ -50,14 +48,12 @@ bool UBloxGPS::setup()
|
|||||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||||
delay(500);
|
delay(500);
|
||||||
|
|
||||||
if (isConnected) {
|
if (isConnected()) {
|
||||||
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
|
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
|
||||||
|
|
||||||
if (!setUBXMode())
|
if (!setUBXMode())
|
||||||
recordCriticalError(UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
recordCriticalError(UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
||||||
|
|
||||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@@ -103,7 +99,7 @@ bool UBloxGPS::factoryReset()
|
|||||||
{
|
{
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
|
|
||||||
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have
|
// It is useful to force back into factory defaults (9600baud, NMEA to test the behavior of boards that don't have
|
||||||
// GPS_TX connected)
|
// GPS_TX connected)
|
||||||
ublox.factoryReset();
|
ublox.factoryReset();
|
||||||
delay(5000);
|
delay(5000);
|
||||||
@@ -113,96 +109,118 @@ bool UBloxGPS::factoryReset()
|
|||||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||||
delay(500);
|
delay(500);
|
||||||
|
|
||||||
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected);
|
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected());
|
||||||
if (isConnected)
|
if (isConnected())
|
||||||
ok = setUBXMode();
|
ok = setUBXMode();
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
/** Idle processing while GPS is looking for lock */
|
||||||
int UBloxGPS::prepareSleep(void *unused)
|
void UBloxGPS::whileActive()
|
||||||
{
|
{
|
||||||
if (isConnected)
|
ublox.getT(maxWait()); // ask for new time data - hopefully ready when we come back
|
||||||
ublox.powerOff();
|
|
||||||
|
|
||||||
return 0;
|
// Ask for a new position fix - hopefully it will have results ready by next time
|
||||||
|
// the order here is important, because we only check for has latitude when reading
|
||||||
|
ublox.getSIV(maxWait());
|
||||||
|
ublox.getPDOP(maxWait());
|
||||||
|
ublox.getP(maxWait());
|
||||||
|
|
||||||
|
// Update fixtype
|
||||||
|
if (ublox.moduleQueried.fixType) {
|
||||||
|
fixType = ublox.getFixType(0);
|
||||||
|
// DEBUG_MSG("GPS fix type %d, numSats %d\n", fixType, numSatellites);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UBloxGPS::doTask()
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
bool UBloxGPS::lookForTime()
|
||||||
{
|
{
|
||||||
if (isConnected) {
|
if (ublox.moduleQueried.gpsSecond) {
|
||||||
// Consume all characters that have arrived
|
/* Convert to unix time
|
||||||
|
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January
|
||||||
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
|
1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||||
|
*/
|
||||||
// if using i2c or serial look too see if any chars are ready
|
struct tm t;
|
||||||
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
t.tm_sec = ublox.getSecond(0);
|
||||||
|
t.tm_min = ublox.getMinute(0);
|
||||||
// If we don't have a fix (a quick check), don't try waiting for a solution)
|
t.tm_hour = ublox.getHour(0);
|
||||||
// Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions
|
t.tm_mday = ublox.getDay(0);
|
||||||
// turn off for now
|
t.tm_mon = ublox.getMonth(0) - 1;
|
||||||
uint16_t maxWait = i2cAddress ? 300 : 0; // If using i2c we must poll with wait
|
t.tm_year = ublox.getYear(0) - 1900;
|
||||||
fixtype = ublox.getFixType(maxWait);
|
t.tm_isdst = false;
|
||||||
DEBUG_MSG("GPS fix type %d\n", fixtype);
|
perhapsSetRTC(RTCQualityGPS, t);
|
||||||
|
return true;
|
||||||
// DEBUG_MSG("sec %d\n", ublox.getSecond());
|
|
||||||
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
|
|
||||||
|
|
||||||
// any fix that has time
|
|
||||||
|
|
||||||
if (ublox.getT(maxWait)) {
|
|
||||||
/* Convert to unix time
|
|
||||||
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).
|
|
||||||
*/
|
|
||||||
struct tm t;
|
|
||||||
t.tm_sec = ublox.getSecond(0);
|
|
||||||
t.tm_min = ublox.getMinute(0);
|
|
||||||
t.tm_hour = ublox.getHour(0);
|
|
||||||
t.tm_mday = ublox.getDay(0);
|
|
||||||
t.tm_mon = ublox.getMonth(0) - 1;
|
|
||||||
t.tm_year = ublox.getYear(0) - 1900;
|
|
||||||
t.tm_isdst = false;
|
|
||||||
perhapsSetRTC(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
latitude = ublox.getLatitude(0);
|
|
||||||
longitude = ublox.getLongitude(0);
|
|
||||||
altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters
|
|
||||||
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
|
||||||
heading = ublox.getHeading(0);
|
|
||||||
numSatellites = ublox.getSIV(0);
|
|
||||||
|
|
||||||
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
|
||||||
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
|
||||||
hasValidLocation =
|
|
||||||
(latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000) && (numSatellites > 0);
|
|
||||||
|
|
||||||
// we only notify if position has changed due to a new fix
|
|
||||||
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(maxWait)) // rd fixes only
|
|
||||||
{
|
|
||||||
if (hasValidLocation) {
|
|
||||||
wantNewLocation = false;
|
|
||||||
// ublox.powerOff();
|
|
||||||
}
|
|
||||||
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
|
||||||
wantNewLocation = true;
|
|
||||||
|
|
||||||
// Notify any status instances that are observing us
|
|
||||||
const meshtastic::GPSStatus status =
|
|
||||||
meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
|
|
||||||
newStatus.notifyObservers(&status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 10s until we have something
|
return false;
|
||||||
// over the serial
|
|
||||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UBloxGPS::startLock()
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
bool UBloxGPS::lookForLocation()
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Looking for GPS lock\n");
|
bool foundLocation = false;
|
||||||
wantNewLocation = true;
|
|
||||||
setPeriod(1);
|
if (ublox.moduleQueried.SIV)
|
||||||
|
setNumSatellites(ublox.getSIV(0));
|
||||||
|
|
||||||
|
if (ublox.moduleQueried.pDOP)
|
||||||
|
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
||||||
|
|
||||||
|
// we only notify if position has changed due to a new fix
|
||||||
|
if ((fixType >= 3 && fixType <= 4)) {
|
||||||
|
if (ublox.moduleQueried.latitude) // rd fixes only
|
||||||
|
{
|
||||||
|
latitude = ublox.getLatitude(0);
|
||||||
|
longitude = ublox.getLongitude(0);
|
||||||
|
altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters
|
||||||
|
|
||||||
|
// Note: heading is only currently implmented in the ublox for the 8m chipset - therefore
|
||||||
|
// don't read it here - it will generate an ignored getPVT command on the 6ms
|
||||||
|
// heading = ublox.getHeading(0);
|
||||||
|
|
||||||
|
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
||||||
|
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
||||||
|
foundLocation = (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UBloxGPS::whileIdle()
|
||||||
|
{
|
||||||
|
// if using i2c or serial look too see if any chars are ready
|
||||||
|
return ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If possible force the GPS into sleep/low power mode
|
||||||
|
/// Note: ublox doesn't need a wake method, because as soon as we send chars to the GPS it will wake up
|
||||||
|
void UBloxGPS::sleep()
|
||||||
|
{
|
||||||
|
// Tell GPS to power down until we send it characters on serial port (we leave vcc connected)
|
||||||
|
ublox.powerOff();
|
||||||
|
// setGPSPower(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBloxGPS::wake()
|
||||||
|
{
|
||||||
|
fixType = 0; // assume we hace no fix yet
|
||||||
|
|
||||||
|
setGPSPower(true);
|
||||||
|
|
||||||
|
// Note: no delay needed because now we leave gps power on always and instead use ublox.powerOff()
|
||||||
|
// Give time for the GPS to boot
|
||||||
|
// delay(200);
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "SparkFun_Ublox_Arduino_Library.h"
|
#include "SparkFun_Ublox_Arduino_Library.h"
|
||||||
@@ -10,29 +9,14 @@
|
|||||||
*
|
*
|
||||||
* When new data is available it will notify observers.
|
* When new data is available it will notify observers.
|
||||||
*/
|
*/
|
||||||
class UBloxGPS : public GPS, public concurrency::PeriodicTask
|
class UBloxGPS : public GPS
|
||||||
{
|
{
|
||||||
SFE_UBLOX_GPS ublox;
|
SFE_UBLOX_GPS ublox;
|
||||||
|
uint8_t fixType = 0;
|
||||||
bool wantNewLocation = true;
|
|
||||||
|
|
||||||
CallbackObserver<UBloxGPS, void *> notifySleepObserver = CallbackObserver<UBloxGPS, void *>(this, &UBloxGPS::prepareSleep);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UBloxGPS();
|
UBloxGPS();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if we succeeded
|
|
||||||
*/
|
|
||||||
virtual bool setup();
|
|
||||||
|
|
||||||
virtual void doTask();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
|
||||||
* called after the CPU wakes from light-sleep state */
|
|
||||||
virtual void startLock();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset our GPS back to factory settings
|
* Reset our GPS back to factory settings
|
||||||
*
|
*
|
||||||
@@ -40,15 +24,48 @@ class UBloxGPS : public GPS, public concurrency::PeriodicTask
|
|||||||
*/
|
*/
|
||||||
bool factoryReset();
|
bool factoryReset();
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
/**
|
||||||
/// always returns 0 to indicate okay to sleep
|
* Returns true if we succeeded
|
||||||
int prepareSleep(void *unused);
|
*/
|
||||||
|
virtual bool setupGPS();
|
||||||
|
|
||||||
|
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||||
|
*
|
||||||
|
* Return true if we received a valid message from the GPS
|
||||||
|
*/
|
||||||
|
virtual bool whileIdle();
|
||||||
|
|
||||||
|
/** Idle processing while GPS is looking for lock */
|
||||||
|
virtual void whileActive();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a time
|
||||||
|
*/
|
||||||
|
virtual bool lookForTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
virtual bool lookForLocation();
|
||||||
|
|
||||||
|
/// If possible force the GPS into sleep/low power mode
|
||||||
|
virtual void sleep();
|
||||||
|
virtual void wake();
|
||||||
|
|
||||||
|
private:
|
||||||
/// Attempt to connect to our GPS, returns false if no gps is present
|
/// Attempt to connect to our GPS, returns false if no gps is present
|
||||||
bool tryConnect();
|
bool tryConnect();
|
||||||
|
|
||||||
/// Switch to our desired operating mode and save the settings to flash
|
/// Switch to our desired operating mode and save the settings to flash
|
||||||
/// returns true for success
|
/// returns true for success
|
||||||
bool setUBXMode();
|
bool setUBXMode();
|
||||||
|
|
||||||
|
uint16_t maxWait() const { return i2cAddress ? 300 : 0; /*If using i2c we must poll with wait */ }
|
||||||
};
|
};
|
||||||
|
|||||||
152
src/graphics/EInkDisplay.cpp
Normal file
152
src/graphics/EInkDisplay.cpp
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#ifdef HAS_EINK
|
||||||
|
#include "EInkDisplay.h"
|
||||||
|
#include "SPILock.h"
|
||||||
|
#include "epd1in54.h" // Screen specific library
|
||||||
|
#include "graphics/configs.h"
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <TFT_eSPI.h> // Graphics library and Sprite class
|
||||||
|
|
||||||
|
Epd ePaper; // Create an instance ePaper
|
||||||
|
|
||||||
|
TFT_eSPI glc = TFT_eSPI(); // Invoke the graphics library class
|
||||||
|
TFT_eSprite frame = TFT_eSprite(&glc); // Invoke the Sprite class for the image frame buffer
|
||||||
|
uint8_t *framePtr; // Pointer for the black frame buffer
|
||||||
|
|
||||||
|
#define COLORED 0
|
||||||
|
#define UNCOLORED 1
|
||||||
|
|
||||||
|
#define INK COLORED // Black ink
|
||||||
|
#define PAPER UNCOLORED // 'paper' background colour
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
// Update display - different displays have different function names in the default
|
||||||
|
// Waveshare libraries :-(
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
#if defined(EPD1IN54B_H) || defined(EPD1IN54C_H) || defined(EPD2IN13B_H) || defined(EPD2IN7B_H) || defined(EPD2IN9B_H) || \
|
||||||
|
defined(EPD4IN2_H)
|
||||||
|
void updateDisplay(uint8_t *blackFrame = blackFramePtr, uint8_t *redFrame = redFramePtr)
|
||||||
|
{
|
||||||
|
ePaper.DisplayFrame(blackFrame, redFrame); // Update 3 colour display
|
||||||
|
#else
|
||||||
|
void updateDisplay(uint8_t *blackFrame = framePtr)
|
||||||
|
{
|
||||||
|
#if defined(EPD2IN7_H) || defined(EPD4IN2_H)
|
||||||
|
ePaper.DisplayFrame(blackFrame); // Update 2 color display
|
||||||
|
|
||||||
|
#elif defined(EPD1IN54_H) || defined(EPD2IN13_H) || defined(EPD2IN9_H)
|
||||||
|
ePaper.SetFrameMemory(blackFrame); // Update 2 colour display
|
||||||
|
ePaper.DisplayFrame();
|
||||||
|
#else
|
||||||
|
#error "Selected ePaper library is not supported"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl)
|
||||||
|
{
|
||||||
|
setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT);
|
||||||
|
// setGeometry(GEOMETRY_RAWMODE, 128, 64); // old resolution
|
||||||
|
// setGeometry(GEOMETRY_128_64); // We originally used this because I wasn't sure if rawmode worked - it does
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME quick hack to limit drawing to a very slow rate
|
||||||
|
uint32_t lastDrawMsec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force a display update if we haven't drawn within the specified msecLimit
|
||||||
|
*/
|
||||||
|
bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
||||||
|
{
|
||||||
|
// No need to grab this lock because we are on our own SPI bus
|
||||||
|
// concurrency::LockGuard g(spiLock);
|
||||||
|
|
||||||
|
uint32_t now = millis();
|
||||||
|
uint32_t sinceLast = now - lastDrawMsec;
|
||||||
|
|
||||||
|
if (framePtr && (sinceLast > msecLimit || lastDrawMsec == 0)) {
|
||||||
|
lastDrawMsec = now;
|
||||||
|
|
||||||
|
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||||
|
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
||||||
|
for (uint8_t y = 0; y < displayHeight; y++) {
|
||||||
|
for (uint8_t x = 0; x < displayWidth; x++) {
|
||||||
|
|
||||||
|
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
||||||
|
auto b = buffer[x + (y / 8) * displayWidth];
|
||||||
|
auto isset = b & (1 << (y & 7));
|
||||||
|
frame.drawPixel(x, y, isset ? INK : PAPER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ePaper.Reset(); // wake the screen from sleep
|
||||||
|
|
||||||
|
DEBUG_MSG("Updating eink... ");
|
||||||
|
updateDisplay(); // Send image to display and refresh
|
||||||
|
DEBUG_MSG("done\n");
|
||||||
|
|
||||||
|
// Put screen to sleep to save power
|
||||||
|
ePaper.Sleep();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// DEBUG_MSG("Skipping eink display\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the buffer to the display memory
|
||||||
|
void EInkDisplay::display(void)
|
||||||
|
{
|
||||||
|
// We don't allow regular 'dumb' display() calls to draw on eink until we've shown
|
||||||
|
// at least one forceDisplay() keyframe. This prevents flashing when we should the critical
|
||||||
|
// bootscreen (that we want to look nice)
|
||||||
|
if (lastDrawMsec)
|
||||||
|
forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a command to the display (low level function)
|
||||||
|
void EInkDisplay::sendCommand(uint8_t com)
|
||||||
|
{
|
||||||
|
(void)com;
|
||||||
|
// Drop all commands to device (we just update the buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the display
|
||||||
|
bool EInkDisplay::connect()
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Doing EInk init\n");
|
||||||
|
|
||||||
|
#ifdef PIN_EINK_PWR_ON
|
||||||
|
digitalWrite(PIN_EINK_PWR_ON, HIGH); // If we need to assert a pin to power external peripherals
|
||||||
|
pinMode(PIN_EINK_PWR_ON, OUTPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef PIN_EINK_EN
|
||||||
|
digitalWrite(PIN_EINK_EN, HIGH);
|
||||||
|
pinMode(PIN_EINK_EN, OUTPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Initialise the ePaper library
|
||||||
|
// FIXME - figure out how to use lut_partial_update
|
||||||
|
if (ePaper.Init(lut_full_update) != 0) {
|
||||||
|
DEBUG_MSG("ePaper init failed\n");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
frame.setColorDepth(1); // Must set the bits per pixel to 1 for ePaper displays
|
||||||
|
// Set bit depth BEFORE creating Sprite, default is 16!
|
||||||
|
|
||||||
|
// Create a frame buffer in RAM of defined size and save the pointer to it
|
||||||
|
// RAM needed is about (EPD_WIDTH * EPD_HEIGHT)/8 , ~5000 bytes for 200 x 200 pixels
|
||||||
|
// Note: always create the Sprite before setting the Sprite rotation
|
||||||
|
framePtr = (uint8_t *)frame.createSprite(EPD_WIDTH, EPD_HEIGHT);
|
||||||
|
|
||||||
|
frame.fillSprite(PAPER); // Fill frame with white
|
||||||
|
/* frame.drawLine(0, 0, frame.width() - 1, frame.height() - 1, INK);
|
||||||
|
frame.drawLine(0, frame.height() - 1, frame.width() - 1, 0, INK);
|
||||||
|
updateDisplay(); */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
48
src/graphics/EInkDisplay.h
Normal file
48
src/graphics/EInkDisplay.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <OLEDDisplay.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An adapter class that allows using the TFT_eSPI library as if it was an OLEDDisplay implementation.
|
||||||
|
*
|
||||||
|
* Remaining TODO:
|
||||||
|
* optimize display() to only draw changed pixels (see other OLED subclasses for examples)
|
||||||
|
* implement displayOn/displayOff to turn off the TFT device (and backlight)
|
||||||
|
* Use the fast NRF52 SPI API rather than the slow standard arduino version
|
||||||
|
*
|
||||||
|
* turn radio back on - currently with both on spi bus is fucked? or are we leaving chip select asserted?
|
||||||
|
*/
|
||||||
|
class EInkDisplay : public OLEDDisplay
|
||||||
|
{
|
||||||
|
/// How often should we update the display
|
||||||
|
/// thereafter we do once per 5 minutes
|
||||||
|
uint32_t slowUpdateMsec = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/* constructor
|
||||||
|
FIXME - the parameters are not used, just a temporary hack to keep working like the old displays
|
||||||
|
*/
|
||||||
|
EInkDisplay(uint8_t address, int sda, int scl);
|
||||||
|
|
||||||
|
// Write the buffer to the display memory (for eink we only do this occasionally)
|
||||||
|
virtual void display(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force a display update if we haven't drawn within the specified msecLimit
|
||||||
|
*
|
||||||
|
* @return true if we did draw the screen
|
||||||
|
*/
|
||||||
|
bool forceDisplay(uint32_t msecLimit = 1000);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// the header size of the buffer used, e.g. for the SPI command header
|
||||||
|
virtual int getBufferOffset(void) { return 0; }
|
||||||
|
|
||||||
|
// Send a command to the display (low level function)
|
||||||
|
virtual void sendCommand(uint8_t com);
|
||||||
|
|
||||||
|
// Connect to the display
|
||||||
|
virtual bool connect();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -31,6 +31,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#include "graphics/images.h"
|
#include "graphics/images.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "meshwifi/meshwifi.h"
|
||||||
|
#include "target_specific.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
using namespace meshtastic; /** @todo remove */
|
using namespace meshtastic; /** @todo remove */
|
||||||
@@ -56,39 +58,73 @@ static char ourId[5];
|
|||||||
static bool heartbeat = false;
|
static bool heartbeat = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// We used to use constants for this - now we pull from the device at startup
|
||||||
|
//#define SCREEN_WIDTH 128
|
||||||
|
//#define SCREEN_HEIGHT 64
|
||||||
|
|
||||||
|
static uint16_t displayWidth, displayHeight;
|
||||||
|
|
||||||
|
#define SCREEN_WIDTH displayWidth
|
||||||
|
#define SCREEN_HEIGHT displayHeight
|
||||||
|
|
||||||
|
#ifdef HAS_EINK
|
||||||
|
// The screen is bigger so use bigger fonts
|
||||||
|
#define FONT_SMALL ArialMT_Plain_16
|
||||||
|
#define FONT_MEDIUM ArialMT_Plain_24
|
||||||
|
#define FONT_LARGE ArialMT_Plain_24
|
||||||
|
#else
|
||||||
|
#define FONT_SMALL ArialMT_Plain_10
|
||||||
|
#define FONT_MEDIUM ArialMT_Plain_16
|
||||||
|
#define FONT_LARGE ArialMT_Plain_24
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||||
|
|
||||||
|
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
|
||||||
|
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
|
||||||
|
|
||||||
|
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||||
|
|
||||||
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
// draw an xbm image.
|
// draw an xbm image.
|
||||||
// Please note that everything that should be transitioned
|
// Please note that everything that should be transitioned
|
||||||
// needs to be drawn relative to x and y
|
// needs to be drawn relative to x and y
|
||||||
display->drawXbm(x + 32, y, icon_width, icon_height, (const uint8_t *)icon_bits);
|
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_16);
|
// draw centered left to right and centered above the one line of app text
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
|
||||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
icon_width, icon_height, (const uint8_t *)icon_bits);
|
||||||
display->setFont(ArialMT_Plain_10);
|
|
||||||
const char *region = xstr(HW_VERSION);
|
display->setFont(FONT_MEDIUM);
|
||||||
if (*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
region += 4;
|
const char *title = "meshtastic.org";
|
||||||
|
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
|
const char *region = myRegion ? myRegion->name : NULL;
|
||||||
|
if (region)
|
||||||
|
display->drawString(x + 0, y + 0, region);
|
||||||
|
|
||||||
char buf[16];
|
char buf[16];
|
||||||
snprintf(buf, sizeof(buf), "%s",
|
snprintf(buf, sizeof(buf), "%s",
|
||||||
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
||||||
display->drawString(SCREEN_WIDTH - 20, 0, buf);
|
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf);
|
||||||
|
screen->forceDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(FONT_MEDIUM);
|
||||||
display->drawString(64 + x, y, "Bluetooth");
|
display->drawString(64 + x, y, "Bluetooth");
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
display->drawString(64 + x, FONT_HEIGHT + y + 2, "Enter this code");
|
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Enter this code");
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_24);
|
display->setFont(FONT_LARGE);
|
||||||
display->drawString(64 + x, 26 + y, btPIN);
|
display->drawString(64 + x, 26 + y, btPIN);
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
char buf[30];
|
char buf[30];
|
||||||
const char *name = "Name: ";
|
const char *name = "Name: ";
|
||||||
strcpy(buf, name);
|
strcpy(buf, name);
|
||||||
@@ -110,10 +146,10 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
|||||||
// with the third parameter you can define the width after which words will
|
// with the third parameter you can define the width after which words will
|
||||||
// be wrapped. Currently only spaces and "-" are allowed for wrapping
|
// be wrapped. Currently only spaces and "-" are allowed for wrapping
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(FONT_MEDIUM);
|
||||||
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
||||||
display->drawString(0 + x, 0 + y, sender);
|
display->drawString(0 + x, 0 + y, sender);
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// the max length of this buffer is much longer than we can possibly print
|
// the max length of this buffer is much longer than we can possibly print
|
||||||
static char tempBuf[96];
|
static char tempBuf[96];
|
||||||
@@ -133,8 +169,8 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
|||||||
int xo = x, yo = y;
|
int xo = x, yo = y;
|
||||||
while (*f) {
|
while (*f) {
|
||||||
display->drawString(xo, yo, *f);
|
display->drawString(xo, yo, *f);
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT_SMALL;
|
||||||
if (yo > SCREEN_HEIGHT - FONT_HEIGHT) {
|
if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) {
|
||||||
xo += SCREEN_WIDTH / 2;
|
xo += SCREEN_WIDTH / 2;
|
||||||
yo = 0;
|
yo = 0;
|
||||||
}
|
}
|
||||||
@@ -160,14 +196,14 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
|||||||
// Wrap to next row, if needed.
|
// Wrap to next row, if needed.
|
||||||
if (++col >= COLUMNS) {
|
if (++col >= COLUMNS) {
|
||||||
xo = x;
|
xo = x;
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT_SMALL;
|
||||||
col = 0;
|
col = 0;
|
||||||
}
|
}
|
||||||
f++;
|
f++;
|
||||||
}
|
}
|
||||||
if (col != 0) {
|
if (col != 0) {
|
||||||
// Include last incomplete line in our total.
|
// Include last incomplete line in our total.
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT_SMALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return yo;
|
return yo;
|
||||||
@@ -234,11 +270,27 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus
|
|||||||
display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
|
display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
|
||||||
|
|
||||||
// Draw the number of satellites
|
// Draw the number of satellites
|
||||||
sprintf(satsString, "%d", gps->getNumSatellites());
|
sprintf(satsString, "%lu", gps->getNumSatellites());
|
||||||
display->drawString(x + 34, y - 2, satsString);
|
display->drawString(x + 34, y - 2, satsString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||||
|
{
|
||||||
|
String displayLine = "";
|
||||||
|
if (!gps->getIsConnected()) {
|
||||||
|
// displayLine = "No GPS Module";
|
||||||
|
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
|
||||||
|
} else if (!gps->getHasLock()) {
|
||||||
|
// displayLine = "No GPS Lock";
|
||||||
|
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
displayLine = "Altitude: " + String(gps->getAltitude()) + "m";
|
||||||
|
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw GPS status coordinates
|
// Draw GPS status coordinates
|
||||||
static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||||
{
|
{
|
||||||
@@ -458,7 +510,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
|
|
||||||
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
|
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
@@ -471,11 +523,11 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
uint32_t agoSecs = sinceLastSeen(node);
|
uint32_t agoSecs = sinceLastSeen(node);
|
||||||
static char lastStr[20];
|
static char lastStr[20];
|
||||||
if (agoSecs < 120) // last 2 mins?
|
if (agoSecs < 120) // last 2 mins?
|
||||||
snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs);
|
snprintf(lastStr, sizeof(lastStr), "%lu seconds ago", agoSecs);
|
||||||
else if (agoSecs < 120 * 60) // last 2 hrs
|
else if (agoSecs < 120 * 60) // last 2 hrs
|
||||||
snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60);
|
snprintf(lastStr, sizeof(lastStr), "%lu minutes ago", agoSecs / 60);
|
||||||
else
|
else
|
||||||
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
|
snprintf(lastStr, sizeof(lastStr), "%lu hours ago", agoSecs / 60 / 60);
|
||||||
|
|
||||||
static char distStr[20];
|
static char distStr[20];
|
||||||
strcpy(distStr, "? km"); // might not have location data
|
strcpy(distStr, "? km"); // might not have location data
|
||||||
@@ -513,7 +565,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
// direction to node is unknown so display question mark
|
// direction to node is unknown so display question mark
|
||||||
// Debug info for gps lock errors
|
// Debug info for gps lock errors
|
||||||
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
||||||
display->drawString(compassX - FONT_HEIGHT / 4, compassY - FONT_HEIGHT / 2, "?");
|
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
|
||||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||||
|
|
||||||
// Must be after distStr is populated
|
// Must be after distStr is populated
|
||||||
@@ -543,7 +595,10 @@ void _screen_header()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Screen::Screen(uint8_t address, int sda, int scl) : cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {}
|
Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev)
|
||||||
|
{
|
||||||
|
cmdQueue.setReader(this);
|
||||||
|
}
|
||||||
|
|
||||||
void Screen::handleSetOn(bool on)
|
void Screen::handleSetOn(bool on)
|
||||||
{
|
{
|
||||||
@@ -555,9 +610,12 @@ void Screen::handleSetOn(bool on)
|
|||||||
DEBUG_MSG("Turning on screen\n");
|
DEBUG_MSG("Turning on screen\n");
|
||||||
dispdev.displayOn();
|
dispdev.displayOn();
|
||||||
dispdev.displayOn();
|
dispdev.displayOn();
|
||||||
|
enabled = true;
|
||||||
|
setInterval(0); // Draw ASAP
|
||||||
} else {
|
} else {
|
||||||
DEBUG_MSG("Turning off screen\n");
|
DEBUG_MSG("Turning off screen\n");
|
||||||
dispdev.displayOff();
|
dispdev.displayOff();
|
||||||
|
enabled = false;
|
||||||
}
|
}
|
||||||
screenOn = on;
|
screenOn = on;
|
||||||
}
|
}
|
||||||
@@ -565,8 +623,6 @@ void Screen::handleSetOn(bool on)
|
|||||||
|
|
||||||
void Screen::setup()
|
void Screen::setup()
|
||||||
{
|
{
|
||||||
concurrency::PeriodicTask::setup();
|
|
||||||
|
|
||||||
// We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device
|
// We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device
|
||||||
// is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device.
|
// is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device.
|
||||||
useDisplay = true;
|
useDisplay = true;
|
||||||
@@ -575,6 +631,10 @@ void Screen::setup()
|
|||||||
|
|
||||||
// Initialising the UI will init the display too.
|
// Initialising the UI will init the display too.
|
||||||
ui.init();
|
ui.init();
|
||||||
|
|
||||||
|
displayWidth = dispdev.width();
|
||||||
|
displayHeight = dispdev.height();
|
||||||
|
|
||||||
ui.setTimePerTransition(300); // msecs
|
ui.setTimePerTransition(300); // msecs
|
||||||
ui.setIndicatorPosition(BOTTOM);
|
ui.setIndicatorPosition(BOTTOM);
|
||||||
// Defines where the first frame is located in the bar.
|
// Defines where the first frame is located in the bar.
|
||||||
@@ -624,14 +684,33 @@ void Screen::setup()
|
|||||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::doTask()
|
void Screen::forceDisplay()
|
||||||
|
{
|
||||||
|
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
|
||||||
|
#ifdef HAS_EINK
|
||||||
|
dispdev.forceDisplay();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Screen::runOnce()
|
||||||
{
|
{
|
||||||
// If we don't have a screen, don't ever spend any CPU for us.
|
// If we don't have a screen, don't ever spend any CPU for us.
|
||||||
if (!useDisplay) {
|
if (!useDisplay) {
|
||||||
setPeriod(0);
|
enabled = false;
|
||||||
return;
|
return RUN_SAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show boot screen for first 3 seconds, then switch to normal operation.
|
||||||
|
static bool showingBootScreen = true;
|
||||||
|
if (showingBootScreen && (millis() > 5000)) {
|
||||||
|
DEBUG_MSG("Done with boot screen...\n");
|
||||||
|
stopBootScreen();
|
||||||
|
showingBootScreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the screen last, after we've figured out what to show.
|
||||||
|
debug_info()->setChannelNameStatus(getChannelName());
|
||||||
|
|
||||||
// Process incoming commands.
|
// Process incoming commands.
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ScreenCmd cmd;
|
ScreenCmd cmd;
|
||||||
@@ -666,10 +745,14 @@ void Screen::doTask()
|
|||||||
|
|
||||||
if (!screenOn) { // If we didn't just wake and the screen is still off, then
|
if (!screenOn) { // If we didn't just wake and the screen is still off, then
|
||||||
// stop updating until it is on again
|
// stop updating until it is on again
|
||||||
setPeriod(0);
|
enabled = false;
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this must be before the frameState == FIXED check, because we always
|
||||||
|
// want to draw at least one FIXED frame before doing forceDisplay
|
||||||
|
ui.update();
|
||||||
|
|
||||||
// Switch to a low framerate (to save CPU) when we are not in transition
|
// Switch to a low framerate (to save CPU) when we are not in transition
|
||||||
// but we should only call setTargetFPS when framestate changes, because
|
// but we should only call setTargetFPS when framestate changes, because
|
||||||
// otherwise that breaks animations.
|
// otherwise that breaks animations.
|
||||||
@@ -678,6 +761,7 @@ void Screen::doTask()
|
|||||||
DEBUG_MSG("Setting idle framerate\n");
|
DEBUG_MSG("Setting idle framerate\n");
|
||||||
targetFramerate = IDLE_FRAMERATE;
|
targetFramerate = IDLE_FRAMERATE;
|
||||||
ui.setTargetFPS(targetFramerate);
|
ui.setTargetFPS(targetFramerate);
|
||||||
|
forceDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
// While showing the bootscreen or Bluetooth pair screen all of our
|
// While showing the bootscreen or Bluetooth pair screen all of our
|
||||||
@@ -686,14 +770,12 @@ void Screen::doTask()
|
|||||||
// standard screen loop handling here
|
// standard screen loop handling here
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.update();
|
|
||||||
|
|
||||||
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
|
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
|
||||||
// ui.getUiState()->frameState); If we are scrolling we need to be called
|
// ui.getUiState()->frameState); If we are scrolling we need to be called
|
||||||
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
|
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
|
||||||
// as fast as we really need so that any rounding errors still result with
|
// as fast as we really need so that any rounding errors still result with
|
||||||
// the correct framerate
|
// the correct framerate
|
||||||
setPeriod(1000 / targetFramerate);
|
return (1000 / targetFramerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
@@ -708,6 +790,11 @@ void Screen::drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUi
|
|||||||
screen->debugInfo.drawFrameSettings(display, state, x, y);
|
screen->debugInfo.drawFrameSettings(display, state, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
Screen *screen = reinterpret_cast<Screen *>(state->userData);
|
||||||
|
screen->debugInfo.drawFrameWiFi(display, state, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
// restore our regular frame list
|
// restore our regular frame list
|
||||||
void Screen::setFrames()
|
void Screen::setFrames()
|
||||||
@@ -739,11 +826,18 @@ void Screen::setFrames()
|
|||||||
// call a method on debugInfoScreen object (for more details)
|
// call a method on debugInfoScreen object (for more details)
|
||||||
normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
|
normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
|
||||||
|
|
||||||
|
if (isWifiAvailable()) {
|
||||||
|
// call a method on debugInfoScreen object (for more details)
|
||||||
|
normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline;
|
||||||
|
}
|
||||||
|
|
||||||
ui.setFrames(normalFrames, numframes);
|
ui.setFrames(normalFrames, numframes);
|
||||||
ui.enableAllIndicators();
|
ui.enableAllIndicators();
|
||||||
|
|
||||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
||||||
// just changed)
|
// just changed)
|
||||||
|
|
||||||
|
setFastFramerate(); // Draw ASAP
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
||||||
@@ -753,16 +847,17 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
|||||||
|
|
||||||
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
||||||
|
|
||||||
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
|
snprintf(btPIN, sizeof(btPIN), "%06lu", pin);
|
||||||
|
|
||||||
ui.disableAllIndicators();
|
ui.disableAllIndicators();
|
||||||
ui.setFrames(btFrames, 1);
|
ui.setFrames(btFrames, 1);
|
||||||
|
setFastFramerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::handlePrint(const char *text)
|
void Screen::handlePrint(const char *text)
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Screen: %s", text);
|
DEBUG_MSG("Screen: %s", text);
|
||||||
if (!useDisplay)
|
if (!useDisplay || !showingNormalScreen)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dispdev.print(text);
|
dispdev.print(text);
|
||||||
@@ -773,22 +868,27 @@ void Screen::handleOnPress()
|
|||||||
// If screen was off, just wake it, otherwise advance to next frame
|
// If screen was off, just wake it, otherwise advance to next frame
|
||||||
// If we are in a transition, the press must have bounced, drop it.
|
// If we are in a transition, the press must have bounced, drop it.
|
||||||
if (ui.getUiState()->frameState == FIXED) {
|
if (ui.getUiState()->frameState == FIXED) {
|
||||||
setPeriod(1); // redraw ASAP
|
|
||||||
ui.nextFrame();
|
ui.nextFrame();
|
||||||
|
|
||||||
DEBUG_MSG("Setting fast framerate\n");
|
setFastFramerate();
|
||||||
|
|
||||||
// We are about to start a transition so speed up fps
|
|
||||||
targetFramerate = TRANSITION_FRAMERATE;
|
|
||||||
ui.setTargetFPS(targetFramerate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::setFastFramerate()
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Setting fast framerate\n");
|
||||||
|
|
||||||
|
// We are about to start a transition so speed up fps
|
||||||
|
targetFramerate = TRANSITION_FRAMERATE;
|
||||||
|
ui.setTargetFPS(targetFramerate);
|
||||||
|
setInterval(0); // redraw ASAP
|
||||||
|
}
|
||||||
|
|
||||||
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
displayedNodeNum = 0; // Not currently showing a node pane
|
displayedNodeNum = 0; // Not currently showing a node pane
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
@@ -797,26 +897,26 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
{
|
{
|
||||||
concurrency::LockGuard guard(&lock);
|
concurrency::LockGuard guard(&lock);
|
||||||
snprintf(channelStr, sizeof(channelStr), "%s", channelName.c_str());
|
snprintf(channelStr, sizeof(channelStr), "%s", channelName.c_str());
|
||||||
|
|
||||||
// Display power status
|
|
||||||
if (powerStatus->getHasBattery())
|
|
||||||
drawBattery(display, x, y + 2, imgBattery, powerStatus);
|
|
||||||
else if (powerStatus->knowsUSB())
|
|
||||||
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
|
|
||||||
// Display nodes status
|
|
||||||
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
|
|
||||||
// Display GPS status
|
|
||||||
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display power status
|
||||||
|
if (powerStatus->getHasBattery())
|
||||||
|
drawBattery(display, x, y + 2, imgBattery, powerStatus);
|
||||||
|
else if (powerStatus->knowsUSB())
|
||||||
|
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
|
||||||
|
// Display nodes status
|
||||||
|
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
|
||||||
|
// Display GPS status
|
||||||
|
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
||||||
|
|
||||||
// Draw the channel name
|
// Draw the channel name
|
||||||
display->drawString(x, y + FONT_HEIGHT, channelStr);
|
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
|
||||||
// Draw our hardware ID to assist with bluetooth pairing
|
// Draw our hardware ID to assist with bluetooth pairing
|
||||||
display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT, 8, 8, imgInfo);
|
display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo);
|
||||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT, ourId);
|
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId);
|
||||||
|
|
||||||
// Draw any log messages
|
// Draw any log messages
|
||||||
display->drawLogBuffer(x, y + (FONT_HEIGHT * 2));
|
display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2));
|
||||||
|
|
||||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||||
#ifdef SHOW_REDRAWS
|
#ifdef SHOW_REDRAWS
|
||||||
@@ -827,40 +927,165 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Jm
|
// Jm
|
||||||
|
void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
#ifdef HAS_WIFI
|
||||||
|
const char *wifiName = radioConfig.preferences.wifi_ssid;
|
||||||
|
const char *wifiPsw = radioConfig.preferences.wifi_password;
|
||||||
|
|
||||||
|
displayedNodeNum = 0; // Not currently showing a node pane
|
||||||
|
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
|
// The coordinates define the left starting point of the text
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
|
if (radioConfig.preferences.wifi_ap_mode) {
|
||||||
|
display->drawString(x, y, String("WiFi: Software AP"));
|
||||||
|
} else if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
display->drawString(x, y, String("WiFi: Not Connected"));
|
||||||
|
} else {
|
||||||
|
display->drawString(x, y, String("WiFi: Connected"));
|
||||||
|
|
||||||
|
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y,
|
||||||
|
"RSSI " + String(WiFi.RSSI()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
- WL_CONNECTED: assigned when connected to a WiFi network;
|
||||||
|
- WL_NO_SSID_AVAIL: assigned when no SSID are available;
|
||||||
|
- WL_CONNECT_FAILED: assigned when the connection fails for all the attempts;
|
||||||
|
- WL_CONNECTION_LOST: assigned when the connection is lost;
|
||||||
|
- WL_DISCONNECTED: assigned when disconnected from a network;
|
||||||
|
- WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of
|
||||||
|
attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED);
|
||||||
|
- WL_SCAN_COMPLETED: assigned when the scan networks is completed;
|
||||||
|
- WL_NO_SHIELD: assigned when no WiFi shield is present;
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
if (radioConfig.preferences.wifi_ap_mode) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.softAPIP().toString().c_str()));
|
||||||
|
} else {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str()));
|
||||||
|
}
|
||||||
|
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found");
|
||||||
|
} else if (WiFi.status() == WL_CONNECTION_LOST) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost");
|
||||||
|
} else if (WiFi.status() == WL_CONNECT_FAILED) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed");
|
||||||
|
//} else if (WiFi.status() == WL_DISCONNECTED) {
|
||||||
|
// display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disconnected");
|
||||||
|
} else if (WiFi.status() == WL_IDLE_STATUS) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting");
|
||||||
|
} else {
|
||||||
|
// Codes:
|
||||||
|
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
|
||||||
|
if (getWifiDisconnectReason() == 2) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Authentication Invalid");
|
||||||
|
} else if (getWifiDisconnectReason() == 3) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "De-authenticated");
|
||||||
|
} else if (getWifiDisconnectReason() == 4) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disassociated Expired");
|
||||||
|
} else if (getWifiDisconnectReason() == 5) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP - Too Many Clients");
|
||||||
|
} else if (getWifiDisconnectReason() == 6) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "NOT_AUTHED");
|
||||||
|
} else if (getWifiDisconnectReason() == 7) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "NOT_ASSOCED");
|
||||||
|
} else if (getWifiDisconnectReason() == 8) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disassociated");
|
||||||
|
} else if (getWifiDisconnectReason() == 9) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "ASSOC_NOT_AUTHED");
|
||||||
|
} else if (getWifiDisconnectReason() == 10) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "DISASSOC_PWRCAP_BAD");
|
||||||
|
} else if (getWifiDisconnectReason() == 11) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "DISASSOC_SUPCHAN_BAD");
|
||||||
|
} else if (getWifiDisconnectReason() == 13) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IE_INVALID");
|
||||||
|
} else if (getWifiDisconnectReason() == 14) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "MIC_FAILURE");
|
||||||
|
} else if (getWifiDisconnectReason() == 15) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP Handshake Timeout");
|
||||||
|
} else if (getWifiDisconnectReason() == 16) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "GROUP_KEY_UPDATE_TIMEOUT");
|
||||||
|
} else if (getWifiDisconnectReason() == 17) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IE_IN_4WAY_DIFFERS");
|
||||||
|
} else if (getWifiDisconnectReason() == 18) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Invalid Group Cipher");
|
||||||
|
} else if (getWifiDisconnectReason() == 19) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Invalid Pairwise Cipher");
|
||||||
|
} else if (getWifiDisconnectReason() == 20) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AKMP_INVALID");
|
||||||
|
} else if (getWifiDisconnectReason() == 21) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "UNSUPP_RSN_IE_VERSION");
|
||||||
|
} else if (getWifiDisconnectReason() == 22) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "INVALID_RSN_IE_CAP");
|
||||||
|
} else if (getWifiDisconnectReason() == 23) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "802_1X_AUTH_FAILED");
|
||||||
|
} else if (getWifiDisconnectReason() == 24) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "CIPHER_SUITE_REJECTED");
|
||||||
|
} else if (getWifiDisconnectReason() == 200) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "BEACON_TIMEOUT");
|
||||||
|
} else if (getWifiDisconnectReason() == 201) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP Not Found");
|
||||||
|
} else if (getWifiDisconnectReason() == 202) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AUTH_FAIL");
|
||||||
|
} else if (getWifiDisconnectReason() == 203) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "ASSOC_FAIL");
|
||||||
|
} else if (getWifiDisconnectReason() == 204) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "HANDSHAKE_TIMEOUT");
|
||||||
|
} else if (getWifiDisconnectReason() == 205) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed");
|
||||||
|
} else {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unknown Status");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((millis() / 1000) % 2) {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName));
|
||||||
|
} else {
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "PWD: " + String(wifiPsw));
|
||||||
|
}
|
||||||
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "http://meshtastic.local");
|
||||||
|
|
||||||
|
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||||
|
#ifdef SHOW_REDRAWS
|
||||||
|
if (heartbeat)
|
||||||
|
display->setPixel(0, 0);
|
||||||
|
heartbeat = !heartbeat;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
displayedNodeNum = 0; // Not currently showing a node pane
|
displayedNodeNum = 0; // Not currently showing a node pane
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
char batStr[20];
|
char batStr[20];
|
||||||
if (powerStatus->getHasBattery())
|
if (powerStatus->getHasBattery()) {
|
||||||
{
|
|
||||||
int batV = powerStatus->getBatteryVoltageMv() / 1000;
|
int batV = powerStatus->getBatteryVoltageMv() / 1000;
|
||||||
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
|
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
|
||||||
|
|
||||||
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c",
|
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(),
|
||||||
batV,
|
powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' ');
|
||||||
batCv,
|
|
||||||
powerStatus->getBatteryChargePercent(),
|
|
||||||
powerStatus->getIsCharging() ? '+' : ' ',
|
|
||||||
powerStatus->getHasUSB() ? 'U' : ' ');
|
|
||||||
|
|
||||||
// Line 1
|
// Line 1
|
||||||
display->drawString(x, y, batStr);
|
display->drawString(x, y, batStr);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Line 1
|
// Line 1
|
||||||
display->drawString(x, y, String("USB"));
|
display->drawString(x, y, String("USB"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("Mode " + String(channelSettings.modem_config)), y,
|
||||||
//TODO: Display status of the BT radio
|
"Mode " + String(channelSettings.modem_config));
|
||||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth("BT On"), y, "BT On");
|
|
||||||
|
|
||||||
// Line 2
|
// Line 2
|
||||||
uint32_t currentMillis = millis();
|
uint32_t currentMillis = millis();
|
||||||
@@ -873,19 +1098,21 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
minutes %= 60;
|
minutes %= 60;
|
||||||
hours %= 24;
|
hours %= 24;
|
||||||
|
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, String(days) + "d "
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1,
|
||||||
+ (hours < 10 ? "0" : "") + String(hours) + ":"
|
String(days) + "d " + (hours < 10 ? "0" : "") + String(hours) + ":" + (minutes < 10 ? "0" : "") +
|
||||||
+ (minutes < 10 ? "0" : "") + String(minutes) + ":"
|
String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds));
|
||||||
+ (seconds < 10 ? "0" : "") + String(seconds));
|
|
||||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("Mode " + String(channelSettings.modem_config)), y + FONT_HEIGHT * 1, "Mode " + String(channelSettings.modem_config));
|
#ifndef NO_ESP32
|
||||||
|
// Show CPU Frequency.
|
||||||
|
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("CPU " + String(getCpuFrequencyMhz()) + "MHz"),
|
||||||
|
y + FONT_HEIGHT_SMALL * 1, "CPU " + String(getCpuFrequencyMhz()) + "MHz");
|
||||||
|
#endif
|
||||||
|
|
||||||
// Line 3
|
// Line 3
|
||||||
// TODO: Use this line for WiFi information.
|
drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||||||
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth("WiFi: 192.168.0.100"))) / 2, y + FONT_HEIGHT * 2, "WiFi: 192.168.0.100");
|
|
||||||
|
|
||||||
// Line 4
|
// Line 4
|
||||||
drawGPScoordinates(display, x, y + FONT_HEIGHT * 3, gpsStatus);
|
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
|
||||||
|
|
||||||
|
|
||||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||||
#ifdef SHOW_REDRAWS
|
#ifdef SHOW_REDRAWS
|
||||||
@@ -914,10 +1141,8 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
|||||||
// DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
// DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
||||||
switch (arg->getStatusType()) {
|
switch (arg->getStatusType()) {
|
||||||
case STATUS_TYPE_NODE:
|
case STATUS_TYPE_NODE:
|
||||||
if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
|
if (showingNormalScreen && (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal())) {
|
||||||
setFrames(); // Regen the list of screens
|
setFrames(); // Regen the list of screens
|
||||||
prevFrame = -1; // Force a GUI update
|
|
||||||
setPeriod(1); // Update the screen right away
|
|
||||||
}
|
}
|
||||||
nodeDB.updateGUI = false;
|
nodeDB.updateGUI = false;
|
||||||
nodeDB.updateTextMessage = false;
|
nodeDB.updateTextMessage = false;
|
||||||
|
|||||||
@@ -10,11 +10,12 @@
|
|||||||
#include <SSD1306Wire.h>
|
#include <SSD1306Wire.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "TFT.h"
|
#include "EInkDisplay.h"
|
||||||
|
#include "TFTDisplay.h"
|
||||||
#include "TypedQueue.h"
|
#include "TypedQueue.h"
|
||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
#include "concurrency/LockGuard.h"
|
#include "concurrency/LockGuard.h"
|
||||||
#include "concurrency/PeriodicTask.h"
|
#include "concurrency/OSThread.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ class DebugInfo
|
|||||||
/// Renders the debug screen.
|
/// Renders the debug screen.
|
||||||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
std::string channelName;
|
std::string channelName;
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ class DebugInfo
|
|||||||
* multiple times simultaneously. All state-changing calls are queued and executed
|
* multiple times simultaneously. All state-changing calls are queued and executed
|
||||||
* when the main loop calls us.
|
* when the main loop calls us.
|
||||||
*/
|
*/
|
||||||
class Screen : public concurrency::PeriodicTask
|
class Screen : public concurrency::OSThread
|
||||||
{
|
{
|
||||||
CallbackObserver<Screen, const meshtastic::Status *> powerStatusObserver =
|
CallbackObserver<Screen, const meshtastic::Status *> powerStatusObserver =
|
||||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||||
@@ -179,11 +180,14 @@ class Screen : public concurrency::PeriodicTask
|
|||||||
|
|
||||||
int handleStatusUpdate(const meshtastic::Status *arg);
|
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||||
|
|
||||||
|
/// Used to force (super slow) eink displays to draw critical frames
|
||||||
|
void forceDisplay();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Updates the UI.
|
/// Updates the UI.
|
||||||
//
|
//
|
||||||
// Called periodically from the main loop.
|
// Called periodically from the main loop.
|
||||||
void doTask() final;
|
int32_t runOnce() final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ScreenCmd {
|
struct ScreenCmd {
|
||||||
@@ -201,7 +205,7 @@ class Screen : public concurrency::PeriodicTask
|
|||||||
return true; // claim success if our display is not in use
|
return true; // claim success if our display is not in use
|
||||||
else {
|
else {
|
||||||
bool success = cmdQueue.enqueue(cmd, 0);
|
bool success = cmdQueue.enqueue(cmd, 0);
|
||||||
setPeriod(1); // handle ASAP
|
enabled = true; // handle ASAP (we are the registered reader for cmdQueue, but might have been disabled)
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,11 +219,16 @@ class Screen : public concurrency::PeriodicTask
|
|||||||
/// Rebuilds our list of frames (screens) to default ones.
|
/// Rebuilds our list of frames (screens) to default ones.
|
||||||
void setFrames();
|
void setFrames();
|
||||||
|
|
||||||
|
/// Try to start drawing ASAP
|
||||||
|
void setFastFramerate();
|
||||||
|
|
||||||
/// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame.
|
/// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame.
|
||||||
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
static void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
static void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
|
static void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
/// Queue of commands to execute in doTask.
|
/// Queue of commands to execute in doTask.
|
||||||
TypedQueue<ScreenCmd> cmdQueue;
|
TypedQueue<ScreenCmd> cmdQueue;
|
||||||
/// Whether we are using a display
|
/// Whether we are using a display
|
||||||
@@ -237,6 +246,8 @@ class Screen : public concurrency::PeriodicTask
|
|||||||
/** FIXME cleanup display abstraction */
|
/** FIXME cleanup display abstraction */
|
||||||
#ifdef ST7735_CS
|
#ifdef ST7735_CS
|
||||||
TFTDisplay dispdev;
|
TFTDisplay dispdev;
|
||||||
|
#elif defined(HAS_EINK)
|
||||||
|
EInkDisplay dispdev;
|
||||||
#elif defined(USE_SH1106)
|
#elif defined(USE_SH1106)
|
||||||
SH1106Wire dispdev;
|
SH1106Wire dispdev;
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#ifdef ST7735_CS
|
#ifdef ST7735_CS
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
#include "TFT.h"
|
#include "TFTDisplay.h"
|
||||||
#include "graphics/configs.h"
|
#include "graphics/configs.h"
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <TFT_eSPI.h> // Graphics and font library for ST7735 driver chip
|
#include <TFT_eSPI.h> // Graphics and font library for ST7735 driver chip
|
||||||
@@ -11,8 +11,7 @@ static TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.
|
|||||||
|
|
||||||
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl)
|
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl)
|
||||||
{
|
{
|
||||||
setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will
|
setGeometry(GEOMETRY_RAWMODE, 160, 80);
|
||||||
// work ie GEOMETRY_RAWMODE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the buffer to the display memory
|
// Write the buffer to the display memory
|
||||||
@@ -20,19 +19,17 @@ void TFTDisplay::display(void)
|
|||||||
{
|
{
|
||||||
concurrency::LockGuard g(spiLock);
|
concurrency::LockGuard g(spiLock);
|
||||||
|
|
||||||
#if 1
|
|
||||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||||
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
||||||
for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) {
|
for (uint8_t y = 0; y < displayHeight; y++) {
|
||||||
for (uint8_t x = 0; x < SCREEN_WIDTH; x++) {
|
for (uint8_t x = 0; x < displayWidth; x++) {
|
||||||
|
|
||||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
||||||
auto b = buffer[x + (y / 8) * SCREEN_WIDTH];
|
auto b = buffer[x + (y / 8) * displayWidth];
|
||||||
auto isset = b & (1 << (y & 7));
|
auto isset = b & (1 << (y & 7));
|
||||||
tft.drawPixel(x, y, isset ? TFT_WHITE : TFT_BLACK);
|
tft.drawPixel(x, y, isset ? TFT_WHITE : TFT_BLACK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a command to the display (low level function)
|
// Send a command to the display (low level function)
|
||||||
@@ -52,12 +49,10 @@ bool TFTDisplay::connect()
|
|||||||
pinMode(ST7735_BACKLIGHT_EN, OUTPUT);
|
pinMode(ST7735_BACKLIGHT_EN, OUTPUT);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if 1
|
|
||||||
tft.init();
|
tft.init();
|
||||||
tft.setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
tft.setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
||||||
tft.fillScreen(TFT_BLACK);
|
tft.fillScreen(TFT_BLACK);
|
||||||
// tft.drawRect(0, 0, 40, 10, TFT_PURPLE); // wide rectangle in upper left
|
// tft.drawRect(0, 0, 40, 10, TFT_PURPLE); // wide rectangle in upper left
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
#include "fonts.h"
|
#include "fonts.h"
|
||||||
|
|
||||||
#define FONT_HEIGHT 14 // actually 13 for "Arial 10" but want a little extra space
|
|
||||||
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
|
||||||
// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
|
// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
|
||||||
#define SCREEN_WIDTH 128
|
|
||||||
#define SCREEN_HEIGHT 64
|
|
||||||
#define TRANSITION_FRAMERATE 30 // fps
|
#define TRANSITION_FRAMERATE 30 // fps
|
||||||
#define IDLE_FRAMERATE 1 // in fps
|
#define IDLE_FRAMERATE 1 // in fps
|
||||||
#define COMPASS_DIAM 44
|
#define COMPASS_DIAM 44
|
||||||
|
|||||||
280
src/main.cpp
280
src/main.cpp
@@ -1,44 +1,26 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Main module
|
|
||||||
|
|
||||||
# Modified by Kyle T. Gabriel to fix issue with incorrect GPS data for TTNMapper
|
|
||||||
|
|
||||||
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
#include "Air530GPS.h"
|
||||||
#include "MeshRadio.h"
|
#include "MeshRadio.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NEMAGPS.h"
|
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "UBloxGPS.h"
|
#include "UBloxGPS.h"
|
||||||
#include "concurrency/Periodic.h"
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
// #include "rom/rtc.h"
|
// #include "rom/rtc.h"
|
||||||
#include "DSRRouter.h"
|
#include "DSRRouter.h"
|
||||||
// #include "debug.h"
|
// #include "debug.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
|
#include "concurrency/Periodic.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "meshwifi/meshhttp.h"
|
||||||
|
#include "meshwifi/meshwifi.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "timing.h"
|
#include "target_specific.h"
|
||||||
#include <OneButton.h>
|
#include <OneButton.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
// #include <driver/rtc_io.h>
|
// #include <driver/rtc_io.h>
|
||||||
@@ -54,8 +36,10 @@
|
|||||||
#include "variant.h"
|
#include "variant.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
using namespace concurrency;
|
||||||
|
|
||||||
// We always create a screen object, but we only init it if we find the hardware
|
// We always create a screen object, but we only init it if we find the hardware
|
||||||
graphics::Screen screen(SSD1306_ADDRESS);
|
graphics::Screen *screen;
|
||||||
|
|
||||||
// Global power status
|
// Global power status
|
||||||
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
|
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
|
||||||
@@ -69,8 +53,7 @@ meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
|
|||||||
bool ssd1306_found;
|
bool ssd1306_found;
|
||||||
bool axp192_found;
|
bool axp192_found;
|
||||||
|
|
||||||
DSRRouter realRouter;
|
Router *router = NULL; // Users of router don't care what sort of subclass implements that API
|
||||||
Router &router = realRouter; // Users of router don't care what sort of subclass implements that API
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Application
|
// Application
|
||||||
@@ -120,7 +103,7 @@ const char *getDeviceName()
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t ledBlinker()
|
static int32_t ledBlinker()
|
||||||
{
|
{
|
||||||
static bool ledOn;
|
static bool ledOn;
|
||||||
ledOn ^= 1;
|
ledOn ^= 1;
|
||||||
@@ -128,26 +111,105 @@ static uint32_t ledBlinker()
|
|||||||
setLed(ledOn);
|
setLed(ledOn);
|
||||||
|
|
||||||
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
||||||
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 2 : 1000);
|
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
concurrency::Periodic ledPeriodic(ledBlinker);
|
/// Wrapper to convert our powerFSM stuff into a 'thread'
|
||||||
|
class PowerFSMThread : public OSThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||||
|
PowerFSMThread() : OSThread("PowerFSM") {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int32_t runOnce()
|
||||||
|
{
|
||||||
|
powerFSM.run_machine();
|
||||||
|
|
||||||
|
/// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake
|
||||||
|
/// cpu for serial rx - FIXME)
|
||||||
|
auto state = powerFSM.getState();
|
||||||
|
canSleep = (state != &statePOWER) && (state != &stateSERIAL);
|
||||||
|
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch a GPIO and if we get an IRQ, wake the main thread.
|
||||||
|
* Use to add wake on button press
|
||||||
|
*/
|
||||||
|
void wakeOnIrq(int irq, int mode)
|
||||||
|
{
|
||||||
|
attachInterrupt(
|
||||||
|
irq,
|
||||||
|
[] {
|
||||||
|
BaseType_t higherWake = 0;
|
||||||
|
mainDelay.interruptFromISR(&higherWake);
|
||||||
|
},
|
||||||
|
FALLING);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ButtonThread : public OSThread
|
||||||
|
{
|
||||||
// Prepare for button presses
|
// Prepare for button presses
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
OneButton userButton;
|
OneButton userButton;
|
||||||
#endif
|
#endif
|
||||||
#ifdef BUTTON_PIN_ALT
|
#ifdef BUTTON_PIN_ALT
|
||||||
OneButton userButtonAlt;
|
OneButton userButtonAlt;
|
||||||
#endif
|
#endif
|
||||||
void userButtonPressed()
|
|
||||||
{
|
public:
|
||||||
powerFSM.trigger(EVENT_PRESS);
|
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||||
}
|
ButtonThread() : OSThread("Button")
|
||||||
void userButtonPressedLong()
|
{
|
||||||
{
|
#ifdef BUTTON_PIN
|
||||||
screen.adjustBrightness();
|
userButton = OneButton(BUTTON_PIN, true, true);
|
||||||
}
|
userButton.attachClick(userButtonPressed);
|
||||||
|
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||||
|
wakeOnIrq(BUTTON_PIN, FALLING);
|
||||||
|
#endif
|
||||||
|
#ifdef BUTTON_PIN_ALT
|
||||||
|
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
|
||||||
|
userButtonAlt.attachClick(userButtonPressed);
|
||||||
|
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||||
|
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// If the button is pressed we suppress CPU sleep until release
|
||||||
|
int32_t runOnce()
|
||||||
|
{
|
||||||
|
canSleep = true; // Assume we should not keep the board awake
|
||||||
|
|
||||||
|
#ifdef BUTTON_PIN
|
||||||
|
userButton.tick();
|
||||||
|
canSleep &= userButton.isIdle();
|
||||||
|
#endif
|
||||||
|
#ifdef BUTTON_PIN_ALT
|
||||||
|
userButtonAlt.tick();
|
||||||
|
canSleep &= userButton.isIdle();
|
||||||
|
#endif
|
||||||
|
// if(!canSleep) DEBUG_MSG("Supressing sleep!\n");
|
||||||
|
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void userButtonPressed()
|
||||||
|
{
|
||||||
|
// DEBUG_MSG("press!\n");
|
||||||
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
|
}
|
||||||
|
static void userButtonPressedLong() { screen->adjustBrightness(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
static Periodic *ledPeriodic;
|
||||||
|
static OSThread *powerFSMthread, *buttonThread;
|
||||||
|
|
||||||
|
RadioInterface *rIf = NULL;
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
@@ -172,6 +234,12 @@ void setup()
|
|||||||
digitalWrite(RESET_OLED, 1);
|
digitalWrite(RESET_OLED, 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
OSThread::setup();
|
||||||
|
|
||||||
|
ledPeriodic = new Periodic("Blink", ledBlinker);
|
||||||
|
|
||||||
|
router = new DSRRouter();
|
||||||
|
|
||||||
#ifdef I2C_SDA
|
#ifdef I2C_SDA
|
||||||
Wire.begin(I2C_SDA, I2C_SCL);
|
Wire.begin(I2C_SDA, I2C_SCL);
|
||||||
#else
|
#else
|
||||||
@@ -183,23 +251,12 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Buttons & LED
|
// Buttons & LED
|
||||||
#ifdef BUTTON_PIN
|
buttonThread = new ButtonThread();
|
||||||
userButton = OneButton(BUTTON_PIN, true, true);
|
|
||||||
userButton.attachClick(userButtonPressed);
|
|
||||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
|
||||||
#endif
|
|
||||||
#ifdef BUTTON_PIN_ALT
|
|
||||||
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
|
|
||||||
userButtonAlt.attachClick(userButtonPressed);
|
|
||||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
|
||||||
#endif
|
|
||||||
#ifdef LED_PIN
|
#ifdef LED_PIN
|
||||||
pinMode(LED_PIN, OUTPUT);
|
pinMode(LED_PIN, OUTPUT);
|
||||||
digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
|
digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ledPeriodic.setup();
|
|
||||||
|
|
||||||
// Hello
|
// Hello
|
||||||
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", optstr(APP_VERSION), optstr(HW_VERSION));
|
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", optstr(APP_VERSION), optstr(HW_VERSION));
|
||||||
|
|
||||||
@@ -211,19 +268,19 @@ void setup()
|
|||||||
esp32Setup();
|
esp32Setup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Currently only the tbeam has a PMU
|
|
||||||
power = new Power();
|
|
||||||
power->setup();
|
|
||||||
power->setStatusHandler(powerStatus);
|
|
||||||
powerStatus->observe(&power->newStatus);
|
|
||||||
|
|
||||||
#ifdef NRF52_SERIES
|
#ifdef NRF52_SERIES
|
||||||
nrf52Setup();
|
nrf52Setup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Currently only the tbeam has a PMU
|
||||||
|
power = new Power();
|
||||||
|
power->setStatusHandler(powerStatus);
|
||||||
|
powerStatus->observe(&power->newStatus);
|
||||||
|
power->setup(); // Must be after status handler is installed, so that handler gets notified of the initial configuration
|
||||||
|
|
||||||
// Init our SPI controller (must be before screen and lora)
|
// Init our SPI controller (must be before screen and lora)
|
||||||
initSPI();
|
initSPI();
|
||||||
#ifdef NRF52_SERIES
|
#ifdef NO_ESP32
|
||||||
SPI.begin();
|
SPI.begin();
|
||||||
#else
|
#else
|
||||||
// ESP32
|
// ESP32
|
||||||
@@ -232,14 +289,7 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Initialize the screen first so we can show the logo while we start up everything else.
|
// Initialize the screen first so we can show the logo while we start up everything else.
|
||||||
#ifdef ST7735_CS
|
screen = new graphics::Screen(SSD1306_ADDRESS);
|
||||||
screen.setup();
|
|
||||||
#else
|
|
||||||
if (ssd1306_found)
|
|
||||||
screen.setup();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
screen.print("Started...\n");
|
|
||||||
|
|
||||||
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
|
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
|
||||||
|
|
||||||
@@ -256,23 +306,41 @@ void setup()
|
|||||||
|
|
||||||
if (GPS::_serial_gps) {
|
if (GPS::_serial_gps) {
|
||||||
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
||||||
// assume NEMA at 9600 baud.
|
// assume NMEA at 9600 baud.
|
||||||
DEBUG_MSG("Hoping that NEMA might work\n");
|
// dumb NMEA access only work for serial GPSes)
|
||||||
|
DEBUG_MSG("Hoping that NMEA might work\n");
|
||||||
|
|
||||||
// dumb NEMA access only work for serial GPSes)
|
#ifdef HAS_AIR530_GPS
|
||||||
gps = new NEMAGPS();
|
gps = new Air530GPS();
|
||||||
|
#else
|
||||||
|
gps = new NMEAGPS();
|
||||||
|
#endif
|
||||||
gps->setup();
|
gps->setup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
gps = new NEMAGPS();
|
gps = new NMEAGPS();
|
||||||
gps->setup();
|
gps->setup();
|
||||||
#endif
|
#endif
|
||||||
gpsStatus->observe(&gps->newStatus);
|
if (gps)
|
||||||
|
gpsStatus->observe(&gps->newStatus);
|
||||||
|
else
|
||||||
|
DEBUG_MSG("Warning: No GPS found - running without GPS\n");
|
||||||
nodeStatus->observe(&nodeDB.newStatus);
|
nodeStatus->observe(&nodeDB.newStatus);
|
||||||
|
|
||||||
service.init();
|
service.init();
|
||||||
|
|
||||||
|
// Don't call screen setup until after nodedb is setup (because we need
|
||||||
|
// the current region name)
|
||||||
|
#if defined(ST7735_CS) || defined(HAS_EINK)
|
||||||
|
screen->setup();
|
||||||
|
#else
|
||||||
|
if (ssd1306_found)
|
||||||
|
screen->setup();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
screen->print("Started...\n");
|
||||||
|
|
||||||
// We have now loaded our saved preferences from flash
|
// We have now loaded our saved preferences from flash
|
||||||
|
|
||||||
// ONCE we will factory reset the GPS for bug #327
|
// ONCE we will factory reset the GPS for bug #327
|
||||||
@@ -289,8 +357,7 @@ void setup()
|
|||||||
digitalWrite(SX1262_ANT_SW, 1);
|
digitalWrite(SX1262_ANT_SW, 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
||||||
RadioInterface *rIf = NULL;
|
|
||||||
|
|
||||||
#if defined(RF95_IRQ)
|
#if defined(RF95_IRQ)
|
||||||
if (!rIf) {
|
if (!rIf) {
|
||||||
@@ -325,13 +392,18 @@ void setup()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Initialize Wifi
|
||||||
|
initWifi();
|
||||||
|
|
||||||
|
|
||||||
if (!rIf)
|
if (!rIf)
|
||||||
recordCriticalError(ErrNoRadio);
|
recordCriticalError(ErrNoRadio);
|
||||||
else
|
else
|
||||||
router.addInterface(rIf);
|
router->addInterface(rIf);
|
||||||
|
|
||||||
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
|
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
|
||||||
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
|
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
|
||||||
|
powerFSMthread = new PowerFSMThread();
|
||||||
|
|
||||||
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
|
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
|
||||||
setCPUFast(false); // 80MHz is fine for our slow peripherals
|
setCPUFast(false); // 80MHz is fine for our slow peripherals
|
||||||
@@ -354,20 +426,12 @@ uint32_t axpDebugRead()
|
|||||||
return 30 * 1000;
|
return 30 * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
concurrency::Periodic axpDebugOutput(axpDebugRead);
|
Periodic axpDebugOutput(axpDebugRead);
|
||||||
axpDebugOutput.setup();
|
axpDebugOutput.setup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
|
|
||||||
|
|
||||||
gps->loop(); // FIXME, remove from main, instead block on read
|
|
||||||
router.loop();
|
|
||||||
powerFSM.run_machine();
|
|
||||||
service.loop();
|
|
||||||
|
|
||||||
concurrency::periodicScheduler.loop();
|
|
||||||
// axpDebugOutput.loop();
|
// axpDebugOutput.loop();
|
||||||
|
|
||||||
#ifdef DEBUG_PORT
|
#ifdef DEBUG_PORT
|
||||||
@@ -379,42 +443,30 @@ void loop()
|
|||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
esp32Loop();
|
esp32Loop();
|
||||||
#endif
|
#endif
|
||||||
#ifdef TBEAM_V10
|
|
||||||
power->loop();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef BUTTON_PIN
|
// For debugging
|
||||||
userButton.tick();
|
// if (rIf) ((RadioLibInterface *)rIf)->isActivelyReceiving();
|
||||||
#endif
|
|
||||||
#ifdef BUTTON_PIN_ALT
|
|
||||||
userButtonAlt.tick();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Show boot screen for first 3 seconds, then switch to normal operation.
|
|
||||||
static bool showingBootScreen = true;
|
|
||||||
if (showingBootScreen && (timing::millis() > 3000)) {
|
|
||||||
screen.stopBootScreen();
|
|
||||||
showingBootScreen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_STACK
|
#ifdef DEBUG_STACK
|
||||||
static uint32_t lastPrint = 0;
|
static uint32_t lastPrint = 0;
|
||||||
if (timing::millis() - lastPrint > 10 * 1000L) {
|
if (millis() - lastPrint > 10 * 1000L) {
|
||||||
lastPrint = timing::millis();
|
lastPrint = millis();
|
||||||
meshtastic::printThreadInfo("main");
|
meshtastic::printThreadInfo("main");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Update the screen last, after we've figured out what to show.
|
// TODO: This should go into a thread handled by FreeRTOS.
|
||||||
screen.debug_info()->setChannelNameStatus(getChannelName());
|
handleWebResponse();
|
||||||
|
|
||||||
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
|
||||||
// i.e. don't just keep spinning in loop as fast as we can.
|
|
||||||
// DEBUG_MSG("msecs %d\n", msecstosleep);
|
|
||||||
|
|
||||||
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons
|
service.loop();
|
||||||
// feel slow
|
|
||||||
msecstosleep = 10;
|
|
||||||
|
|
||||||
delay(msecstosleep);
|
long delayMsec = mainController.runOrDelay();
|
||||||
|
|
||||||
|
/* if (mainController.nextThread && delayMsec)
|
||||||
|
DEBUG_MSG("Next %s in %ld\n", mainController.nextThread->ThreadName.c_str(),
|
||||||
|
mainController.nextThread->tillRun(millis())); */
|
||||||
|
|
||||||
|
// We want to sleep as long as possible here - because it saves power
|
||||||
|
mainDelay.delay(delayMsec);
|
||||||
|
// if (didWake) DEBUG_MSG("wake!\n");
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/main.h
18
src/main.h
@@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "graphics/Screen.h"
|
|
||||||
#include "PowerStatus.h"
|
|
||||||
#include "GPSStatus.h"
|
#include "GPSStatus.h"
|
||||||
#include "NodeStatus.h"
|
#include "NodeStatus.h"
|
||||||
|
#include "PowerStatus.h"
|
||||||
|
#include "graphics/Screen.h"
|
||||||
|
|
||||||
extern bool axp192_found;
|
extern bool axp192_found;
|
||||||
extern bool ssd1306_found;
|
extern bool ssd1306_found;
|
||||||
@@ -11,16 +11,14 @@ extern bool isCharging;
|
|||||||
extern bool isUSBPowered;
|
extern bool isUSBPowered;
|
||||||
|
|
||||||
// Global Screen singleton.
|
// Global Screen singleton.
|
||||||
extern graphics::Screen screen;
|
extern graphics::Screen *screen;
|
||||||
//extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
// extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
||||||
|
|
||||||
//extern meshtastic::PowerStatus *powerStatus;
|
// extern meshtastic::PowerStatus *powerStatus;
|
||||||
//extern meshtastic::GPSStatus *gpsStatus;
|
// extern meshtastic::GPSStatus *gpsStatus;
|
||||||
//extern meshtastic::NodeStatusHandler *nodeStatusHandler;
|
// extern meshtastic::NodeStatusHandler *nodeStatusHandler;
|
||||||
|
|
||||||
// Return a human readable string of the form "Meshtastic_ab13"
|
// Return a human readable string of the form "Meshtastic_ab13"
|
||||||
const char *getDeviceName();
|
const char *getDeviceName();
|
||||||
|
|
||||||
void getMacAddr(uint8_t *dmac);
|
void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop();
|
||||||
|
|
||||||
void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop();
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "PacketHistory.h"
|
#include "PacketHistory.h"
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,66 +6,17 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "mesh.pb.h"
|
#include "mesh.pb.h"
|
||||||
|
|
||||||
// US channel settings
|
// Map from old region names to new region enums
|
||||||
#define CH0_US 903.08f // MHz
|
struct RegionInfo {
|
||||||
#define CH_SPACING_US 2.16f // MHz
|
RegionCode code;
|
||||||
#define NUM_CHANNELS_US 13
|
uint8_t numChannels;
|
||||||
|
uint8_t powerLimit; // Or zero for not set
|
||||||
|
float freq;
|
||||||
|
float spacing;
|
||||||
|
const char *name; // EU433 etc
|
||||||
|
};
|
||||||
|
|
||||||
// EU433 channel settings
|
extern const RegionInfo regions[];
|
||||||
#define CH0_EU433 433.175f // MHz
|
extern const RegionInfo *myRegion;
|
||||||
#define CH_SPACING_EU433 0.2f // MHz
|
|
||||||
#define NUM_CHANNELS_EU433 8
|
|
||||||
|
|
||||||
// EU865 channel settings
|
extern void initRegion();
|
||||||
#define CH0_EU865 865.2f // MHz
|
|
||||||
#define CH_SPACING_EU865 0.3f // MHz
|
|
||||||
#define NUM_CHANNELS_EU865 10
|
|
||||||
|
|
||||||
// CN channel settings
|
|
||||||
#define CH0_CN 470.0f // MHz
|
|
||||||
#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510
|
|
||||||
#define NUM_CHANNELS_CN 20
|
|
||||||
|
|
||||||
// JP channel settings (AS1 bandplan)
|
|
||||||
#define CH0_JP 920.0f // MHz
|
|
||||||
#define CH_SPACING_JP 0.5f
|
|
||||||
#define NUM_CHANNELS_JP 10
|
|
||||||
|
|
||||||
// TW channel settings (AS2 bandplan 923-925MHz)
|
|
||||||
#define CH0_TW 923.0f // MHz
|
|
||||||
#define CH_SPACING_TW 0.2
|
|
||||||
#define NUM_CHANNELS_TW 10
|
|
||||||
|
|
||||||
// FIXME add defs for other regions and use them here
|
|
||||||
#ifdef HW_VERSION_US
|
|
||||||
#define CH0 CH0_US
|
|
||||||
#define CH_SPACING CH_SPACING_US
|
|
||||||
#define NUM_CHANNELS NUM_CHANNELS_US
|
|
||||||
#elif defined(HW_VERSION_EU433)
|
|
||||||
#define CH0 CH0_EU433
|
|
||||||
#define CH_SPACING CH_SPACING_EU433
|
|
||||||
#define NUM_CHANNELS NUM_CHANNELS_EU433
|
|
||||||
#elif defined(HW_VERSION_EU865)
|
|
||||||
#define CH0 CH0_EU865
|
|
||||||
#define CH_SPACING CH_SPACING_EU865
|
|
||||||
#define NUM_CHANNELS NUM_CHANNELS_EU865
|
|
||||||
#elif defined(HW_VERSION_CN)
|
|
||||||
#define CH0 CH0_CN
|
|
||||||
#define CH_SPACING CH_SPACING_CN
|
|
||||||
#define NUM_CHANNELS NUM_CHANNELS_CN
|
|
||||||
#elif defined(HW_VERSION_JP)
|
|
||||||
// Also called AS1 bandplan
|
|
||||||
#define CH0 CH0_JP
|
|
||||||
#define CH_SPACING CH_SPACING_JP
|
|
||||||
#define NUM_CHANNELS NUM_CHANNELS_JP
|
|
||||||
#elif defined(HW_VERSION_TW)
|
|
||||||
// Also called AS2 bandplan
|
|
||||||
#define CH0 CH0_TW
|
|
||||||
#define CH_SPACING CH_SPACING_TW
|
|
||||||
#define NUM_CHANNELS NUM_CHANNELS_TW
|
|
||||||
#else
|
|
||||||
// HW version not set - assume US
|
|
||||||
#define CH0 CH0_US
|
|
||||||
#define CH_SPACING CH_SPACING_US
|
|
||||||
#define NUM_CHANNELS NUM_CHANNELS_US
|
|
||||||
#endif
|
|
||||||
@@ -10,10 +10,10 @@
|
|||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
#include "timing.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
||||||
@@ -49,14 +49,14 @@ MeshService service;
|
|||||||
|
|
||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
|
|
||||||
static uint32_t sendOwnerCb()
|
static int32_t sendOwnerCb()
|
||||||
{
|
{
|
||||||
service.sendOurOwner();
|
service.sendOurOwner();
|
||||||
|
|
||||||
return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000;
|
return getPref_send_owner_interval() * getPref_position_broadcast_secs() * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static concurrency::Periodic sendOwnerPeriod(sendOwnerCb);
|
static concurrency::Periodic *sendOwnerPeriod;
|
||||||
|
|
||||||
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
|
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
|
||||||
{
|
{
|
||||||
@@ -65,17 +65,18 @@ MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
|
|||||||
|
|
||||||
void MeshService::init()
|
void MeshService::init()
|
||||||
{
|
{
|
||||||
sendOwnerPeriod.setup();
|
sendOwnerPeriod = new concurrency::Periodic("SendOwner", sendOwnerCb);
|
||||||
|
|
||||||
nodeDB.init();
|
nodeDB.init();
|
||||||
|
|
||||||
assert(gps);
|
if (gps)
|
||||||
gpsObserver.observe(&gps->newStatus);
|
gpsObserver.observe(&gps->newStatus);
|
||||||
packetReceivedObserver.observe(&router.notifyPacketReceived);
|
packetReceivedObserver.observe(&router->notifyPacketReceived);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
|
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
|
||||||
{
|
{
|
||||||
MeshPacket *p = router.allocForSending();
|
MeshPacket *p = router->allocForSending();
|
||||||
p->to = dest;
|
p->to = dest;
|
||||||
p->decoded.want_response = wantReplies;
|
p->decoded.want_response = wantReplies;
|
||||||
p->decoded.which_payload = SubPacket_user_tag;
|
p->decoded.which_payload = SubPacket_user_tag;
|
||||||
@@ -122,7 +123,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
|
|||||||
sendOurOwner(mp->from);
|
sendOurOwner(mp->from);
|
||||||
|
|
||||||
String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n";
|
String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n";
|
||||||
screen.print(lcd.c_str());
|
screen->print(lcd.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mp;
|
return mp;
|
||||||
@@ -140,7 +141,7 @@ void MeshService::handleIncomingPosition(const MeshPacket *mp)
|
|||||||
tv.tv_sec = secs;
|
tv.tv_sec = secs;
|
||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
perhapsSetRTC(&tv);
|
perhapsSetRTC(RTCQualityFromNet, &tv);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DEBUG_MSG("Ignoring incoming packet - not a position\n");
|
DEBUG_MSG("Ignoring incoming packet - not a position\n");
|
||||||
@@ -151,12 +152,8 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
|
|||||||
{
|
{
|
||||||
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
|
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
|
||||||
|
|
||||||
// If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work)
|
// If it is a position packet, perhaps set our clock - this must be before nodeDB.updateFrom
|
||||||
if (!gps->isConnected)
|
handleIncomingPosition(mp);
|
||||||
handleIncomingPosition(mp);
|
|
||||||
else {
|
|
||||||
DEBUG_MSG("Ignoring incoming time, because we have a GPS\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) {
|
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) {
|
||||||
mp = handleFromRadioUser(mp);
|
mp = handleFromRadioUser(mp);
|
||||||
@@ -198,12 +195,21 @@ void MeshService::loop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The radioConfig object just changed, call this to force the hw to change to the new settings
|
/// The radioConfig object just changed, call this to force the hw to change to the new settings
|
||||||
void MeshService::reloadConfig()
|
bool MeshService::reloadConfig()
|
||||||
{
|
{
|
||||||
// If we can successfully set this radio to these settings, save them to disk
|
// If we can successfully set this radio to these settings, save them to disk
|
||||||
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);
|
configChanged.notifyObservers(NULL);
|
||||||
nodeDB.saveToDisk();
|
nodeDB.saveToDisk();
|
||||||
|
|
||||||
|
return didReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
|
||||||
|
void MeshService::reloadOwner()
|
||||||
|
{
|
||||||
|
sendOurOwner();
|
||||||
|
nodeDB.saveToDisk();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -221,8 +227,8 @@ void MeshService::handleToRadio(MeshPacket &p)
|
|||||||
if (p.id == 0)
|
if (p.id == 0)
|
||||||
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
||||||
|
|
||||||
p.rx_time = getValidTime(); // Record the time the packet arrived from the phone
|
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
|
||||||
// (so we update our nodedb for the local node)
|
// (so we update our nodedb for the local node)
|
||||||
|
|
||||||
// Send the packet into the mesh
|
// Send the packet into the mesh
|
||||||
|
|
||||||
@@ -242,10 +248,10 @@ void MeshService::sendToMesh(MeshPacket *p)
|
|||||||
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...)
|
||||||
|
|
||||||
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
|
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
|
||||||
// nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless
|
// nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless
|
||||||
// devices can get time.
|
// devices can get time.
|
||||||
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) {
|
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) {
|
||||||
if (!gps->isConnected) {
|
if (getRTCQuality() < RTCQualityGPS) {
|
||||||
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
|
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
|
||||||
p->decoded.position.time = 0;
|
p->decoded.position.time = 0;
|
||||||
} else
|
} else
|
||||||
@@ -253,7 +259,7 @@ void MeshService::sendToMesh(MeshPacket *p)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
|
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
|
||||||
router.sendLocal(p);
|
router->sendLocal(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
|
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
|
||||||
@@ -275,12 +281,13 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
|
|||||||
assert(node->has_position);
|
assert(node->has_position);
|
||||||
|
|
||||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||||
MeshPacket *p = router.allocForSending();
|
MeshPacket *p = router->allocForSending();
|
||||||
p->to = dest;
|
p->to = dest;
|
||||||
p->decoded.which_payload = SubPacket_position_tag;
|
p->decoded.which_payload = SubPacket_position_tag;
|
||||||
p->decoded.position = node->position;
|
p->decoded.position = node->position;
|
||||||
p->decoded.want_response = wantReplies;
|
p->decoded.want_response = wantReplies;
|
||||||
p->decoded.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
p->decoded.position.time =
|
||||||
|
getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||||
sendToMesh(p);
|
sendToMesh(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,19 +295,20 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||||
MeshPacket *p = router.allocForSending();
|
MeshPacket *p = router->allocForSending();
|
||||||
p->decoded.which_payload = SubPacket_position_tag;
|
p->decoded.which_payload = SubPacket_position_tag;
|
||||||
|
|
||||||
Position &pos = p->decoded.position;
|
Position &pos = p->decoded.position;
|
||||||
// !zero or !zero lat/long means valid
|
|
||||||
if (gps->latitude != 0 || gps->longitude != 0) {
|
if (gps->hasLock()) {
|
||||||
if (gps->altitude != 0)
|
if (gps->altitude != 0)
|
||||||
pos.altitude = gps->altitude;
|
pos.altitude = gps->altitude;
|
||||||
pos.latitude_i = gps->latitude;
|
pos.latitude_i = gps->latitude;
|
||||||
pos.longitude_i = gps->longitude;
|
pos.longitude_i = gps->longitude;
|
||||||
pos.time = getValidTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pos.time = getValidTime(RTCQualityGPS);
|
||||||
|
|
||||||
// Include our current battery voltage in our position announcement
|
// Include our current battery voltage in our position announcement
|
||||||
pos.battery_level = powerStatus->getBatteryChargePercent();
|
pos.battery_level = powerStatus->getBatteryChargePercent();
|
||||||
updateBatteryLevel(pos.battery_level);
|
updateBatteryLevel(pos.battery_level);
|
||||||
@@ -309,8 +317,8 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
|||||||
|
|
||||||
// We limit our GPS broadcasts to a max rate
|
// We limit our GPS broadcasts to a max rate
|
||||||
static uint32_t lastGpsSend;
|
static uint32_t lastGpsSend;
|
||||||
uint32_t now = timing::millis();
|
uint32_t now = millis();
|
||||||
if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000) {
|
if (lastGpsSend == 0 || now - lastGpsSend > getPref_position_broadcast_secs() * 1000) {
|
||||||
lastGpsSend = now;
|
lastGpsSend = now;
|
||||||
DEBUG_MSG("Sending position to mesh\n");
|
DEBUG_MSG("Sending position to mesh\n");
|
||||||
|
|
||||||
|
|||||||
@@ -63,11 +63,13 @@ class MeshService
|
|||||||
*/
|
*/
|
||||||
void handleToRadio(MeshPacket &p);
|
void handleToRadio(MeshPacket &p);
|
||||||
|
|
||||||
/// The radioConfig object just changed, call this to force the hw to change to the new settings
|
/** The radioConfig object just changed, call this to force the hw to change to the new settings
|
||||||
void reloadConfig();
|
* @return true if client devices should be sent a new set of radio configs
|
||||||
|
*/
|
||||||
|
bool reloadConfig();
|
||||||
|
|
||||||
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
|
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
|
||||||
void reloadOwner() { sendOurOwner(); }
|
void reloadOwner();
|
||||||
|
|
||||||
/// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least
|
/// 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
|
/// sends our owner
|
||||||
|
|||||||
@@ -6,13 +6,16 @@
|
|||||||
|
|
||||||
#include "CryptoEngine.h"
|
#include "CryptoEngine.h"
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
|
#include "MeshRadio.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PacketHistory.h"
|
#include "PacketHistory.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "meshwifi/meshwifi.h"
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
|
|
||||||
@@ -32,7 +35,14 @@ DeviceState versions used to be defined in the .proto file but really only this
|
|||||||
#define DEVICESTATE_CUR_VER 11
|
#define DEVICESTATE_CUR_VER 11
|
||||||
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifdef PORTDUINO
|
||||||
|
// Portduino version
|
||||||
|
#include "PortduinoFS.h"
|
||||||
|
#define FS PortduinoFS
|
||||||
|
#define FSBegin() true
|
||||||
|
#define FILE_O_WRITE "w"
|
||||||
|
#define FILE_O_READ "r"
|
||||||
|
#elif !defined(NO_ESP32)
|
||||||
// ESP32 version
|
// ESP32 version
|
||||||
#include "SPIFFS.h"
|
#include "SPIFFS.h"
|
||||||
#define FS SPIFFS
|
#define FS SPIFFS
|
||||||
@@ -95,29 +105,27 @@ const char *getChannelName()
|
|||||||
|
|
||||||
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {}
|
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {}
|
||||||
|
|
||||||
void NodeDB::resetRadioConfig()
|
bool NodeDB::resetRadioConfig()
|
||||||
{
|
{
|
||||||
|
bool didFactoryReset = false;
|
||||||
|
|
||||||
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
|
/// 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,
|
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
||||||
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
||||||
|
|
||||||
if (radioConfig.preferences.sds_secs == 0) {
|
if (radioConfig.preferences.factory_reset) {
|
||||||
DEBUG_MSG("RadioConfig reset!\n");
|
DEBUG_MSG("Performing factory reset!\n");
|
||||||
radioConfig.preferences.send_owner_interval = 4; // per sw-design.md
|
installDefaultDeviceState();
|
||||||
radioConfig.preferences.position_broadcast_secs = 15 * 60;
|
didFactoryReset = true;
|
||||||
radioConfig.preferences.wait_bluetooth_secs = 120;
|
} else if (!channelSettings.psk.size) {
|
||||||
radioConfig.preferences.screen_on_secs = 5 * 60;
|
DEBUG_MSG("Setting default preferences!\n");
|
||||||
radioConfig.preferences.mesh_sds_timeout_secs = 2 * 60 * 60;
|
|
||||||
radioConfig.preferences.phone_sds_timeout_sec = 2 * 60 * 60;
|
|
||||||
radioConfig.preferences.sds_secs = 365 * 24 * 60 * 60; // one year
|
|
||||||
radioConfig.preferences.ls_secs = 60 * 60;
|
|
||||||
radioConfig.preferences.phone_timeout_secs = 15 * 60;
|
|
||||||
radioConfig.has_channel_settings = true;
|
radioConfig.has_channel_settings = true;
|
||||||
radioConfig.has_preferences = true;
|
radioConfig.has_preferences = true;
|
||||||
|
|
||||||
// radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
|
// radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
|
||||||
// channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide bandwidth
|
// channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide
|
||||||
// so incompatible radios can talk together
|
// bandwidth so incompatible radios can talk together
|
||||||
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
||||||
|
|
||||||
channelSettings.tx_power = 0; // default
|
channelSettings.tx_power = 0; // default
|
||||||
@@ -139,13 +147,22 @@ void NodeDB::resetRadioConfig()
|
|||||||
radioConfig.preferences.wait_bluetooth_secs = 30;
|
radioConfig.preferences.wait_bluetooth_secs = 30;
|
||||||
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
||||||
radioConfig.preferences.ls_secs = 60;
|
radioConfig.preferences.ls_secs = 60;
|
||||||
|
radioConfig.preferences.region = RegionCode_TW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return didFactoryReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::installDefaultDeviceState()
|
void NodeDB::installDefaultDeviceState()
|
||||||
{
|
{
|
||||||
|
// We try to preserve the region setting because it will really bum users out if we discard it
|
||||||
|
String oldRegion = myNodeInfo.region;
|
||||||
|
RegionCode oldRegionCode = radioConfig.preferences.region;
|
||||||
|
|
||||||
memset(&devicestate, 0, sizeof(devicestate));
|
memset(&devicestate, 0, sizeof(devicestate));
|
||||||
|
|
||||||
|
*numNodes = 0; // Forget node DB
|
||||||
|
|
||||||
// init our devicestate with valid flags so protobuf writing/reading will work
|
// init our devicestate with valid flags so protobuf writing/reading will work
|
||||||
devicestate.has_my_node = true;
|
devicestate.has_my_node = true;
|
||||||
devicestate.has_radio = true;
|
devicestate.has_radio = true;
|
||||||
@@ -174,6 +191,12 @@ void NodeDB::installDefaultDeviceState()
|
|||||||
// owner.short_name now
|
// owner.short_name now
|
||||||
sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]);
|
sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]);
|
||||||
sprintf(owner.short_name, "?%02X", (unsigned)(myNodeInfo.my_node_num & 0xff));
|
sprintf(owner.short_name, "?%02X", (unsigned)(myNodeInfo.my_node_num & 0xff));
|
||||||
|
|
||||||
|
// Restore region if possible
|
||||||
|
if (oldRegionCode != RegionCode_Unset)
|
||||||
|
radioConfig.preferences.region = oldRegionCode;
|
||||||
|
if (oldRegion.length())
|
||||||
|
strcpy(myNodeInfo.region, oldRegion.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::init()
|
void NodeDB::init()
|
||||||
@@ -206,13 +229,32 @@ void NodeDB::init()
|
|||||||
|
|
||||||
// We set these _after_ loading from disk - because they come from the build and are more trusted than
|
// We set these _after_ loading from disk - because they come from the build and are more trusted than
|
||||||
// what is stored in flash
|
// what is stored in flash
|
||||||
strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region));
|
if (xstr(HW_VERSION)[0])
|
||||||
|
strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region));
|
||||||
|
else
|
||||||
|
DEBUG_MSG("This build does not specify a HW_VERSION\n"); // Eventually new builds will no longer include this build flag
|
||||||
|
|
||||||
|
// Check for the old style of region code strings, if found, convert to the new enum.
|
||||||
|
// Those strings will look like "1.0-EU433"
|
||||||
|
if (radioConfig.preferences.region == RegionCode_Unset && strncmp(myNodeInfo.region, "1.0-", 4) == 0) {
|
||||||
|
const char *regionStr = myNodeInfo.region + 4; // EU433 or whatever
|
||||||
|
for (const RegionInfo *r = regions; r->code != RegionCode_Unset; r++)
|
||||||
|
if (strcmp(r->name, regionStr) == 0) {
|
||||||
|
radioConfig.preferences.region = r->code;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the global myRegion
|
||||||
|
initRegion();
|
||||||
|
|
||||||
strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
|
strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
|
||||||
strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
|
strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
|
||||||
|
|
||||||
resetRadioConfig(); // If bogus settings got saved, then fix them
|
resetRadioConfig(); // If bogus settings got saved, then fix them
|
||||||
|
|
||||||
DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes);
|
DEBUG_MSG("legacy_region=%s, region=%d, NODENUM=0x%x, dbsize=%d\n", myNodeInfo.region, radioConfig.preferences.region,
|
||||||
|
myNodeInfo.my_node_num, *numNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We reserve a few nodenums for future use
|
// We reserve a few nodenums for future use
|
||||||
@@ -377,10 +419,10 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
|||||||
|
|
||||||
switch (p.which_payload) {
|
switch (p.which_payload) {
|
||||||
case SubPacket_position_tag: {
|
case SubPacket_position_tag: {
|
||||||
// we carefully preserve the old time, because we always trust our local timestamps more
|
// we always trust our local timestamps more
|
||||||
uint32_t oldtime = info->position.time;
|
|
||||||
info->position = p.position;
|
info->position = p.position;
|
||||||
info->position.time = oldtime;
|
if (mp.rx_time)
|
||||||
|
info->position.time = mp.rx_time;
|
||||||
info->has_position = true;
|
info->has_position = true;
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
@@ -399,6 +441,12 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
|||||||
updateTextMessage = true;
|
updateTextMessage = true;
|
||||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
|
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
|
|
||||||
|
// This is going into the wifidev feature branch
|
||||||
|
// Only update the WebUI if WiFi is enabled
|
||||||
|
//#if WiFi_MODE != 0
|
||||||
|
// notifyWebUI();
|
||||||
|
//#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -46,8 +46,13 @@ class NodeDB
|
|||||||
/// write to flash
|
/// write to flash
|
||||||
void saveToDisk();
|
void saveToDisk();
|
||||||
|
|
||||||
// Reinit radio config if needed, because sometimes a buggy android app might send us bogus settings
|
/** Reinit radio config if needed, because either:
|
||||||
void resetRadioConfig();
|
* a) sometimes a buggy android app might send us bogus settings or
|
||||||
|
* b) the client set factory_reset
|
||||||
|
*
|
||||||
|
* @return true if the config was completely reset, in that case, we should send it back to the client
|
||||||
|
*/
|
||||||
|
bool resetRadioConfig();
|
||||||
|
|
||||||
/// given a subpacket sniffed from the network, update our DB state
|
/// given a subpacket sniffed from the network, update our DB state
|
||||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||||
@@ -132,4 +137,25 @@ their nodes
|
|||||||
*
|
*
|
||||||
* https://github.com/meshtastic/Meshtastic-device/issues/269
|
* https://github.com/meshtastic/Meshtastic-device/issues/269
|
||||||
*/
|
*/
|
||||||
const char *getChannelName();
|
const char *getChannelName();
|
||||||
|
|
||||||
|
#define PREF_GET(name, defaultVal) \
|
||||||
|
inline uint32_t getPref_##name() { return radioConfig.preferences.name ? radioConfig.preferences.name : (defaultVal); }
|
||||||
|
|
||||||
|
PREF_GET(send_owner_interval, 4)
|
||||||
|
PREF_GET(position_broadcast_secs, 15 * 60)
|
||||||
|
|
||||||
|
// Each time we wake into the DARK state allow 1 minute to send and receive BLE packets to the phone
|
||||||
|
PREF_GET(wait_bluetooth_secs, 60)
|
||||||
|
|
||||||
|
PREF_GET(screen_on_secs, 60)
|
||||||
|
PREF_GET(mesh_sds_timeout_secs, 2 * 60 * 60)
|
||||||
|
PREF_GET(phone_sds_timeout_sec, 2 * 60 * 60)
|
||||||
|
PREF_GET(sds_secs, 365 * 24 * 60 * 60)
|
||||||
|
|
||||||
|
// We default to sleeping (with bluetooth off for 5 minutes at a time). This seems to be a good tradeoff between
|
||||||
|
// latency for the user sending messages and power savings because of not having to run (expensive) ESP32 bluetooth
|
||||||
|
PREF_GET(ls_secs, 5 * 60)
|
||||||
|
|
||||||
|
PREF_GET(phone_timeout_secs, 15 * 60)
|
||||||
|
PREF_GET(min_wake_secs, 10)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "PacketHistory.h"
|
#include "PacketHistory.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "../timing.h"
|
|
||||||
|
|
||||||
PacketHistory::PacketHistory()
|
PacketHistory::PacketHistory()
|
||||||
{
|
{
|
||||||
@@ -19,7 +18,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate)
|
|||||||
return false; // Not a floodable message ID, so we don't care
|
return false; // Not a floodable message ID, so we don't care
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t now = timing::millis();
|
uint32_t now = millis();
|
||||||
for (size_t i = 0; i < recentPackets.size();) {
|
for (size_t i = 0; i < recentPackets.size();) {
|
||||||
PacketRecord &r = recentPackets[i];
|
PacketRecord &r = recentPackets[i];
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "RadioInterface.h"
|
#include "RadioInterface.h"
|
||||||
#include "timing.h"
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
PhoneAPI::PhoneAPI()
|
PhoneAPI::PhoneAPI()
|
||||||
@@ -21,7 +20,7 @@ void PhoneAPI::init()
|
|||||||
void PhoneAPI::checkConnectionTimeout()
|
void PhoneAPI::checkConnectionTimeout()
|
||||||
{
|
{
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
bool newConnected = (timing::millis() - lastContactMsec < radioConfig.preferences.phone_timeout_secs * 1000L);
|
bool newConnected = (millis() - lastContactMsec < getPref_phone_timeout_secs() * 1000L);
|
||||||
if (!newConnected) {
|
if (!newConnected) {
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
onConnectionChanged(isConnected);
|
onConnectionChanged(isConnected);
|
||||||
@@ -35,7 +34,7 @@ void PhoneAPI::checkConnectionTimeout()
|
|||||||
void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
|
void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
|
||||||
{
|
{
|
||||||
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep
|
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep
|
||||||
lastContactMsec = timing::millis();
|
lastContactMsec = millis();
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
isConnected = true;
|
isConnected = true;
|
||||||
onConnectionChanged(isConnected);
|
onConnectionChanged(isConnected);
|
||||||
@@ -110,7 +109,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_SEND_MY_INFO:
|
case STATE_SEND_MY_INFO:
|
||||||
myNodeInfo.has_gps = gps && gps->isConnected; // Update with latest GPS connect info
|
// If the user has specified they don't want our node to share its location, make sure to tell the phone
|
||||||
|
// app not to send locations on our behalf.
|
||||||
|
myNodeInfo.has_gps = (radioConfig.preferences.location_share == LocationSharing_LocDisabled)
|
||||||
|
? true
|
||||||
|
: (gps && gps->isConnected()); // Update with latest GPS connect info
|
||||||
fromRadioScratch.which_variant = FromRadio_my_info_tag;
|
fromRadioScratch.which_variant = FromRadio_my_info_tag;
|
||||||
fromRadioScratch.variant.my_info = myNodeInfo;
|
fromRadioScratch.variant.my_info = myNodeInfo;
|
||||||
state = STATE_SEND_RADIO;
|
state = STATE_SEND_RADIO;
|
||||||
@@ -118,7 +121,14 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
|||||||
|
|
||||||
case STATE_SEND_RADIO:
|
case STATE_SEND_RADIO:
|
||||||
fromRadioScratch.which_variant = FromRadio_radio_tag;
|
fromRadioScratch.which_variant = FromRadio_radio_tag;
|
||||||
|
|
||||||
fromRadioScratch.variant.radio = radioConfig;
|
fromRadioScratch.variant.radio = radioConfig;
|
||||||
|
|
||||||
|
// NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior.
|
||||||
|
// So even if we internally use 0 to represent 'use default' we still need to send the value we are
|
||||||
|
// using to the app (so that even old phone apps work with new device loads).
|
||||||
|
fromRadioScratch.variant.radio.preferences.ls_secs = getPref_ls_secs();
|
||||||
|
|
||||||
state = STATE_SEND_NODEINFO;
|
state = STATE_SEND_NODEINFO;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -244,7 +254,10 @@ void PhoneAPI::handleSetRadio(const RadioConfig &r)
|
|||||||
{
|
{
|
||||||
radioConfig = r;
|
radioConfig = r;
|
||||||
|
|
||||||
service.reloadConfig();
|
bool didReset = service.reloadConfig();
|
||||||
|
if (didReset) {
|
||||||
|
state = STATE_SEND_MY_INFO; // Squirt a completely new set of configs to the client
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,8 +3,12 @@
|
|||||||
#include "RadioLibRF95.h"
|
#include "RadioLibRF95.h"
|
||||||
#include <configuration.h>
|
#include <configuration.h>
|
||||||
|
|
||||||
#define MAX_POWER 17
|
#define MAX_POWER 20
|
||||||
// if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17
|
// if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17
|
||||||
|
// In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING
|
||||||
|
// if you set power to something higher than 17 or 20 you might fry your board.
|
||||||
|
|
||||||
|
#define POWER_DEFAULT 17 // How much power to use if the user hasn't set a power level
|
||||||
|
|
||||||
RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi)
|
RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi)
|
||||||
: RadioLibInterface(cs, irq, rst, 0, spi)
|
: RadioLibInterface(cs, irq, rst, 0, spi)
|
||||||
@@ -32,9 +36,15 @@ bool RF95Interface::init()
|
|||||||
RadioLibInterface::init();
|
RadioLibInterface::init();
|
||||||
|
|
||||||
applyModemConfig();
|
applyModemConfig();
|
||||||
|
|
||||||
|
if (power == 0)
|
||||||
|
power = POWER_DEFAULT;
|
||||||
|
|
||||||
if (power > MAX_POWER) // This chip has lower power limits than some
|
if (power > MAX_POWER) // This chip has lower power limits than some
|
||||||
power = MAX_POWER;
|
power = MAX_POWER;
|
||||||
|
|
||||||
|
limitPower();
|
||||||
|
|
||||||
iface = lora = new RadioLibRF95(&module);
|
iface = lora = new RadioLibRF95(&module);
|
||||||
|
|
||||||
#ifdef RF95_TCXO
|
#ifdef RF95_TCXO
|
||||||
@@ -150,7 +160,7 @@ void RF95Interface::startReceive()
|
|||||||
|
|
||||||
isReceiving = true;
|
isReceiving = true;
|
||||||
|
|
||||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
// Must be done AFTER, starting receive, because startReceive clears (possibly stale) interrupt pending register bits
|
||||||
enableInterrupt(isrRxLevel0);
|
enableInterrupt(isrRxLevel0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +173,7 @@ bool RF95Interface::isActivelyReceiving()
|
|||||||
bool RF95Interface::sleep()
|
bool RF95Interface::sleep()
|
||||||
{
|
{
|
||||||
// put chipset into sleep mode
|
// put chipset into sleep mode
|
||||||
disableInterrupt();
|
setStandby(); // First cancel any active receving/sending
|
||||||
lora->sleep();
|
lora->sleep();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -6,11 +6,41 @@
|
|||||||
#include "assert.h"
|
#include "assert.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "timing.h"
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
|
|
||||||
|
#define RDEF(name, freq, spacing, num_ch, power_limit) \
|
||||||
|
{ \
|
||||||
|
RegionCode_##name, num_ch, power_limit, freq, spacing, #name \
|
||||||
|
}
|
||||||
|
|
||||||
|
const RegionInfo regions[] = {
|
||||||
|
RDEF(US, 903.08f, 2.16f, 13, 0), RDEF(EU433, 433.175f, 0.2f, 8, 0), RDEF(EU865, 865.2f, 0.3f, 10, 0),
|
||||||
|
RDEF(CN, 470.0f, 2.0f, 20, 0),
|
||||||
|
RDEF(JP, 920.0f, 0.5f, 10, 13), // See https://github.com/meshtastic/Meshtastic-device/issues/346 power level 13
|
||||||
|
RDEF(ANZ, 916.0f, 0.5f, 20, 0), // AU/NZ channel settings 915-928MHz
|
||||||
|
RDEF(KR, 921.9f, 0.2f, 8, 0), // KR channel settings (KR920-923) Start from TTN download channel
|
||||||
|
// freq. (921.9f is for download, others are for uplink)
|
||||||
|
RDEF(TW, 923.0f, 0.2f, 10, 0), // TW channel settings (AS2 bandplan 923-925MHz)
|
||||||
|
RDEF(Unset, 903.08f, 2.16f, 13, 0) // Assume US freqs if unset, Must be last
|
||||||
|
};
|
||||||
|
|
||||||
|
const RegionInfo *myRegion;
|
||||||
|
|
||||||
|
void initRegion()
|
||||||
|
{
|
||||||
|
if (!myRegion) {
|
||||||
|
const RegionInfo *r = regions;
|
||||||
|
for (; r->code != RegionCode_Unset && r->code != radioConfig.preferences.region; r++)
|
||||||
|
;
|
||||||
|
myRegion = r;
|
||||||
|
DEBUG_MSG("Wanted region %d, using %s\n", radioConfig.preferences.region, r->name);
|
||||||
|
|
||||||
|
myNodeInfo.num_channels = myRegion->numChannels; // Tell our android app how many channels we have
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## LoRaWAN for North America
|
* ## LoRaWAN for North America
|
||||||
|
|
||||||
@@ -78,8 +108,6 @@ RadioInterface::RadioInterface()
|
|||||||
{
|
{
|
||||||
assert(sizeof(PacketHeader) == 4 || sizeof(PacketHeader) == 16); // make sure the compiler did what we expected
|
assert(sizeof(PacketHeader) == 4 || sizeof(PacketHeader) == 16); // make sure the compiler did what we expected
|
||||||
|
|
||||||
myNodeInfo.num_channels = NUM_CHANNELS;
|
|
||||||
|
|
||||||
// Can't print strings this early - serial not setup yet
|
// Can't print strings this early - serial not setup yet
|
||||||
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
||||||
}
|
}
|
||||||
@@ -96,9 +124,6 @@ bool RadioInterface::init()
|
|||||||
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
|
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
|
||||||
// time.
|
// time.
|
||||||
|
|
||||||
// we want this thread to run at very high priority, because it is effectively running as a user space ISR
|
|
||||||
start("radio", RADIO_STACK_SIZE, configMAX_PRIORITIES - 1); // Start our worker thread
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +132,7 @@ bool RadioInterface::init()
|
|||||||
* djb2 by Dan Bernstein.
|
* djb2 by Dan Bernstein.
|
||||||
* http://www.cse.yorku.ca/~oz/hash.html
|
* http://www.cse.yorku.ca/~oz/hash.html
|
||||||
*/
|
*/
|
||||||
unsigned long hash(char *str)
|
unsigned long hash(const char *str)
|
||||||
{
|
{
|
||||||
unsigned long hash = 5381;
|
unsigned long hash = 5381;
|
||||||
int c;
|
int c;
|
||||||
@@ -118,8 +143,6 @@ unsigned long hash(char *str)
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define POWER_DEFAULT 17
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pull our channel settings etc... from protobufs to the dumb interface settings
|
* Pull our channel settings etc... from protobufs to the dumb interface settings
|
||||||
*/
|
*/
|
||||||
@@ -129,15 +152,40 @@ void RadioInterface::applyModemConfig()
|
|||||||
// No Sync Words in LORA mode
|
// No Sync Words in LORA mode
|
||||||
|
|
||||||
power = channelSettings.tx_power;
|
power = channelSettings.tx_power;
|
||||||
if (power == 0)
|
|
||||||
power = POWER_DEFAULT;
|
assert(myRegion); // Should have been found in init
|
||||||
|
|
||||||
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
|
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
|
||||||
int channel_num = (channelSettings.channel_num ? channelSettings.channel_num - 1 : hash(channelSettings.name)) % NUM_CHANNELS;
|
int channel_num =
|
||||||
freq = CH0 + CH_SPACING * channel_num;
|
(channelSettings.channel_num ? channelSettings.channel_num - 1 : hash(channelSettings.name)) % myRegion->numChannels;
|
||||||
|
freq = myRegion->freq + myRegion->spacing * channel_num;
|
||||||
|
|
||||||
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelSettings.name, channelSettings.modem_config, channel_num,
|
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelSettings.name, channelSettings.modem_config, channel_num,
|
||||||
power);
|
power);
|
||||||
|
DEBUG_MSG("Radio myRegion->freq: %f\n", myRegion->freq);
|
||||||
|
DEBUG_MSG("Radio myRegion->spacing: %f\n", myRegion->spacing);
|
||||||
|
DEBUG_MSG("Radio myRegion->numChannels: %d\n", myRegion->numChannels);
|
||||||
|
DEBUG_MSG("Radio channel_num: %d\n", channel_num);
|
||||||
|
DEBUG_MSG("Radio frequency: %f\n", freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some regulatory regions limit xmit power.
|
||||||
|
* This function should be called by subclasses after setting their desired power. It might lower it
|
||||||
|
*/
|
||||||
|
void RadioInterface::limitPower()
|
||||||
|
{
|
||||||
|
uint8_t maxPower = 255; // No limit
|
||||||
|
|
||||||
|
if (myRegion->powerLimit)
|
||||||
|
maxPower = myRegion->powerLimit;
|
||||||
|
|
||||||
|
if (power > maxPower) {
|
||||||
|
DEBUG_MSG("Lowering transmit power because of regulatory limits\n");
|
||||||
|
power = maxPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_MSG("Set radio: final power level=%d\n", power);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode SimRadio::send(MeshPacket *p)
|
ErrorCode SimRadio::send(MeshPacket *p)
|
||||||
@@ -163,7 +211,7 @@ size_t RadioInterface::beginSending(MeshPacket *p)
|
|||||||
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
|
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
|
||||||
assert(p->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now
|
assert(p->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now
|
||||||
|
|
||||||
lastTxStart = timing::millis();
|
lastTxStart = millis();
|
||||||
|
|
||||||
PacketHeader *h = (PacketHeader *)radiobuf;
|
PacketHeader *h = (PacketHeader *)radiobuf;
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ typedef struct {
|
|||||||
*
|
*
|
||||||
* This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations)
|
* This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations)
|
||||||
*/
|
*/
|
||||||
class RadioInterface : protected concurrency::NotifiedWorkerThread
|
class RadioInterface
|
||||||
{
|
{
|
||||||
friend class MeshRadio; // for debugging we let that class touch pool
|
friend class MeshRadio; // for debugging we let that class touch pool
|
||||||
PointerQueue<MeshPacket> *rxDest = NULL;
|
PointerQueue<MeshPacket> *rxDest = NULL;
|
||||||
@@ -72,6 +72,8 @@ class RadioInterface : protected concurrency::NotifiedWorkerThread
|
|||||||
*/
|
*/
|
||||||
RadioInterface();
|
RadioInterface();
|
||||||
|
|
||||||
|
virtual ~RadioInterface() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set where to deliver received packets. This method should only be used by the Router class
|
* Set where to deliver received packets. This method should only be used by the Router class
|
||||||
*/
|
*/
|
||||||
@@ -117,7 +119,11 @@ class RadioInterface : protected concurrency::NotifiedWorkerThread
|
|||||||
*/
|
*/
|
||||||
size_t beginSending(MeshPacket *p);
|
size_t beginSending(MeshPacket *p);
|
||||||
|
|
||||||
virtual void loop() {} // Idle processing
|
/**
|
||||||
|
* Some regulatory regions limit xmit power.
|
||||||
|
* This function should be called by subclasses after setting their desired power. It might lower it
|
||||||
|
*/
|
||||||
|
void limitPower();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert our modemConfig enum into wf, sf, etc...
|
* Convert our modemConfig enum into wf, sf, etc...
|
||||||
|
|||||||
@@ -19,17 +19,11 @@ void LockingModule::SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint
|
|||||||
|
|
||||||
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
||||||
SPIClass &spi, PhysicalLayer *_iface)
|
SPIClass &spi, PhysicalLayer *_iface)
|
||||||
: concurrency::PeriodicTask(0), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
|
: NotifiedWorkerThread("RadioIf"), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
|
||||||
{
|
{
|
||||||
instance = this;
|
instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RadioLibInterface::init()
|
|
||||||
{
|
|
||||||
setup(); // init our timer
|
|
||||||
return RadioInterface::init();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
// ESP32 doesn't use that flag
|
// ESP32 doesn't use that flag
|
||||||
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
|
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
|
||||||
@@ -41,9 +35,8 @@ void INTERRUPT_ATTR RadioLibInterface::isrLevel0Common(PendingISR cause)
|
|||||||
{
|
{
|
||||||
instance->disableInterrupt();
|
instance->disableInterrupt();
|
||||||
|
|
||||||
instance->pending = cause;
|
|
||||||
BaseType_t xHigherPriorityTaskWoken;
|
BaseType_t xHigherPriorityTaskWoken;
|
||||||
instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, eSetValueWithOverwrite);
|
instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, true);
|
||||||
|
|
||||||
/* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE.
|
/* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE.
|
||||||
The macro used to do this is dependent on the port and may be called
|
The macro used to do this is dependent on the port and may be called
|
||||||
@@ -191,10 +184,8 @@ transmitters that we are potentially stomping on. Requires further thought.
|
|||||||
|
|
||||||
FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later.
|
FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later.
|
||||||
*/
|
*/
|
||||||
void RadioLibInterface::loop()
|
void RadioLibInterface::onNotify(uint32_t notification)
|
||||||
{
|
{
|
||||||
pending = ISR_NONE;
|
|
||||||
|
|
||||||
switch (notification) {
|
switch (notification) {
|
||||||
case ISR_TX:
|
case ISR_TX:
|
||||||
handleTransmitInterrupt();
|
handleTransmitInterrupt();
|
||||||
@@ -209,6 +200,8 @@ void RadioLibInterface::loop()
|
|||||||
startTransmitTimer();
|
startTransmitTimer();
|
||||||
break;
|
break;
|
||||||
case TRANSMIT_DELAY_COMPLETED:
|
case TRANSMIT_DELAY_COMPLETED:
|
||||||
|
// DEBUG_MSG("delay done\n");
|
||||||
|
|
||||||
// If we are not currently in receive mode, then restart the timer and try again later (this can happen if the main thread
|
// If we are not currently in receive mode, then restart the timer and try again later (this can happen if the main thread
|
||||||
// has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode?
|
// has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode?
|
||||||
if (!txQueue.isEmpty()) {
|
if (!txQueue.isEmpty()) {
|
||||||
@@ -229,25 +222,14 @@ void RadioLibInterface::loop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RadioLibInterface::doTask()
|
|
||||||
{
|
|
||||||
disable(); // Don't call this callback again
|
|
||||||
|
|
||||||
// We use without overwrite, so that if there is already an interrupt pending to be handled, that gets handle properly (the
|
|
||||||
// ISR handler will restart our timer)
|
|
||||||
|
|
||||||
notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadioLibInterface::startTransmitTimer(bool withDelay)
|
void RadioLibInterface::startTransmitTimer(bool withDelay)
|
||||||
{
|
{
|
||||||
// If we have work to do and the timer wasn't already scheduled, schedule it now
|
// If we have work to do and the timer wasn't already scheduled, schedule it now
|
||||||
if (getPeriod() == 0 && !txQueue.isEmpty()) {
|
if (!txQueue.isEmpty()) {
|
||||||
uint32_t delay =
|
uint32_t delay =
|
||||||
!withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values
|
!withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values
|
||||||
// DEBUG_MSG("xmit timer %d\n", delay);
|
// DEBUG_MSG("xmit timer %d\n", delay);
|
||||||
// DEBUG_MSG("delaying %u\n", delay);
|
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
|
||||||
setPeriod(delay);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../concurrency/PeriodicTask.h"
|
#include "../concurrency/OSThread.h"
|
||||||
#include "RadioInterface.h"
|
#include "RadioInterface.h"
|
||||||
|
|
||||||
#ifdef CubeCell_BoardPlus
|
#ifdef CubeCell_BoardPlus
|
||||||
@@ -59,13 +59,11 @@ class LockingModule : public Module
|
|||||||
virtual void SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes);
|
virtual void SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes);
|
||||||
};
|
};
|
||||||
|
|
||||||
class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTask
|
class RadioLibInterface : public RadioInterface, protected concurrency::NotifiedWorkerThread
|
||||||
{
|
{
|
||||||
/// Used as our notification from the ISR
|
/// Used as our notification from the ISR
|
||||||
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED };
|
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED };
|
||||||
|
|
||||||
volatile PendingISR pending = ISR_NONE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raw ISR handler that just calls our polymorphic method
|
* Raw ISR handler that just calls our polymorphic method
|
||||||
*/
|
*/
|
||||||
@@ -137,6 +135,11 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
*/
|
*/
|
||||||
virtual void startReceive() = 0;
|
virtual void startReceive() = 0;
|
||||||
|
|
||||||
|
/** are we actively receiving a packet (only called during receiving state)
|
||||||
|
* This method is only public to facilitate debugging. Do not call.
|
||||||
|
*/
|
||||||
|
virtual bool isActivelyReceiving() = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing
|
/** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing
|
||||||
* the transmit
|
* the transmit
|
||||||
@@ -150,7 +153,7 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
|
|
||||||
static void timerCallback(void *p1, uint32_t p2);
|
static void timerCallback(void *p1, uint32_t p2);
|
||||||
|
|
||||||
virtual void doTask();
|
virtual void onNotify(uint32_t notification);
|
||||||
|
|
||||||
/** start an immediate transmit
|
/** start an immediate transmit
|
||||||
* This method is virtual so subclasses can hook as needed, subclasses should not call directly
|
* This method is virtual so subclasses can hook as needed, subclasses should not call directly
|
||||||
@@ -158,10 +161,6 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
virtual void startSend(MeshPacket *txp);
|
virtual void startSend(MeshPacket *txp);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Initialise the Driver transport hardware and software.
|
|
||||||
/// Make sure the Driver is properly configured before calling init().
|
|
||||||
/// \return true if initialisation succeeded.
|
|
||||||
virtual bool init();
|
|
||||||
|
|
||||||
/** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */
|
/** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */
|
||||||
virtual void configHardwareForSend() {}
|
virtual void configHardwareForSend() {}
|
||||||
@@ -176,9 +175,6 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
|
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
|
||||||
virtual bool canSendImmediately();
|
virtual bool canSendImmediately();
|
||||||
|
|
||||||
/** are we actively receiving a packet (only called during receiving state) */
|
|
||||||
virtual bool isActivelyReceiving() = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raw ISR handler that just calls our polymorphic method
|
* Raw ISR handler that just calls our polymorphic method
|
||||||
*/
|
*/
|
||||||
@@ -193,7 +189,5 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
*/
|
*/
|
||||||
virtual void addReceiveMetadata(MeshPacket *mp) = 0;
|
virtual void addReceiveMetadata(MeshPacket *mp) = 0;
|
||||||
|
|
||||||
virtual void loop(); // Idle processing
|
|
||||||
|
|
||||||
virtual void setStandby() = 0;
|
virtual void setStandby() = 0;
|
||||||
};
|
};
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "timing.h"
|
|
||||||
|
|
||||||
// ReliableRouter::ReliableRouter() {}
|
// ReliableRouter::ReliableRouter() {}
|
||||||
|
|
||||||
@@ -161,9 +160,10 @@ PendingPacket *ReliableRouter::startRetransmission(MeshPacket *p)
|
|||||||
/**
|
/**
|
||||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||||
*/
|
*/
|
||||||
void ReliableRouter::doRetransmissions()
|
int32_t ReliableRouter::doRetransmissions()
|
||||||
{
|
{
|
||||||
uint32_t now = timing::millis();
|
uint32_t now = millis();
|
||||||
|
int32_t d = INT32_MAX;
|
||||||
|
|
||||||
// FIXME, we should use a better datastructure rather than walking through this map.
|
// FIXME, we should use a better datastructure rather than walking through this map.
|
||||||
// for(auto el: pending) {
|
// for(auto el: pending) {
|
||||||
@@ -193,5 +193,13 @@ void ReliableRouter::doRetransmissions()
|
|||||||
p.setNextTx();
|
p.setNextTx();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Not yet time
|
||||||
|
int32_t t = p.nextTxMsec - now;
|
||||||
|
|
||||||
|
d = min(t, d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "FloodingRouter.h"
|
#include "FloodingRouter.h"
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include "../timing.h"
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +47,7 @@ struct PendingPacket {
|
|||||||
PendingPacket() {}
|
PendingPacket() {}
|
||||||
PendingPacket(MeshPacket *p);
|
PendingPacket(MeshPacket *p);
|
||||||
|
|
||||||
void setNextTx() { nextTxMsec = timing::millis() + random(20 * 1000L, 22 * 1000L); }
|
void setNextTx() { nextTxMsec = millis() + random(20 * 1000L, 22 * 1000L); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class GlobalPacketIdHashFunction
|
class GlobalPacketIdHashFunction
|
||||||
@@ -81,10 +79,13 @@ class ReliableRouter : public FloodingRouter
|
|||||||
virtual ErrorCode send(MeshPacket *p);
|
virtual ErrorCode send(MeshPacket *p);
|
||||||
|
|
||||||
/** Do our retransmission handling */
|
/** Do our retransmission handling */
|
||||||
virtual void loop()
|
virtual int32_t runOnce()
|
||||||
{
|
{
|
||||||
doRetransmissions();
|
auto d = FloodingRouter::runOnce();
|
||||||
FloodingRouter::loop();
|
|
||||||
|
int32_t r = doRetransmissions();
|
||||||
|
|
||||||
|
return min(d, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -125,6 +126,8 @@ class ReliableRouter : public FloodingRouter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||||
|
*
|
||||||
|
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
|
||||||
*/
|
*/
|
||||||
void doRetransmissions();
|
int32_t doRetransmissions();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
#include "CryptoEngine.h"
|
#include "CryptoEngine.h"
|
||||||
#include "GPS.h"
|
#include "RTC.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include <NodeDB.h>
|
#include <NodeDB.h>
|
||||||
@@ -34,25 +34,29 @@ Allocator<MeshPacket> &packetPool = staticPool;
|
|||||||
*
|
*
|
||||||
* Currently we only allow one interface, that may change in the future
|
* Currently we only allow one interface, that may change in the future
|
||||||
*/
|
*/
|
||||||
Router::Router() : fromRadioQueue(MAX_RX_FROMRADIO)
|
Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRADIO)
|
||||||
{
|
{
|
||||||
// This is called pre main(), don't touch anything here, the following code is not safe
|
// This is called pre main(), don't touch anything here, the following code is not safe
|
||||||
|
|
||||||
/* DEBUG_MSG("Size of NodeInfo %d\n", sizeof(NodeInfo));
|
/* DEBUG_MSG("Size of NodeInfo %d\n", sizeof(NodeInfo));
|
||||||
DEBUG_MSG("Size of SubPacket %d\n", sizeof(SubPacket));
|
DEBUG_MSG("Size of SubPacket %d\n", sizeof(SubPacket));
|
||||||
DEBUG_MSG("Size of MeshPacket %d\n", sizeof(MeshPacket)); */
|
DEBUG_MSG("Size of MeshPacket %d\n", sizeof(MeshPacket)); */
|
||||||
|
|
||||||
|
fromRadioQueue.setReader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* do idle processing
|
* do idle processing
|
||||||
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
||||||
*/
|
*/
|
||||||
void Router::loop()
|
int32_t Router::runOnce()
|
||||||
{
|
{
|
||||||
MeshPacket *mp;
|
MeshPacket *mp;
|
||||||
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
|
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
|
||||||
perhapsHandleReceived(mp);
|
perhapsHandleReceived(mp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return INT32_MAX; // Wait a long time - until we get woken for the message queue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a unique packet id
|
/// Generate a unique packet id
|
||||||
@@ -89,7 +93,7 @@ MeshPacket *Router::allocForSending()
|
|||||||
p->to = NODENUM_BROADCAST;
|
p->to = NODENUM_BROADCAST;
|
||||||
p->hop_limit = HOP_RELIABLE;
|
p->hop_limit = HOP_RELIABLE;
|
||||||
p->id = generatePacketId();
|
p->id = generatePacketId();
|
||||||
p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp
|
p->rx_time = getValidTime(RTCQualityFromNet); // Just in case we process the packet locally - make sure it has a valid timestamp
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@@ -198,9 +202,8 @@ NodeNum Router::getNodeNum()
|
|||||||
*/
|
*/
|
||||||
void Router::handleReceived(MeshPacket *p)
|
void Router::handleReceived(MeshPacket *p)
|
||||||
{
|
{
|
||||||
// FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function
|
|
||||||
// Also, we should set the time from the ISR and it should have msec level resolution
|
// Also, we should set the time from the ISR and it should have msec level resolution
|
||||||
p->rx_time = getValidTime(); // store the arrival timestamp for the phone
|
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
|
||||||
|
|
||||||
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
||||||
if (perhapsDecode(p)) {
|
if (perhapsDecode(p)) {
|
||||||
|
|||||||
@@ -5,12 +5,13 @@
|
|||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "PointerQueue.h"
|
#include "PointerQueue.h"
|
||||||
#include "RadioInterface.h"
|
#include "RadioInterface.h"
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
#include "mesh.pb.h"
|
#include "mesh.pb.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mesh aware router that supports multiple interfaces.
|
* A mesh aware router that supports multiple interfaces.
|
||||||
*/
|
*/
|
||||||
class Router
|
class Router : protected concurrency::OSThread
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
RadioInterface *iface;
|
RadioInterface *iface;
|
||||||
@@ -44,7 +45,7 @@ class Router
|
|||||||
* do idle processing
|
* do idle processing
|
||||||
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
||||||
*/
|
*/
|
||||||
virtual void loop();
|
virtual int32_t runOnce();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Works like send, but if we are sending to the local node, we directly put the message in the receive queue
|
* Works like send, but if we are sending to the local node, we directly put the message in the receive queue
|
||||||
@@ -113,7 +114,7 @@ class Router
|
|||||||
void handleReceived(MeshPacket *p);
|
void handleReceived(MeshPacket *p);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Router &router;
|
extern Router *router;
|
||||||
|
|
||||||
/// Generate a unique packet id
|
/// Generate a unique packet id
|
||||||
// FIXME, move this someplace better
|
// FIXME, move this someplace better
|
||||||
|
|||||||
@@ -37,8 +37,15 @@ bool SX1262Interface::init()
|
|||||||
bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
|
bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
|
||||||
|
|
||||||
applyModemConfig();
|
applyModemConfig();
|
||||||
|
|
||||||
|
if (power == 0)
|
||||||
|
power = 22;
|
||||||
|
|
||||||
if (power > 22) // This chip has lower power limits than some
|
if (power > 22) // This chip has lower power limits than some
|
||||||
power = 22;
|
power = 22;
|
||||||
|
|
||||||
|
limitPower();
|
||||||
|
|
||||||
int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO);
|
int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO);
|
||||||
DEBUG_MSG("SX1262 init result %d\n", res);
|
DEBUG_MSG("SX1262 init result %d\n", res);
|
||||||
|
|
||||||
@@ -75,8 +82,8 @@ bool SX1262Interface::reconfigure()
|
|||||||
assert(err == ERR_NONE);
|
assert(err == ERR_NONE);
|
||||||
|
|
||||||
// Hmm - seems to lower SNR when the signal levels are high. Leaving off for now...
|
// Hmm - seems to lower SNR when the signal levels are high. Leaving off for now...
|
||||||
// err = lora.setRxGain(true);
|
err = lora.setRxGain(true);
|
||||||
// assert(err == ERR_NONE);
|
assert(err == ERR_NONE);
|
||||||
|
|
||||||
err = lora.setSyncWord(syncWord);
|
err = lora.setSyncWord(syncWord);
|
||||||
assert(err == ERR_NONE);
|
assert(err == ERR_NONE);
|
||||||
@@ -172,9 +179,19 @@ void SX1262Interface::startReceive()
|
|||||||
/** Could we send right now (i.e. either not actively receving or transmitting)? */
|
/** Could we send right now (i.e. either not actively receving or transmitting)? */
|
||||||
bool SX1262Interface::isActivelyReceiving()
|
bool SX1262Interface::isActivelyReceiving()
|
||||||
{
|
{
|
||||||
// return false; // FIXME
|
// The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
|
||||||
// FIXME this is not correct? - often always true - need to add an extra conditional
|
// received and handled the interrupt for reading the packet/handling errors.
|
||||||
return lora.getPacketLength() > 0;
|
// FIXME: it would be better to check for preamble, but we currently have our ISR not set to fire for packets that
|
||||||
|
// never even get a valid header, so we don't want preamble to get set and stay set due to noise on the network.
|
||||||
|
|
||||||
|
uint16_t irq = lora.getIrqStatus();
|
||||||
|
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
|
||||||
|
|
||||||
|
// this is not correct - often always true - need to add an extra conditional
|
||||||
|
// size_t bytesPending = lora.getPacketLength();
|
||||||
|
|
||||||
|
// if (hasPreamble) DEBUG_MSG("rx hasPreamble\n");
|
||||||
|
return hasPreamble;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SX1262Interface::sleep()
|
bool SX1262Interface::sleep()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
#include "freertosinc.h"
|
#include "freertosinc.h"
|
||||||
|
|
||||||
#ifdef HAS_FREE_RTOS
|
#ifdef HAS_FREE_RTOS
|
||||||
@@ -15,6 +16,7 @@ template <class T> class TypedQueue
|
|||||||
{
|
{
|
||||||
static_assert(std::is_pod<T>::value, "T must be pod");
|
static_assert(std::is_pod<T>::value, "T must be pod");
|
||||||
QueueHandle_t h;
|
QueueHandle_t h;
|
||||||
|
concurrency::OSThread *reader = NULL;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TypedQueue(int maxElements)
|
TypedQueue(int maxElements)
|
||||||
@@ -29,13 +31,35 @@ template <class T> class TypedQueue
|
|||||||
|
|
||||||
bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; }
|
bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; }
|
||||||
|
|
||||||
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { return xQueueSendToBack(h, &x, maxWait) == pdTRUE; }
|
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
||||||
|
{
|
||||||
|
if (reader) {
|
||||||
|
reader->setInterval(0);
|
||||||
|
concurrency::mainDelay.interrupt();
|
||||||
|
}
|
||||||
|
return xQueueSendToBack(h, &x, maxWait) == pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
bool enqueueFromISR(T x, BaseType_t *higherPriWoken) { return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE; }
|
bool enqueueFromISR(T x, BaseType_t *higherPriWoken)
|
||||||
|
{
|
||||||
|
if (reader) {
|
||||||
|
reader->setInterval(0);
|
||||||
|
concurrency::mainDelay.interruptFromISR(higherPriWoken);
|
||||||
|
}
|
||||||
|
return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY) { return xQueueReceive(h, p, maxWait) == pdTRUE; }
|
bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY) { return xQueueReceive(h, p, maxWait) == pdTRUE; }
|
||||||
|
|
||||||
bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a thread that is reading from this queue
|
||||||
|
* If a message is pushed to this queue that thread will be scheduled to run ASAP.
|
||||||
|
*
|
||||||
|
* Note: thread will not be automatically enabled, just have its interval set to 0
|
||||||
|
*/
|
||||||
|
void setReader(concurrency::OSThread *t) { reader = t; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#else
|
#else
|
||||||
@@ -49,16 +73,22 @@ template <class T> class TypedQueue
|
|||||||
template <class T> class TypedQueue
|
template <class T> class TypedQueue
|
||||||
{
|
{
|
||||||
std::queue<T> q;
|
std::queue<T> q;
|
||||||
|
concurrency::OSThread *reader = NULL;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TypedQueue(int maxElements) {}
|
TypedQueue(int maxElements) {}
|
||||||
|
|
||||||
// int numFree() { return uxQueueSpacesAvailable(h); }
|
int numFree() { return 1; } // Always claim 1 free, because we can grow to any size
|
||||||
|
|
||||||
bool isEmpty() { return q.empty(); }
|
bool isEmpty() { return q.empty(); }
|
||||||
|
|
||||||
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
||||||
{
|
{
|
||||||
|
if (reader) {
|
||||||
|
reader->setInterval(0);
|
||||||
|
concurrency::mainDelay.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
q.push(x);
|
q.push(x);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -77,5 +107,7 @@ template <class T> class TypedQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
// bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
// bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
||||||
|
|
||||||
|
void setReader(concurrency::OSThread *t) { reader = t; }
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
|
|
||||||
#ifdef NO_ESP32
|
#ifdef ARDUINO_ARCH_NRF52
|
||||||
#include "Adafruit_LittleFS.h"
|
#include "Adafruit_LittleFS.h"
|
||||||
using namespace Adafruit_LittleFS_Namespace; // To get File type
|
using namespace Adafruit_LittleFS_Namespace; // To get File type
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
|
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
|
||||||
/// returns the encoded packet size
|
/// returns the encoded packet size
|
||||||
@@ -49,7 +49,7 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count)
|
|||||||
return count == 0;
|
return count == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = (file->read(buf, count) == (int) count);
|
status = (file->read(buf, count) == (int)count);
|
||||||
|
|
||||||
if (file->available() == 0)
|
if (file->available() == 0)
|
||||||
stream->bytes_left = 0;
|
stream->bytes_left = 0;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user