mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-08 05:00:05 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62a8c968e8 | ||
|
|
b9a1cae72d | ||
|
|
6b442784f3 | ||
|
|
cfcb62bd18 | ||
|
|
f698883c02 | ||
|
|
4a5cef886e | ||
|
|
f6ec129288 | ||
|
|
17763034a0 | ||
|
|
4ad562b9f4 | ||
|
|
6b838002d4 | ||
|
|
5e0d53a1e3 | ||
|
|
7597d5b3fd | ||
|
|
1a8891c33d | ||
|
|
f0eeaf01d4 | ||
|
|
d4e95e95a6 | ||
|
|
0767c8be03 | ||
|
|
18bbf3523e | ||
|
|
b081a6da56 | ||
|
|
a102e49fdb | ||
|
|
c078c08c3e | ||
|
|
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 | ||
|
|
e1f0e11cb8 | ||
|
|
c73ee98739 | ||
|
|
c2a1141dfa | ||
|
|
5b4472ab56 | ||
|
|
3262f732d8 | ||
|
|
cff21ca130 | ||
|
|
81ce04d3da | ||
|
|
f4d2b10840 | ||
|
|
2370cb8aac | ||
|
|
59ec87f5b0 | ||
|
|
0d9481b6ea | ||
|
|
8f0105ccd9 | ||
|
|
05ca3c3d56 | ||
|
|
ba549d8fcd | ||
|
|
b9df2c00fa | ||
|
|
d9dcb33576 | ||
|
|
f698231be7 | ||
|
|
8414f4a6a3 | ||
|
|
8505020be5 | ||
|
|
f3b93d55fb | ||
|
|
9e0731a956 | ||
|
|
2b373048c6 | ||
|
|
22f23bb07d | ||
|
|
b32e3f1269 | ||
|
|
68ddb712f5 | ||
|
|
2fb5cd8c1c | ||
|
|
79aea8231f | ||
|
|
b0837c10c6 | ||
|
|
cd811951b1 | ||
|
|
df2976dad0 | ||
|
|
4ccbe6ff71 | ||
|
|
038ddb887f | ||
|
|
9134faaed1 | ||
|
|
649a120fe0 | ||
|
|
4db0c4a563 | ||
|
|
5f2f3c94b9 | ||
|
|
3b2f5fa5e3 | ||
|
|
97adb598b6 | ||
|
|
152ebf0dff | ||
|
|
5b1511c930 | ||
|
|
7ef2cc8623 | ||
|
|
b41a32c6b6 | ||
|
|
1ebd7b0c3e | ||
|
|
5457541244 | ||
|
|
ca77d48b20 | ||
|
|
965c2bda8d | ||
|
|
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 | ||
|
|
b4b1b24c84 | ||
|
|
ef146fc0b5 | ||
|
|
f6861a8fe2 | ||
|
|
736642455f | ||
|
|
3c1c11e439 | ||
|
|
b072eec4ac | ||
|
|
ff9b49ddaa | ||
|
|
b8863c8a07 | ||
|
|
4d7cd0a09d | ||
|
|
fed8e80ae4 | ||
|
|
b38bcffafb | ||
|
|
530432411e | ||
|
|
71cdbb1a73 | ||
|
|
200aa27cc0 | ||
|
|
f7752e4f9d | ||
|
|
7f0e8a8d6b | ||
|
|
f30c84012f | ||
|
|
1f6877606f | ||
|
|
1907873831 | ||
|
|
bacc6caf04 | ||
|
|
56d4250197 | ||
|
|
d66cede7fc | ||
|
|
f7ffd196e3 | ||
|
|
3a638090a2 | ||
|
|
4342ae74fb | ||
|
|
5150d15997 | ||
|
|
7d6dbcfa3f | ||
|
|
5a7962896d | ||
|
|
cfb9a600e4 | ||
|
|
7f3217d69e | ||
|
|
d94be0f534 | ||
|
|
17a3e6e975 | ||
|
|
423cbc2c6d | ||
|
|
124a82888d | ||
|
|
fec7a6bf17 | ||
|
|
bc50b39a3b | ||
|
|
158e3edbe7 | ||
|
|
116fe6d109 | ||
|
|
6a4ef7e1d1 | ||
|
|
a0fd83428f | ||
|
|
e5d4fbb164 | ||
|
|
26bafb4082 | ||
|
|
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 | ||
|
|
6e3b22c624 | ||
|
|
82fbedbf41 | ||
|
|
0929b86d62 | ||
|
|
65fc1cf4a6 | ||
|
|
6f753799fd | ||
|
|
4d72afebe6 | ||
|
|
b1643e6036 | ||
|
|
362d8cb831 | ||
|
|
b203c95dd1 | ||
|
|
8d4672964c | ||
|
|
6e5e5822aa | ||
|
|
9624cc3798 | ||
|
|
48dd6d388d | ||
|
|
64710a6a04 | ||
|
|
c83ff03d66 | ||
|
|
73b47a78aa | ||
|
|
493b25f23e | ||
|
|
c656a95a84 | ||
|
|
27ad8472c1 | ||
|
|
3fcd4a61aa | ||
|
|
066d9d48a4 | ||
|
|
b2a6a4000b | ||
|
|
f5c939fb10 | ||
|
|
e508306395 | ||
|
|
d859700497 | ||
|
|
f129b458ad | ||
|
|
0b1a78c028 | ||
|
|
b8e1b28958 |
Vendored
+3
@@ -53,9 +53,12 @@
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Blox",
|
||||
"EINK",
|
||||
"HFSR",
|
||||
"Meshtastic",
|
||||
"NEMAGPS",
|
||||
"NMEAGPS",
|
||||
"RDEF",
|
||||
"Ublox",
|
||||
"bkpt",
|
||||
"cfsr",
|
||||
|
||||
@@ -25,7 +25,7 @@ We currently support three models of radios.
|
||||
- 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-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"
|
||||
- [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"
|
||||
@@ -103,10 +103,10 @@ Hard resetting via RTS pin...
|
||||
```
|
||||
|
||||
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`.
|
||||
- Example: `./device-install.sh firmware-HELTEC-US-0.0.3.bin`.
|
||||
7. To update run `device-update.sh firmware-_board_-_country_.bin`
|
||||
- Example: `./device-update.sh firmware-HELTEC-US-0.0.3.bin`.
|
||||
6. Install the correct firmware for your board with `device-install.sh -f firmware-_board_-_country_.bin`.
|
||||
- Example: `./device-install.sh -f firmware-HELTEC-US-0.0.3.bin`.
|
||||
7. To update run `device-update.sh -f firmware-_board_-_country_.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.
|
||||
|
||||
|
||||
+57
-28
@@ -4,16 +4,15 @@ set -e
|
||||
|
||||
source bin/version.sh
|
||||
|
||||
COUNTRIES="US EU433 EU865 CN JP ANZ"
|
||||
COUNTRIES="US EU433 EU865 CN JP ANZ KR"
|
||||
#COUNTRIES=US
|
||||
#COUNTRIES=CN
|
||||
|
||||
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
|
||||
BOARDS_NRF52="lora-relay-v1"
|
||||
BOARDS="$BOARDS_ESP32 $BOARDS_NRF52"
|
||||
#BOARDS=tbeam
|
||||
|
||||
OUTDIR=release/latest
|
||||
|
||||
@@ -22,22 +21,61 @@ ARCHIVEDIR=release/archive
|
||||
|
||||
rm -f $OUTDIR/firmware*
|
||||
|
||||
mkdir -p $OUTDIR/bins $OUTDIR/elfs
|
||||
rm -f $OUTDIR/bins/*
|
||||
mkdir -p $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
|
||||
function do_build {
|
||||
echo "Building for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
||||
function do_build() {
|
||||
BOARD=$1
|
||||
COUNTRY=$2
|
||||
isNrf=$3
|
||||
|
||||
echo "Building $COUNTRY for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
||||
rm -f .pio/build/$BOARD/firmware.*
|
||||
|
||||
# The shell vars the build tool expects to find
|
||||
export HW_VERSION="1.0-$COUNTRY"
|
||||
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
|
||||
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
|
||||
@@ -46,26 +84,16 @@ git submodule update
|
||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||
platformio lib update
|
||||
|
||||
for COUNTRY in $COUNTRIES; do
|
||||
for BOARD in $BOARDS; do
|
||||
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
|
||||
do_boards "$BOARDS_ESP32" "false"
|
||||
do_boards "$BOARDS_NRF52" "true"
|
||||
|
||||
# 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
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
@@ -79,6 +107,7 @@ Generated by bin/buildall.sh -->
|
||||
</resources>
|
||||
XML
|
||||
|
||||
echo Generating $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
|
||||
|
||||
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
# You probably don't want to use this script, it programs a custom bootloader build onto a nrf52 board
|
||||
|
||||
set -e
|
||||
|
||||
# dependencies
|
||||
# apt install srecord
|
||||
|
||||
BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader
|
||||
BOARD=othernet_ppr1
|
||||
BOOTVER=0.3.2
|
||||
BOOTNUM=128
|
||||
BOOTSHA=gc01b9ea
|
||||
SDCODE=s113
|
||||
SDVER=7.2.0
|
||||
PROJ=ppr1
|
||||
|
||||
# FIXME for nRF52840 use 0xff000, for nRF52833 use 0x7f000
|
||||
BOOTSET=0x7f000
|
||||
|
||||
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 $BOOTSET -output /tmp/bootconf.hex -intel
|
||||
|
||||
echo Generating merged hex file from .pio/build/$PROJ/firmware.hex
|
||||
mergehex -o ${BOARD}_full.hex -m $BOOTDIR/_build/build-$BOARD/${BOARD}_bootloader-$BOOTVER-$BOOTNUM-$BOOTSHA-dirty_${SDCODE}_$SDVER.hex .pio/build/$PROJ/firmware.hex /tmp/bootconf.hex
|
||||
|
||||
echo Telling bootloader app region is valid and telling CPU to run
|
||||
nrfjprog --program ${BOARD}_full.hex -f nrf52 --reset
|
||||
|
||||
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
||||
Executable
+22
@@ -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
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52832_XXAA
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52833_XXAA
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52832_XXAA
|
||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA
|
||||
|
||||
Executable
+6
@@ -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,4 +1,4 @@
|
||||
|
||||
echo "Converting to uf2 for NRF52 Adafruit bootloader"
|
||||
bin/uf2conv.py .pio/build/lora-relay-v1/firmware.hex -f 0xADA52840
|
||||
bin/uf2conv.py .pio/build/lora-relay-v2/firmware.hex -f 0xADA52840
|
||||
# cp flash.uf2 /media/kevinh/FTH*BOOT/
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
export VERSION=1.1.0
|
||||
export VERSION=1.1.7
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
echo using amap tool to display memory map
|
||||
amap .pio/build/output.map
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_TTGO_EINK -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"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V2 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4406"]],
|
||||
"usb_product": "LORA_RELAY",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "lora_relay_v2",
|
||||
"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": "Meshtastic Lora Relay V1 (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay",
|
||||
"vendor": "BigCorvus"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52833_s113_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52833_PPR -DNRF52833_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4406"]],
|
||||
"usb_product": "PPR",
|
||||
"mcu": "nrf52833",
|
||||
"variant": "ppr",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS113",
|
||||
"sd_name": "s113",
|
||||
"sd_version": "7.2.0",
|
||||
"sd_fwid": "0x00b6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52833_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52833.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Meshtastic PPR1 (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://meshtastic.org/",
|
||||
"vendor": "Othernet"
|
||||
}
|
||||
+277
@@ -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;
|
||||
}
|
||||
Binary file not shown.
+1
-1
@@ -1,7 +1,7 @@
|
||||
theme: jekyll-theme-cayman
|
||||
|
||||
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
|
||||
|
||||
include: [".well-known"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 272 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 417 KiB |
Binary file not shown.
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.
|
||||
|
||||
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:
|
||||
|
||||
- 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 User from "user" - to get the username for this node
|
||||
- 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 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,27 @@
|
||||
|
||||
## RAK815
|
||||
|
||||
TODO:
|
||||
### PPR1 TODO
|
||||
|
||||
* V_BK for the GPS should probably be supplied from something always on
|
||||
|
||||
* use S113 soft device 7.2.0
|
||||
* properly test charge controller config and read battery/charge status
|
||||
* fix bluetooth
|
||||
* fix LCD max contrast (currently too high, needs to be about 40?)
|
||||
* save brightness settings in flash
|
||||
* make ST7567Wire driver less ugly, move OLED stuff into a common class treee
|
||||
* add LCD power save mode for lcd per page 31 of datasheet
|
||||
* add LCD power off sequence per datasheet to lcd driver
|
||||
* leave LCD screen on most of the time (because it needs little power)
|
||||
|
||||
### general nrf52 TODO:
|
||||
|
||||
- turn off transitions on eink screens
|
||||
- change update interval on eink from 1/sec frames to one frame every 5 mins
|
||||
- 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
|
||||
- get nrf52832 working again (currently OOM)
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
These are **preliminary** notes on support for Meshtastic in the Pinetab.
|
||||
|
||||
A RF95 is connected via a CS341 USB-SPI chip.
|
||||
A RF95 is connected via a CH341 USB-SPI chip.
|
||||
|
||||
Pin assignments:
|
||||
CS0 from RF95 goes to CS0 on CS341
|
||||
DIO0 from RF95 goes to INT on CS341
|
||||
RST from RF95 goes to RST on CS341
|
||||
CS0 from RF95 goes to CS0 on CH341
|
||||
DIO0 from RF95 goes to INT on CH341
|
||||
RST from RF95 goes to RST on CH341
|
||||
|
||||
This linux driver claims to provide USB-SPI support: https://github.com/gschorcht/spi-ch341-usb
|
||||
Notes here on using that driver: https://www.linuxquestions.org/questions/linux-hardware-18/ch341-usb-to-spi-adaptor-driver-doesn%27t-work-4175622736/
|
||||
|
||||
+10
-4
@@ -32,14 +32,18 @@ From lower to higher power consumption.
|
||||
onEntry: setBluetoothOn(true)
|
||||
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
|
||||
onEntry: setBluetooth off, screen on
|
||||
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
|
||||
|
||||
### 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 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 any state: if we have AC power, go to POWER
|
||||
|
||||
### 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 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
|
||||
|
||||
@@ -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.
|
||||
* [Power Management](power.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.
|
||||
|
||||
@@ -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]
|
||||
+63
-8
@@ -10,6 +10,7 @@
|
||||
|
||||
[platformio]
|
||||
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 is not currently used
|
||||
@@ -29,11 +30,15 @@ build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/n
|
||||
-DHW_VERSION_${sysenv.COUNTRY}
|
||||
-DAPP_VERSION=${sysenv.APP_VERSION}
|
||||
-DHW_VERSION=${sysenv.HW_VERSION}
|
||||
-DUSE_THREAD_NAMES
|
||||
|
||||
; leave this commented out to avoid breaking Windows
|
||||
;upload_port = /dev/ttyUSB0
|
||||
;monitor_port = /dev/ttyUSB0
|
||||
|
||||
;upload_port = /dev/cu.SLAB_USBtoUART
|
||||
;monitor_port = /dev/cu.SLAB_USBtoUART
|
||||
|
||||
; the default is esptool
|
||||
; upload_protocol = esp-prog
|
||||
|
||||
@@ -54,15 +59,16 @@ debug_tool = jlink
|
||||
|
||||
lib_deps =
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||
1260 ; OneButton library for non-blocking button debounce
|
||||
https://github.com/geeksville/OneButton.git ; OneButton library for non-blocking button debounce
|
||||
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
||||
https://github.com/meshtastic/arduino-fsm.git
|
||||
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
|
||||
https://github.com/meshtastic/RadioLib.git#ac7feac00f5e0bd95a3ac5d5852b4cc7344cf95c
|
||||
https://github.com/meshtastic/arduino-fsm.git#2f106146071fc7bc620e1e8d4b88dc4e0266ce39
|
||||
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
|
||||
https://github.com/meshtastic/RadioLib.git#8657380241bce681c33aab46598bbf13b11f876c
|
||||
https://github.com/meshtastic/TinyGPSPlus.git
|
||||
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]
|
||||
@@ -88,6 +94,9 @@ build_flags =
|
||||
${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
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
https://github.com/meshtastic/esp32_https_server.git
|
||||
# Hmm - this doesn't work yet
|
||||
# board_build.ldscript = linker/esp32.extram.bss.ld
|
||||
lib_ignore = segger_rtt
|
||||
@@ -110,7 +119,7 @@ board_build.partitions = partition-table.csv
|
||||
extends = esp32_base
|
||||
board = ttgo-t-beam
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
${esp32_base.lib_deps}
|
||||
build_flags =
|
||||
${esp32_base.build_flags} -D TBEAM_V10
|
||||
|
||||
@@ -172,7 +181,7 @@ build_flags =
|
||||
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
|
||||
;-DCFG_DEBUG=3
|
||||
src_filter =
|
||||
${arduino_base.src_filter} -<esp32/> -<nimble/>
|
||||
${arduino_base.src_filter} -<esp32/> -<nimble/> -<meshwifi/>
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
monitor_port = /dev/ttyACM1
|
||||
@@ -227,6 +236,29 @@ lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
UC1701
|
||||
|
||||
; The PPR board
|
||||
[env:ppr1]
|
||||
extends = nrf52_base
|
||||
board = ppr1
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/ppr1
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/ppr1>
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
|
||||
; 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
|
||||
[env:lora-relay-v1]
|
||||
extends = nrf52_base
|
||||
@@ -248,12 +280,35 @@ lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
||||
TFT_eSPI
|
||||
# Adafruit ST7735 and ST7789 Library
|
||||
|
||||
; The https://github.com/BigCorvus/LoRa-BLE-Relay-v2 board by @BigCorvus
|
||||
[env:lora-relay-v2]
|
||||
extends = nrf52_base
|
||||
board = lora-relay-v2
|
||||
# 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/lora_relay_v2
|
||||
-DUSER_SETUP_LOADED
|
||||
-DTFT_WIDTH=80
|
||||
-DTFT_HEIGHT=160
|
||||
-DST7735_GREENTAB160x80
|
||||
-DST7735_DRIVER
|
||||
-DTFT_CS=ST7735_CS
|
||||
-DTFT_DC=ST7735_RS
|
||||
-DTFT_RST=ST7735_RESET
|
||||
-DSPI_FREQUENCY=27000000
|
||||
-DTFT_WR=ST7735_SDA
|
||||
-DTFT_SCLK=ST7735_SCK
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/lora_relay_v2>
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
||||
TFT_eSPI
|
||||
|
||||
; 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/>
|
||||
src_filter = ${env.src_filter} -<esp32/> -<nimble/> -<nrf52/> -<meshwifi/>
|
||||
build_flags = ${arduino_base.build_flags} -O0
|
||||
framework = arduino
|
||||
board = linux_x86_64
|
||||
|
||||
+1
-1
Submodule proto updated: ce422b7c44...a0b8d88896
+82
-110
@@ -1,126 +1,98 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "Status.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace meshtastic {
|
||||
namespace meshtastic
|
||||
{
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
class GPSStatus : public Status
|
||||
/// Describes the state of the GPS system.
|
||||
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:
|
||||
CallbackObserver<GPSStatus, const GPSStatus *> statusObserver = CallbackObserver<GPSStatus, const GPSStatus *>(this, &GPSStatus::updateStatus);
|
||||
void observe(Observable<const GPSStatus *> *source) { statusObserver.observe(source); }
|
||||
|
||||
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;
|
||||
bool getHasLock() const { return hasLock; }
|
||||
|
||||
public:
|
||||
bool getIsConnected() const { return isConnected; }
|
||||
|
||||
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()
|
||||
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;
|
||||
{
|
||||
this->hasLock = hasLock;
|
||||
this->isConnected = isConnected;
|
||||
this->latitude = latitude;
|
||||
this->longitude = longitude;
|
||||
this->altitude = altitude;
|
||||
this->dop = dop;
|
||||
this->heading = heading;
|
||||
this->numSatellites = numSatellites;
|
||||
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;
|
||||
}
|
||||
GPSStatus(const GPSStatus &);
|
||||
GPSStatus &operator=(const GPSStatus &);
|
||||
|
||||
void observe(Observable<const GPSStatus *> *source)
|
||||
{
|
||||
statusObserver.observe(source);
|
||||
if (isDirty) {
|
||||
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,
|
||||
altitude, dop * 1e-2, heading * 1e-5, numSatellites);
|
||||
else
|
||||
DEBUG_MSG("No GPS lock\n");
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
bool getHasLock() const
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace meshtastic
|
||||
|
||||
extern meshtastic::GPSStatus *gpsStatus;
|
||||
+96
-53
@@ -18,6 +18,21 @@ Power *power;
|
||||
|
||||
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
|
||||
*/
|
||||
@@ -37,10 +52,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
float v = getBattVoltage() / 1000;
|
||||
|
||||
if (v < 2.1)
|
||||
if (v < noBatVolt)
|
||||
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()
|
||||
{
|
||||
// Tested ttgo eink nrf52 board and the reported value is perfect
|
||||
// DEBUG_MSG("raw val %u", raw);
|
||||
return
|
||||
#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
|
||||
NAN;
|
||||
#endif
|
||||
@@ -59,14 +79,40 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
/**
|
||||
* 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;
|
||||
|
||||
Power::Power() : OSThread("Power") {}
|
||||
|
||||
bool Power::analogInit()
|
||||
{
|
||||
#ifdef BATTERY_PIN
|
||||
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);
|
||||
#endif
|
||||
#ifdef NRF52_SERIES
|
||||
analogReference(AR_INTERNAL); // 3.6V
|
||||
#endif
|
||||
|
||||
// adcStart(BATTERY_PIN);
|
||||
analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
|
||||
batteryLevel = &analogLevel;
|
||||
@@ -83,10 +129,7 @@ bool Power::setup()
|
||||
if (!found) {
|
||||
found = analogInit();
|
||||
}
|
||||
if (found) {
|
||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
||||
setPeriod(1);
|
||||
}
|
||||
enabled = found;
|
||||
|
||||
return found;
|
||||
}
|
||||
@@ -119,6 +162,8 @@ void Power::readPowerStatus()
|
||||
const PowerStatus powerStatus =
|
||||
PowerStatus(hasBattery ? OptTrue : OptFalse, batteryLevel->isVBUSPlug() ? OptTrue : OptFalse,
|
||||
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);
|
||||
|
||||
// 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();
|
||||
|
||||
#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
|
||||
if (statusHandler && statusHandler->isInitialized())
|
||||
setPeriod(1000 * 20);
|
||||
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +247,7 @@ bool Power::axp192Init()
|
||||
DEBUG_MSG("----------------------------------------\n");
|
||||
|
||||
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_EXTEN, AXP202_ON);
|
||||
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
||||
@@ -200,9 +279,11 @@ bool Power::axp192Init()
|
||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||
|
||||
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
||||
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
||||
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
|
||||
1);
|
||||
// we do not look for AXP202_CHARGING_FINISHED_IRQ & AXP202_CHARGING_IRQ because it occurs repeatedly while there is
|
||||
// no battery also it could cause inadvertent waking from light sleep just because the battery filled
|
||||
// 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();
|
||||
#endif
|
||||
@@ -219,41 +300,3 @@ bool Power::axp192Init()
|
||||
return false;
|
||||
#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
|
||||
}
|
||||
|
||||
+93
-49
@@ -4,24 +4,15 @@
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
|
||||
static void sdsEnter()
|
||||
{
|
||||
/*
|
||||
|
||||
// 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 (millis() - lastPressMs > radioConfig.preferences.mesh_sds_timeout_secs)
|
||||
{
|
||||
doDeepSleep(radioConfig.preferences.sds_secs);
|
||||
}
|
||||
*/
|
||||
|
||||
doDeepSleep(radioConfig.preferences.sds_secs * 1000LL);
|
||||
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
||||
doDeepSleep(getPref_sds_secs() * 1000LL);
|
||||
}
|
||||
|
||||
#include "error.h"
|
||||
@@ -30,8 +21,8 @@ static uint32_t secsSlept;
|
||||
|
||||
static void lsEnter()
|
||||
{
|
||||
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
||||
screen.setOn(false);
|
||||
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", getPref_ls_secs());
|
||||
screen->setOn(false);
|
||||
secsSlept = 0; // How long have we been sleeping this time
|
||||
|
||||
DEBUG_MSG("lsEnter end\n");
|
||||
@@ -39,13 +30,13 @@ static void lsEnter()
|
||||
|
||||
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
|
||||
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
||||
|
||||
// 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
|
||||
uint32_t sleepTime = 30;
|
||||
|
||||
@@ -54,7 +45,8 @@ static void lsIdle()
|
||||
setLed(false); // Never leave led on while in light sleep
|
||||
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
|
||||
|
||||
setLed(true); // briefly turn on led
|
||||
@@ -62,12 +54,15 @@ static void lsIdle()
|
||||
|
||||
secsSlept += sleepTime;
|
||||
// DEBUG_MSG("sleeping, flash led!\n");
|
||||
}
|
||||
if (wakeCause == ESP_SLEEP_WAKEUP_UART) {
|
||||
break;
|
||||
|
||||
case ESP_SLEEP_WAKEUP_UART:
|
||||
// Not currently used (because uart triggers in hw have problems)
|
||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||
} else {
|
||||
// We woke for some other reason (button press, uart, device interrupt)
|
||||
break;
|
||||
|
||||
default:
|
||||
// We woke for some other reason (button press, device interrupt)
|
||||
// uint64_t status = esp_sleep_get_ext1_wakeup_status();
|
||||
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
||||
|
||||
@@ -81,8 +76,10 @@ static void lsIdle()
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so
|
||||
@@ -100,12 +97,12 @@ static void lsIdle()
|
||||
static void lsExit()
|
||||
{
|
||||
// setGPSPower(true); // restore GPS power
|
||||
gps->startLock();
|
||||
gps->forceWake(true);
|
||||
}
|
||||
|
||||
static void nbEnter()
|
||||
{
|
||||
screen.setOn(false);
|
||||
screen->setOn(false);
|
||||
setBluetoothEnable(false);
|
||||
|
||||
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
|
||||
@@ -114,18 +111,33 @@ static void nbEnter()
|
||||
static void darkEnter()
|
||||
{
|
||||
setBluetoothEnable(true);
|
||||
screen.setOn(false);
|
||||
screen->setOn(false);
|
||||
}
|
||||
|
||||
static void serialEnter()
|
||||
{
|
||||
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()
|
||||
{
|
||||
screen.setOn(true);
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
|
||||
static uint32_t lastPingMs;
|
||||
@@ -137,13 +149,12 @@ static void onEnter()
|
||||
service.sendNetworkPing(displayedNodeNum, true); // Refresh the currently displayed node
|
||||
lastPingMs = now;
|
||||
}
|
||||
}
|
||||
|
||||
static void wakeForPing() {}
|
||||
}
|
||||
|
||||
static void screenPress()
|
||||
{
|
||||
screen.onPress();
|
||||
screen->onPress();
|
||||
}
|
||||
|
||||
static void bootEnter() {}
|
||||
@@ -155,16 +166,25 @@ State stateDARK(darkEnter, NULL, NULL, "DARK");
|
||||
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
|
||||
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
|
||||
State stateON(onEnter, NULL, NULL, "ON");
|
||||
State statePOWER(powerEnter, NULL, powerExit, "POWER");
|
||||
Fsm powerFSM(&stateBOOT);
|
||||
|
||||
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
|
||||
// handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
||||
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB or dark and
|
||||
// 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");
|
||||
|
||||
@@ -172,7 +192,10 @@ void PowerFSM_setup()
|
||||
powerFSM.add_transition(&stateLS, &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(&statePOWER, &statePOWER, EVENT_PRESS, screenPress, "Press");
|
||||
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
|
||||
powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
|
||||
@@ -185,42 +208,63 @@ void PowerFSM_setup()
|
||||
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(&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");
|
||||
// if we are a router we don't turn the screen on for these things
|
||||
if (!isRouter) {
|
||||
// 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");
|
||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
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
|
||||
// Show the received text message
|
||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||
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(&stateNB, &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");
|
||||
|
||||
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(&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_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
|
||||
// We never enter light-sleep state 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");
|
||||
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
|
||||
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL,
|
||||
"Bluetooth timeout");
|
||||
lowPowerState = &stateDARK;
|
||||
|
||||
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
|
||||
|
||||
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL,
|
||||
"mesh timeout");
|
||||
auto meshSds = getPref_mesh_sds_timeout_secs();
|
||||
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
|
||||
// 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");
|
||||
|
||||
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
||||
|
||||
+4
-1
@@ -13,10 +13,13 @@
|
||||
#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_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_DISCONNECTED 12
|
||||
#define EVENT_POWER_CONNECTED 13
|
||||
#define EVENT_POWER_DISCONNECTED 14
|
||||
|
||||
extern Fsm powerFSM;
|
||||
extern State statePOWER, stateSERIAL;
|
||||
|
||||
void PowerFSM_setup();
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ class PowerStatus : public Status
|
||||
isCharging = newStatus->isCharging;
|
||||
}
|
||||
if (isDirty) {
|
||||
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||
// DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
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
|
||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorkerThread.h"
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
/**
|
||||
* @brief A worker thread that waits on a freertos notification
|
||||
*/
|
||||
class BaseNotifiedWorkerThread : public WorkerThread
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
virtual void notify(uint32_t v = 0, eNotifyAction action = eNoAction) = 0;
|
||||
|
||||
/**
|
||||
* Notify from an ISR
|
||||
*
|
||||
* This must be inline or IRAM_ATTR on ESP32
|
||||
*/
|
||||
virtual void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction) { notify(v, action); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The notification that was most recently used to wake the thread. Read from loop()
|
||||
*/
|
||||
uint32_t notification = 0;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
|
||||
*/
|
||||
virtual void block() = 0;
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,12 +0,0 @@
|
||||
#include "Thread.h"
|
||||
#include <assert.h>
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
void BaseThread::callRun(void *_this)
|
||||
{
|
||||
((BaseThread *)_this)->doRun();
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "freertosinc.h"
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Base threading
|
||||
*/
|
||||
class BaseThread
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* set this to true to ask thread to cleanly exit asap
|
||||
*/
|
||||
volatile bool wantExit = false;
|
||||
|
||||
public:
|
||||
virtual void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY) = 0;
|
||||
|
||||
virtual ~BaseThread() {}
|
||||
|
||||
// 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()
|
||||
*/
|
||||
virtual void serviceWatchdog() {}
|
||||
virtual void startWatchdog() {}
|
||||
virtual void stopWatchdog() {}
|
||||
|
||||
static void callRun(void *_this);
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#include "NotifiedWorkerThread.h"
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
void FreeRtosNotifiedWorkerThread::notify(uint32_t v, eNotifyAction action)
|
||||
{
|
||||
xTaskNotify(taskHandle, v, action);
|
||||
}
|
||||
|
||||
void FreeRtosNotifiedWorkerThread::block()
|
||||
{
|
||||
xTaskNotifyWait(0, // don't clear notification on entry
|
||||
clearOnRead, ¬ification, portMAX_DELAY); // Wait forever
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
#endif
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseNotifiedWorkerThread.h"
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
/**
|
||||
* @brief A worker thread that waits on a freertos notification
|
||||
*/
|
||||
class FreeRtosNotifiedWorkerThread : public BaseNotifiedWorkerThread
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
void notify(uint32_t v = 0, eNotifyAction action = eNoAction);
|
||||
|
||||
/**
|
||||
* Notify from an ISR
|
||||
*
|
||||
* This must be inline or IRAM_ATTR on ESP32
|
||||
*/
|
||||
inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction)
|
||||
{
|
||||
xTaskNotifyFromISR(taskHandle, v, action, highPriWoken);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
|
||||
*/
|
||||
virtual void block();
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "FreeRtosThread.h"
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "esp_task_wdt.h"
|
||||
#endif
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
void FreeRtosThread::start(const char *name, size_t stackSize, uint32_t priority)
|
||||
{
|
||||
auto r = xTaskCreate(callRun, name, stackSize, this, priority, &taskHandle);
|
||||
assert(r == pdPASS);
|
||||
}
|
||||
|
||||
void FreeRtosThread::serviceWatchdog()
|
||||
{
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
esp_task_wdt_reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
void FreeRtosThread::startWatchdog()
|
||||
{
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
auto r = esp_task_wdt_add(taskHandle);
|
||||
assert(r == ESP_OK);
|
||||
#endif
|
||||
}
|
||||
|
||||
void FreeRtosThread::stopWatchdog()
|
||||
{
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
auto r = esp_task_wdt_delete(taskHandle);
|
||||
assert(r == ESP_OK);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
#endif
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseThread.h"
|
||||
#include "freertosinc.h"
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Base threading
|
||||
*/
|
||||
class FreeRtosThread : public BaseThread
|
||||
{
|
||||
protected:
|
||||
TaskHandle_t taskHandle = NULL;
|
||||
|
||||
public:
|
||||
void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY);
|
||||
|
||||
virtual ~FreeRtosThread() { 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();
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "NotifiedWorkerThread.h"
|
||||
#include "configuration.h"
|
||||
#include <assert.h>
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
static bool debugNotification;
|
||||
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
bool NotifiedWorkerThread::notify(uint32_t v, bool overwrite)
|
||||
{
|
||||
bool r = notifyCommon(v, overwrite);
|
||||
|
||||
if (r)
|
||||
mainDelay.interrupt();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
IRAM_ATTR bool NotifiedWorkerThread::notifyCommon(uint32_t v, bool overwrite)
|
||||
{
|
||||
if (overwrite || notification == 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,17 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "FreeRtosNotifiedWorkerThread.h"
|
||||
#include "PosixNotifiedWorkerThread.h"
|
||||
#include "OSThread.h"
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
typedef FreeRtosNotifiedWorkerThread NotifiedWorkerThread;
|
||||
#endif
|
||||
/**
|
||||
* @brief A worker thread that waits on a freertos notification
|
||||
*/
|
||||
class NotifiedWorkerThread : public OSThread
|
||||
{
|
||||
/**
|
||||
* The notification that was most recently used to wake the thread. Read from runOnce()
|
||||
*/
|
||||
uint32_t notification = 0;
|
||||
|
||||
#ifdef __unix__
|
||||
typedef PosixNotifiedWorkerThread NotifiedWorkerThread;
|
||||
#endif
|
||||
public:
|
||||
NotifiedWorkerThread(const char *name) : OSThread(name) {}
|
||||
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
bool notify(uint32_t v, bool overwrite);
|
||||
|
||||
/**
|
||||
* Notify from an ISR
|
||||
*
|
||||
* This must be inline or IRAM_ATTR on ESP32
|
||||
*/
|
||||
bool notifyFromISR(BaseType_t *highPriWoken, uint32_t v, bool overwrite);
|
||||
|
||||
/**
|
||||
* Schedule a notification to fire in delay msecs
|
||||
*/
|
||||
bool notifyLater(uint32_t delay, uint32_t v, bool overwrite);
|
||||
|
||||
protected:
|
||||
virtual void onNotify(uint32_t notification) = 0;
|
||||
|
||||
virtual int32_t runOnce();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
bool notifyCommon(uint32_t v, bool overwrite);
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
#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?
|
||||
*/
|
||||
class Periodic : public PeriodicTask
|
||||
class Periodic : public OSThread
|
||||
{
|
||||
uint32_t (*callback)();
|
||||
int32_t (*callback)();
|
||||
|
||||
public:
|
||||
// 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:
|
||||
void doTask() {
|
||||
uint32_t p = callback();
|
||||
setPeriod(p);
|
||||
}
|
||||
int32_t runOnce() { return callback(); }
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "PeriodicScheduler.h"
|
||||
#include "PeriodicTask.h"
|
||||
#include "LockGuard.h"
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
/// call this from loop
|
||||
void PeriodicScheduler::loop()
|
||||
{
|
||||
LockGuard lg(&lock);
|
||||
|
||||
uint32_t now = 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 <Arduino.h>
|
||||
#include "PeriodicScheduler.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 = 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,19 +0,0 @@
|
||||
#include "PosixNotifiedWorkerThread.h"
|
||||
|
||||
#ifdef __unix__
|
||||
|
||||
#include <Utility.h>
|
||||
|
||||
using namespace concurrency;
|
||||
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
void PosixNotifiedWorkerThread::notify(uint32_t v, eNotifyAction action) NOT_IMPLEMENTED("notify");
|
||||
|
||||
/**
|
||||
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
|
||||
*/
|
||||
void PosixNotifiedWorkerThread::block() NOT_IMPLEMENTED("block");
|
||||
|
||||
#endif
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseNotifiedWorkerThread.h"
|
||||
|
||||
namespace concurrency {
|
||||
|
||||
/**
|
||||
* @brief A worker thread that waits on a freertos notification
|
||||
*/
|
||||
class PosixNotifiedWorkerThread : public BaseNotifiedWorkerThread
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Notify this thread so it can run
|
||||
*/
|
||||
void notify(uint32_t v = 0, eNotifyAction action = eNoAction);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
|
||||
*/
|
||||
virtual void block();
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseThread.h"
|
||||
|
||||
#ifdef __unix__
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Base threading
|
||||
*/
|
||||
class PosixThread : public BaseThread
|
||||
{
|
||||
protected:
|
||||
public:
|
||||
void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY) {}
|
||||
|
||||
virtual ~PosixThread() {}
|
||||
|
||||
// uint32_t getStackHighwaterMark() { return uxTaskGetStackHighWaterMark(taskHandle); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The method that will be called when start is called.
|
||||
*/
|
||||
virtual void doRun() = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
#endif
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "FreeRtosThread.h"
|
||||
#include "PosixThread.h"
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
typedef FreeRtosThread Thread;
|
||||
#endif
|
||||
|
||||
#ifdef __unix__
|
||||
typedef PosixThread Thread;
|
||||
#endif
|
||||
|
||||
} // namespace concurrency
|
||||
@@ -1,31 +0,0 @@
|
||||
#include "WorkerThread.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 (millis() - lastPrint > 10 * 1000L) {
|
||||
lastPrint = 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
|
||||
+24
-5
@@ -77,6 +77,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BUTTON_PIN PIN_BUTTON1
|
||||
#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)
|
||||
#elif defined(CubeCell_BoardPlus)
|
||||
|
||||
@@ -95,6 +99,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// Standard definitions for ESP32 targets
|
||||
//
|
||||
|
||||
#define HAS_WIFI
|
||||
|
||||
#define GPS_SERIAL_NUM 1
|
||||
#define GPS_RX_PIN 34
|
||||
#ifdef USE_JTAG
|
||||
@@ -133,6 +139,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define SSD1306_ADDRESS 0x3C
|
||||
#define ST7567_ADDRESS 0x3F
|
||||
|
||||
// The SH1106 controller is almost, but not quite, the same as SSD1306
|
||||
// Define this if you know you have that controller or your "SSD1306" misbehaves.
|
||||
@@ -140,10 +147,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Flip the screen upside down by default as it makes more sense on T-BEAM
|
||||
// devices. Comment this out to not rotate screen 180 degrees.
|
||||
#define FLIP_SCREEN_VERTICALLY
|
||||
#define SCREEN_FLIP_VERTICALLY
|
||||
|
||||
// DEBUG LED
|
||||
#define LED_INVERTED 0 // define as 1 if LED is active low (on)
|
||||
// Define if screen should be mirrored left to right
|
||||
// #define SCREEN_MIRROR
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// GPS
|
||||
@@ -163,6 +170,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_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
|
||||
// not found then probe for SX1262
|
||||
#define USE_RF95
|
||||
@@ -184,8 +194,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// code)
|
||||
#endif
|
||||
|
||||
// Leave undefined to disable our PMU IRQ handler
|
||||
#define PMU_IRQ 35
|
||||
// Leave undefined to disable our PMU IRQ handler. DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts
|
||||
// and waking from light sleep
|
||||
// #define PMU_IRQ 35
|
||||
#define AXP192_SLAVE_ADDRESS 0x34
|
||||
|
||||
#elif defined(TBEAM_V07)
|
||||
@@ -361,6 +372,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#endif
|
||||
|
||||
// DEBUG LED
|
||||
#ifndef LED_INVERTED
|
||||
#define LED_INVERTED 0 // define as 1 if LED is active low (on)
|
||||
#endif
|
||||
|
||||
#ifdef USE_RF95
|
||||
#define RF95_RESET LORA_RESET
|
||||
#define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0
|
||||
@@ -418,3 +434,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define GPS_POWER_CTRL_CH 3
|
||||
#define LORA_POWER_CTRL_CH 2
|
||||
|
||||
// Default Bluetooth PIN
|
||||
#define defaultBLEPin 123456
|
||||
|
||||
@@ -72,7 +72,7 @@ int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
||||
crc.update(data, len);
|
||||
Update.write(data, 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;
|
||||
}
|
||||
|
||||
@@ -33,25 +33,36 @@ void WiFiServerAPI::loop()
|
||||
if (client.connected()) {
|
||||
StreamAPI::loop();
|
||||
} else {
|
||||
DEBUG_MSG("Client dropped connection, closing UDP server\n");
|
||||
DEBUG_MSG("Client dropped connection, closing TCP server\n");
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
#define MESHTASTIC_PORTNUM 4403
|
||||
|
||||
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM) {}
|
||||
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM), concurrency::OSThread("ApiServer") {}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void WiFiServerPort::loop()
|
||||
int32_t WiFiServerPort::runOnce()
|
||||
{
|
||||
auto client = available();
|
||||
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
|
||||
|
||||
#include "StreamAPI.h"
|
||||
#include "concurrency/OSThread.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
|
||||
*/
|
||||
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:
|
||||
WiFiServerPort();
|
||||
|
||||
void init();
|
||||
|
||||
void loop();
|
||||
int32_t runOnce();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#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() {
|
||||
NMEAGPS::sleep();
|
||||
#ifdef PIN_GPS_WAKE
|
||||
sendCommand("$PGKC105,4");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// wake the GPS into normal operation mode
|
||||
void Air530GPS::wake()
|
||||
{
|
||||
#if 1
|
||||
NMEAGPS::wake();
|
||||
#else
|
||||
// For power testing - keep GPS sleeping forever
|
||||
sleep();
|
||||
#endif
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
+244
-50
@@ -1,14 +1,16 @@
|
||||
|
||||
#include "GPS.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "configuration.h"
|
||||
#include "sleep.h"
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
|
||||
// If we have a serial GPS port it will not be null
|
||||
#ifdef GPS_RX_PIN
|
||||
HardwareSerial _serial_gps_real(GPS_SERIAL_NUM);
|
||||
HardwareSerial *GPS::_serial_gps = &_serial_gps_real;
|
||||
#elif defined(NRF52840_XXAA)
|
||||
#elif defined(NRF52840_XXAA) || defined(NRF52833_XXAA)
|
||||
// Assume NRF52840
|
||||
HardwareSerial *GPS::_serial_gps = &Serial1;
|
||||
#else
|
||||
@@ -21,67 +23,259 @@ uint8_t GPS::i2cAddress = GPS_I2C_ADDRESS;
|
||||
uint8_t GPS::i2cAddress = 0;
|
||||
#endif
|
||||
|
||||
bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep
|
||||
|
||||
GPS *gps;
|
||||
|
||||
// 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()
|
||||
bool GPS::setupGPS()
|
||||
{
|
||||
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) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
|
||||
timeStartMsec = now;
|
||||
zeroOffsetSecs = tv.tv_sec;
|
||||
}
|
||||
}
|
||||
|
||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
void perhapsSetRTC(const struct timeval *tv)
|
||||
{
|
||||
if (!timeSetFromGPS) {
|
||||
timeSetFromGPS = true;
|
||||
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
||||
#ifndef NO_ESP32
|
||||
settimeofday(tv, NULL);
|
||||
if (_serial_gps) {
|
||||
#ifdef GPS_RX_PIN
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
#else
|
||||
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
|
||||
_serial_gps->begin(GPS_BAUDRATE);
|
||||
#endif
|
||||
readFromRTC();
|
||||
#ifndef NO_ESP32
|
||||
_serial_gps->setRxBufferSize(2048); // the default is 256
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GPS::setup()
|
||||
{
|
||||
// Master power for the GPS
|
||||
#ifdef PIN_GPS_EN
|
||||
digitalWrite(PIN_GPS_EN, PIN_GPS_EN);
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef PIN_GPS_RESET
|
||||
digitalWrite(PIN_GPS_RESET, 1); // assert for 10ms
|
||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||
delay(10);
|
||||
digitalWrite(PIN_GPS_RESET, 0);
|
||||
#endif
|
||||
|
||||
setAwake(true); // Wake GPS power before doing any init
|
||||
bool ok = setupGPS();
|
||||
|
||||
if (ok)
|
||||
notifySleepObserver.observe(¬ifySleep);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Allow defining the polarity of the WAKE output. default is active high
|
||||
#ifndef GPS_WAKE_ACTIVE
|
||||
#define GPS_WAKE_ACTIVE 1
|
||||
#endif
|
||||
|
||||
void GPS::wake()
|
||||
{
|
||||
#ifdef PIN_GPS_WAKE
|
||||
digitalWrite(PIN_GPS_WAKE, GPS_WAKE_ACTIVE);
|
||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void GPS::sleep() {
|
||||
#ifdef PIN_GPS_WAKE
|
||||
digitalWrite(PIN_GPS_WAKE, GPS_WAKE_ACTIVE ? 0 : 1);
|
||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Record that we have a GPS
|
||||
void GPS::setConnected()
|
||||
{
|
||||
if (!hasGPS) {
|
||||
hasGPS = true;
|
||||
shouldPublish = true;
|
||||
}
|
||||
}
|
||||
|
||||
void perhapsSetRTC(struct tm &t)
|
||||
void GPS::setNumSatellites(uint8_t n)
|
||||
{
|
||||
/* 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);
|
||||
else
|
||||
perhapsSetRTC(&tv);
|
||||
if (n != numSatellites) {
|
||||
numSatellites = n;
|
||||
shouldPublish = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getTime()
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||
if (!wakeAllowed && on) {
|
||||
DEBUG_MSG("Inhibiting because !wakeAllowed\n");
|
||||
on = false;
|
||||
}
|
||||
|
||||
if (isAwake != on) {
|
||||
DEBUG_MSG("WANT GPS=%d\n", on);
|
||||
if (on) {
|
||||
lastWakeStartMsec = millis();
|
||||
wake();
|
||||
} else {
|
||||
lastSleepStartMsec = millis();
|
||||
sleep();
|
||||
}
|
||||
|
||||
isAwake = on;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getValidTime()
|
||||
GpsOperation GPS::getGpsOp() const
|
||||
{
|
||||
return timeSetFromGPS ? getTime() : 0;
|
||||
auto op = radioConfig.preferences.gps_operation;
|
||||
|
||||
if (op == GpsOperation_GpsOpUnset)
|
||||
op = (radioConfig.preferences.location_share == LocationSharing_LocDisabled) ? GpsOperation_GpsOpTimeOnly
|
||||
: GpsOperation_GpsOpMobile;
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
/** Get how long we should stay looking for each aquisition in msecs
|
||||
*/
|
||||
uint32_t GPS::getWakeTime() const
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
+97
-25
@@ -1,35 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/PeriodicTask.h"
|
||||
#include "GPSStatus.h"
|
||||
#include "Observer.h"
|
||||
#include "sys/time.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);
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
// Generate a string representation of 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)
|
||||
*
|
||||
* 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 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:
|
||||
/** If !NULL we will use this serial port to construct our 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
|
||||
// scaling before use)
|
||||
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 */
|
||||
Observable<const meshtastic::GPSStatus *> newStatus;
|
||||
@@ -54,18 +54,90 @@ class GPS
|
||||
/**
|
||||
* Returns true if we succeeded
|
||||
*/
|
||||
virtual bool setup() { return true; }
|
||||
|
||||
/// A loop callback for subclasses that need it. FIXME, instead just block on serial reads
|
||||
virtual void loop() {}
|
||||
virtual bool setup();
|
||||
|
||||
/// Returns ture if we have acquired GPS lock.
|
||||
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
|
||||
* called after the CPU wakes from light-sleep state */
|
||||
virtual void startLock() {}
|
||||
* called after the CPU wakes from light-sleep state
|
||||
*
|
||||
* 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();
|
||||
|
||||
/// 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;
|
||||
|
||||
+95
-63
@@ -1,4 +1,5 @@
|
||||
#include "NMEAGPS.h"
|
||||
#include "RTC.h"
|
||||
#include "configuration.h"
|
||||
|
||||
static int32_t toDegInt(RawDegrees d)
|
||||
@@ -10,71 +11,102 @@ static int32_t toDegInt(RawDegrees d)
|
||||
return r;
|
||||
}
|
||||
|
||||
void NMEAGPS::loop()
|
||||
bool NMEAGPS::setupGPS()
|
||||
{
|
||||
GPS::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();
|
||||
// Serial.write(c);
|
||||
reader.encode(c);
|
||||
// DEBUG_MSG("%c", c);
|
||||
isValid |= reader.encode(c);
|
||||
}
|
||||
|
||||
uint32_t now = 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)
|
||||
}
|
||||
|
||||
uint8_t fixtype = reader.fixQuality();
|
||||
hasValidLocation = ((fixtype >= 1) && (fixtype <= 5));
|
||||
|
||||
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 NMEA 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);
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
+24
-4
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/PeriodicTask.h"
|
||||
#include "GPS.h"
|
||||
#include "Observer.h"
|
||||
#include "TinyGPS++.h"
|
||||
@@ -14,8 +13,29 @@ class NMEAGPS : public GPS
|
||||
{
|
||||
TinyGPSPlus reader;
|
||||
|
||||
uint32_t lastUpdateMsec = 0;
|
||||
|
||||
public:
|
||||
virtual void loop();
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
+113
-104
@@ -1,48 +1,37 @@
|
||||
#include "UBloxGPS.h"
|
||||
#include "RTC.h"
|
||||
#include "error.h"
|
||||
#include "sleep.h"
|
||||
#include <assert.h>
|
||||
|
||||
UBloxGPS::UBloxGPS() : concurrency::PeriodicTask()
|
||||
{
|
||||
notifySleepObserver.observe(¬ifySleep);
|
||||
}
|
||||
UBloxGPS::UBloxGPS() {}
|
||||
|
||||
bool UBloxGPS::tryConnect()
|
||||
{
|
||||
isConnected = false;
|
||||
bool c = false;
|
||||
|
||||
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
|
||||
// supports the newer API
|
||||
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) {
|
||||
#ifdef GPS_RX_PIN
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
#else
|
||||
_serial_gps->begin(GPS_BAUDRATE);
|
||||
#endif
|
||||
// _serial_gps.setRxBufferSize(1024); // the default is 256
|
||||
}
|
||||
|
||||
#ifdef GPS_POWER_EN
|
||||
pinMode(GPS_POWER_EN, OUTPUT);
|
||||
digitalWrite(GPS_POWER_EN, 1);
|
||||
delay(200); // Give time for the GPS to startup after we gave power
|
||||
#endif
|
||||
GPS::setupGPS();
|
||||
|
||||
// uncomment to see debug info
|
||||
// ublox.enableDebugging(Serial);
|
||||
|
||||
// try a second time, the ublox lib serial parsing is buggy?
|
||||
@@ -50,14 +39,12 @@ bool UBloxGPS::setup()
|
||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||
delay(500);
|
||||
|
||||
if (isConnected) {
|
||||
if (isConnected()) {
|
||||
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
|
||||
|
||||
if (!setUBXMode())
|
||||
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;
|
||||
} else {
|
||||
return false;
|
||||
@@ -113,96 +100,118 @@ bool UBloxGPS::factoryReset()
|
||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||
delay(500);
|
||||
|
||||
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected);
|
||||
if (isConnected)
|
||||
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected());
|
||||
if (isConnected())
|
||||
ok = setUBXMode();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
int UBloxGPS::prepareSleep(void *unused)
|
||||
/** Idle processing while GPS is looking for lock */
|
||||
void UBloxGPS::whileActive()
|
||||
{
|
||||
if (isConnected)
|
||||
ublox.powerOff();
|
||||
ublox.getT(maxWait()); // ask for new time data - hopefully ready when we come back
|
||||
|
||||
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) {
|
||||
// Consume all characters that have arrived
|
||||
|
||||
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
|
||||
|
||||
// if using i2c or serial look too see if any chars are ready
|
||||
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
||||
|
||||
// If we don't have a fix (a quick check), don't try waiting for a solution)
|
||||
// Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions
|
||||
// turn off for now
|
||||
uint16_t maxWait = i2cAddress ? 300 : 0; // If using i2c we must poll with wait
|
||||
fixtype = ublox.getFixType(maxWait);
|
||||
DEBUG_MSG("GPS fix type %d\n", fixtype);
|
||||
|
||||
// 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);
|
||||
if (ublox.moduleQueried.gpsSecond) {
|
||||
/* 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(RTCQualityGPS, t);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 10s until we have something
|
||||
// over the serial
|
||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
||||
return false;
|
||||
}
|
||||
|
||||
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");
|
||||
wantNewLocation = true;
|
||||
setPeriod(1);
|
||||
bool foundLocation = false;
|
||||
|
||||
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);
|
||||
}
|
||||
+39
-22
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/PeriodicTask.h"
|
||||
#include "GPS.h"
|
||||
#include "Observer.h"
|
||||
#include "SparkFun_Ublox_Arduino_Library.h"
|
||||
@@ -10,29 +9,14 @@
|
||||
*
|
||||
* When new data is available it will notify observers.
|
||||
*/
|
||||
class UBloxGPS : public GPS, public concurrency::PeriodicTask
|
||||
class UBloxGPS : public GPS
|
||||
{
|
||||
SFE_UBLOX_GPS ublox;
|
||||
|
||||
bool wantNewLocation = true;
|
||||
|
||||
CallbackObserver<UBloxGPS, void *> notifySleepObserver = CallbackObserver<UBloxGPS, void *>(this, &UBloxGPS::prepareSleep);
|
||||
uint8_t fixType = 0;
|
||||
|
||||
public:
|
||||
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
|
||||
*
|
||||
@@ -40,15 +24,48 @@ class UBloxGPS : public GPS, public concurrency::PeriodicTask
|
||||
*/
|
||||
bool factoryReset();
|
||||
|
||||
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);
|
||||
protected:
|
||||
/**
|
||||
* Returns true if we succeeded
|
||||
*/
|
||||
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
|
||||
bool tryConnect();
|
||||
|
||||
/// Switch to our desired operating mode and save the settings to flash
|
||||
/// returns true for success
|
||||
bool setUBXMode();
|
||||
|
||||
uint16_t maxWait() const { return i2cAddress ? 300 : 0; /*If using i2c we must poll with wait */ }
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
+324
-92
@@ -31,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include "target_specific.h"
|
||||
#include "utils.h"
|
||||
|
||||
@@ -57,39 +58,73 @@ static char ourId[5];
|
||||
static bool heartbeat = false;
|
||||
#endif
|
||||
|
||||
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)
|
||||
|
||||
#ifndef SCREEN_TRANSITION_MSECS
|
||||
#define SCREEN_TRANSITION_MSECS 300
|
||||
#endif
|
||||
|
||||
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// draw an xbm image.
|
||||
// Please note that everything that should be transitioned
|
||||
// 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);
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
const char *region = xstr(HW_VERSION);
|
||||
if (*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string
|
||||
region += 4;
|
||||
// draw centered left to right and centered above the one line of app text
|
||||
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
|
||||
icon_width, icon_height, (const uint8_t *)icon_bits);
|
||||
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
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];
|
||||
snprintf(buf, sizeof(buf), "%s",
|
||||
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)
|
||||
{
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(ArialMT_Plain_16);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(64 + x, y, "Bluetooth");
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->drawString(64 + x, FONT_HEIGHT + y + 2, "Enter this code");
|
||||
display->setFont(FONT_SMALL);
|
||||
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->setFont(ArialMT_Plain_10);
|
||||
display->setFont(FONT_SMALL);
|
||||
char buf[30];
|
||||
const char *name = "Name: ";
|
||||
strcpy(buf, name);
|
||||
@@ -111,10 +146,10 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
// with the third parameter you can define the width after which words will
|
||||
// be wrapped. Currently only spaces and "-" are allowed for wrapping
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(ArialMT_Plain_16);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
||||
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
|
||||
static char tempBuf[96];
|
||||
@@ -134,8 +169,8 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
||||
int xo = x, yo = y;
|
||||
while (*f) {
|
||||
display->drawString(xo, yo, *f);
|
||||
yo += FONT_HEIGHT;
|
||||
if (yo > SCREEN_HEIGHT - FONT_HEIGHT) {
|
||||
yo += FONT_HEIGHT_SMALL;
|
||||
if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) {
|
||||
xo += SCREEN_WIDTH / 2;
|
||||
yo = 0;
|
||||
}
|
||||
@@ -161,14 +196,14 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
||||
// Wrap to next row, if needed.
|
||||
if (++col >= COLUMNS) {
|
||||
xo = x;
|
||||
yo += FONT_HEIGHT;
|
||||
yo += FONT_HEIGHT_SMALL;
|
||||
col = 0;
|
||||
}
|
||||
f++;
|
||||
}
|
||||
if (col != 0) {
|
||||
// Include last incomplete line in our total.
|
||||
yo += FONT_HEIGHT;
|
||||
yo += FONT_HEIGHT_SMALL;
|
||||
}
|
||||
|
||||
return yo;
|
||||
@@ -235,11 +270,27 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus
|
||||
display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
|
||||
|
||||
// Draw the number of satellites
|
||||
sprintf(satsString, "%d", gps->getNumSatellites());
|
||||
sprintf(satsString, "%lu", gps->getNumSatellites());
|
||||
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
|
||||
static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||
{
|
||||
@@ -459,7 +510,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
|
||||
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
@@ -472,11 +523,11 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
uint32_t agoSecs = sinceLastSeen(node);
|
||||
static char lastStr[20];
|
||||
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
|
||||
snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60);
|
||||
snprintf(lastStr, sizeof(lastStr), "%lu minutes ago", agoSecs / 60);
|
||||
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];
|
||||
strcpy(distStr, "? km"); // might not have location data
|
||||
@@ -514,7 +565,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
// direction to node is unknown so display question mark
|
||||
// Debug info for gps lock errors
|
||||
// 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);
|
||||
|
||||
// Must be after distStr is populated
|
||||
@@ -544,7 +595,10 @@ void _screen_header()
|
||||
}
|
||||
#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)
|
||||
{
|
||||
@@ -556,9 +610,12 @@ void Screen::handleSetOn(bool on)
|
||||
DEBUG_MSG("Turning on screen\n");
|
||||
dispdev.displayOn();
|
||||
dispdev.displayOn();
|
||||
enabled = true;
|
||||
setInterval(0); // Draw ASAP
|
||||
} else {
|
||||
DEBUG_MSG("Turning off screen\n");
|
||||
dispdev.displayOff();
|
||||
enabled = false;
|
||||
}
|
||||
screenOn = on;
|
||||
}
|
||||
@@ -566,17 +623,21 @@ void Screen::handleSetOn(bool on)
|
||||
|
||||
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
|
||||
// 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;
|
||||
|
||||
dispdev.resetOrientation();
|
||||
// I think this is not needed - redundant with ui.init
|
||||
// dispdev.resetOrientation();
|
||||
|
||||
// Initialising the UI will init the display too.
|
||||
ui.init();
|
||||
ui.setTimePerTransition(300); // msecs
|
||||
|
||||
displayWidth = dispdev.width();
|
||||
displayHeight = dispdev.height();
|
||||
|
||||
ui.setTimePerTransition(SCREEN_TRANSITION_MSECS);
|
||||
|
||||
ui.setIndicatorPosition(BOTTOM);
|
||||
// Defines where the first frame is located in the bar.
|
||||
ui.setIndicatorDirection(LEFT_RIGHT);
|
||||
@@ -602,7 +663,9 @@ void Screen::setup()
|
||||
// Set up a log buffer with 3 lines, 32 chars each.
|
||||
dispdev.setLogBuffer(3, 32);
|
||||
|
||||
#ifdef FLIP_SCREEN_VERTICALLY
|
||||
#ifdef SCREEN_MIRROR
|
||||
dispdev.mirrorScreen();
|
||||
#elif defined(SCREEN_FLIP_VERTICALLY)
|
||||
dispdev.flipScreenVertically();
|
||||
#endif
|
||||
|
||||
@@ -625,14 +688,33 @@ void Screen::setup()
|
||||
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 (!useDisplay) {
|
||||
setPeriod(0);
|
||||
return;
|
||||
enabled = false;
|
||||
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.
|
||||
for (;;) {
|
||||
ScreenCmd cmd;
|
||||
@@ -667,10 +749,14 @@ void Screen::doTask()
|
||||
|
||||
if (!screenOn) { // If we didn't just wake and the screen is still off, then
|
||||
// stop updating until it is on again
|
||||
setPeriod(0);
|
||||
return;
|
||||
enabled = false;
|
||||
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
|
||||
// but we should only call setTargetFPS when framestate changes, because
|
||||
// otherwise that breaks animations.
|
||||
@@ -679,6 +765,7 @@ void Screen::doTask()
|
||||
DEBUG_MSG("Setting idle framerate\n");
|
||||
targetFramerate = IDLE_FRAMERATE;
|
||||
ui.setTargetFPS(targetFramerate);
|
||||
forceDisplay();
|
||||
}
|
||||
|
||||
// While showing the bootscreen or Bluetooth pair screen all of our
|
||||
@@ -687,14 +774,12 @@ void Screen::doTask()
|
||||
// standard screen loop handling here
|
||||
}
|
||||
|
||||
ui.update();
|
||||
|
||||
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
|
||||
// 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
|
||||
// as fast as we really need so that any rounding errors still result with
|
||||
// the correct framerate
|
||||
setPeriod(1000 / targetFramerate);
|
||||
return (1000 / targetFramerate);
|
||||
}
|
||||
|
||||
void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
@@ -709,6 +794,11 @@ void Screen::drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUi
|
||||
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
|
||||
void Screen::setFrames()
|
||||
@@ -740,11 +830,18 @@ void Screen::setFrames()
|
||||
// call a method on debugInfoScreen object (for more details)
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
|
||||
|
||||
if (isWifiAvailable()) {
|
||||
// call a method on debugInfoScreen object (for more details)
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline;
|
||||
}
|
||||
|
||||
ui.setFrames(normalFrames, numframes);
|
||||
ui.enableAllIndicators();
|
||||
|
||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
||||
// just changed)
|
||||
|
||||
setFastFramerate(); // Draw ASAP
|
||||
}
|
||||
|
||||
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
||||
@@ -754,16 +851,17 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
||||
|
||||
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
||||
|
||||
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
|
||||
snprintf(btPIN, sizeof(btPIN), "%06lu", pin);
|
||||
|
||||
ui.disableAllIndicators();
|
||||
ui.setFrames(btFrames, 1);
|
||||
setFastFramerate();
|
||||
}
|
||||
|
||||
void Screen::handlePrint(const char *text)
|
||||
{
|
||||
DEBUG_MSG("Screen: %s", text);
|
||||
if (!useDisplay)
|
||||
if (!useDisplay || !showingNormalScreen)
|
||||
return;
|
||||
|
||||
dispdev.print(text);
|
||||
@@ -774,22 +872,31 @@ void Screen::handleOnPress()
|
||||
// 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 (ui.getUiState()->frameState == FIXED) {
|
||||
setPeriod(1); // redraw ASAP
|
||||
ui.nextFrame();
|
||||
|
||||
DEBUG_MSG("Setting fast framerate\n");
|
||||
|
||||
// We are about to start a transition so speed up fps
|
||||
targetFramerate = TRANSITION_FRAMERATE;
|
||||
ui.setTargetFPS(targetFramerate);
|
||||
setFastFramerate();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SCREEN_TRANSITION_FRAMERATE
|
||||
#define SCREEN_TRANSITION_FRAMERATE 30 // fps
|
||||
#endif
|
||||
|
||||
void Screen::setFastFramerate()
|
||||
{
|
||||
DEBUG_MSG("Setting fast framerate\n");
|
||||
|
||||
// We are about to start a transition so speed up fps
|
||||
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
|
||||
ui.setTargetFPS(targetFramerate);
|
||||
setInterval(0); // redraw ASAP
|
||||
}
|
||||
|
||||
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
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
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
@@ -798,26 +905,26 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
{
|
||||
concurrency::LockGuard guard(&lock);
|
||||
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
|
||||
display->drawString(x, y + FONT_HEIGHT, channelStr);
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
|
||||
// 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->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT, ourId);
|
||||
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_SMALL, ourId);
|
||||
|
||||
// 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 */
|
||||
#ifdef SHOW_REDRAWS
|
||||
@@ -828,40 +935,165 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
char batStr[20];
|
||||
if (powerStatus->getHasBattery())
|
||||
{
|
||||
if (powerStatus->getHasBattery()) {
|
||||
int batV = powerStatus->getBatteryVoltageMv() / 1000;
|
||||
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
|
||||
|
||||
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c",
|
||||
batV,
|
||||
batCv,
|
||||
powerStatus->getBatteryChargePercent(),
|
||||
powerStatus->getIsCharging() ? '+' : ' ',
|
||||
powerStatus->getHasUSB() ? 'U' : ' ');
|
||||
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(),
|
||||
powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' ');
|
||||
|
||||
// Line 1
|
||||
display->drawString(x, y, batStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Line 1
|
||||
display->drawString(x, y, String("USB"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO: Display status of the BT radio
|
||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth("BT On"), y, "BT On");
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("Mode " + String(channelSettings.modem_config)), y,
|
||||
"Mode " + String(channelSettings.modem_config));
|
||||
|
||||
// Line 2
|
||||
uint32_t currentMillis = millis();
|
||||
@@ -874,19 +1106,21 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
minutes %= 60;
|
||||
hours %= 24;
|
||||
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, String(days) + "d "
|
||||
+ (hours < 10 ? "0" : "") + String(hours) + ":"
|
||||
+ (minutes < 10 ? "0" : "") + String(minutes) + ":"
|
||||
+ (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));
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1,
|
||||
String(days) + "d " + (hours < 10 ? "0" : "") + String(hours) + ":" + (minutes < 10 ? "0" : "") +
|
||||
String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds));
|
||||
|
||||
#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
|
||||
// TODO: Use this line for WiFi information.
|
||||
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth("WiFi: 192.168.0.100"))) / 2, y + FONT_HEIGHT * 2, "WiFi: 192.168.0.100");
|
||||
drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||||
|
||||
// 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 */
|
||||
#ifdef SHOW_REDRAWS
|
||||
@@ -915,10 +1149,8 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
// DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
||||
switch (arg->getStatusType()) {
|
||||
case STATUS_TYPE_NODE:
|
||||
if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
|
||||
setFrames(); // Regen the list of screens
|
||||
prevFrame = -1; // Force a GUI update
|
||||
setPeriod(1); // Update the screen right away
|
||||
if (showingNormalScreen && (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal())) {
|
||||
setFrames(); // Regen the list of screens
|
||||
}
|
||||
nodeDB.updateGUI = false;
|
||||
nodeDB.updateTextMessage = false;
|
||||
|
||||
+27
-7
@@ -6,18 +6,26 @@
|
||||
|
||||
#ifdef USE_SH1106
|
||||
#include <SH1106Wire.h>
|
||||
#elif defined(USE_ST7567)
|
||||
#include <ST7567Wire.h>
|
||||
#else
|
||||
#include <SSD1306Wire.h>
|
||||
#endif
|
||||
|
||||
#include "TFT.h"
|
||||
#include "EInkDisplay.h"
|
||||
#include "TFTDisplay.h"
|
||||
#include "TypedQueue.h"
|
||||
#include "commands.h"
|
||||
#include "concurrency/LockGuard.h"
|
||||
#include "concurrency/PeriodicTask.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "power.h"
|
||||
#include <string>
|
||||
|
||||
// 0 to 255, though particular variants might define different defaults
|
||||
#ifndef BRIGHTNESS_DEFAULT
|
||||
#define BRIGHTNESS_DEFAULT 150
|
||||
#endif
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
@@ -46,7 +54,7 @@ class DebugInfo
|
||||
/// Renders the debug screen.
|
||||
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 drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
std::string channelName;
|
||||
|
||||
@@ -61,7 +69,7 @@ class DebugInfo
|
||||
* multiple times simultaneously. All state-changing calls are queued and executed
|
||||
* 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 *>(this, &Screen::handleStatusUpdate);
|
||||
@@ -96,7 +104,7 @@ class Screen : public concurrency::PeriodicTask
|
||||
|
||||
// Implementation to Adjust Brightness
|
||||
void adjustBrightness();
|
||||
uint8_t brightness = 150;
|
||||
uint8_t brightness = BRIGHTNESS_DEFAULT;
|
||||
|
||||
/// Starts showing the Bluetooth PIN screen.
|
||||
//
|
||||
@@ -179,11 +187,14 @@ class Screen : public concurrency::PeriodicTask
|
||||
|
||||
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||
|
||||
/// Used to force (super slow) eink displays to draw critical frames
|
||||
void forceDisplay();
|
||||
|
||||
protected:
|
||||
/// Updates the UI.
|
||||
//
|
||||
// Called periodically from the main loop.
|
||||
void doTask() final;
|
||||
int32_t runOnce() final;
|
||||
|
||||
private:
|
||||
struct ScreenCmd {
|
||||
@@ -201,7 +212,7 @@ class Screen : public concurrency::PeriodicTask
|
||||
return true; // claim success if our display is not in use
|
||||
else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -215,11 +226,16 @@ class Screen : public concurrency::PeriodicTask
|
||||
/// Rebuilds our list of frames (screens) to default ones.
|
||||
void setFrames();
|
||||
|
||||
/// Try to start drawing ASAP
|
||||
void setFastFramerate();
|
||||
|
||||
/// 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 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.
|
||||
TypedQueue<ScreenCmd> cmdQueue;
|
||||
/// Whether we are using a display
|
||||
@@ -237,8 +253,12 @@ class Screen : public concurrency::PeriodicTask
|
||||
/** FIXME cleanup display abstraction */
|
||||
#ifdef ST7735_CS
|
||||
TFTDisplay dispdev;
|
||||
#elif defined(HAS_EINK)
|
||||
EInkDisplay dispdev;
|
||||
#elif defined(USE_SH1106)
|
||||
SH1106Wire dispdev;
|
||||
#elif defined(USE_ST7567)
|
||||
ST7567Wire dispdev;
|
||||
#else
|
||||
SSD1306Wire dispdev;
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#ifdef ST7735_CS
|
||||
#include "SPILock.h"
|
||||
#include "TFT.h"
|
||||
#include "TFTDisplay.h"
|
||||
#include "graphics/configs.h"
|
||||
#include <SPI.h>
|
||||
#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)
|
||||
{
|
||||
setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will
|
||||
// work ie GEOMETRY_RAWMODE
|
||||
setGeometry(GEOMETRY_RAWMODE, 160, 80);
|
||||
}
|
||||
|
||||
// Write the buffer to the display memory
|
||||
@@ -20,19 +19,17 @@ void TFTDisplay::display(void)
|
||||
{
|
||||
concurrency::LockGuard g(spiLock);
|
||||
|
||||
#if 1
|
||||
// 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 < SCREEN_HEIGHT; y++) {
|
||||
for (uint8_t x = 0; x < SCREEN_WIDTH; x++) {
|
||||
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) * SCREEN_WIDTH];
|
||||
auto b = buffer[x + (y / 8) * displayWidth];
|
||||
auto isset = b & (1 << (y & 7));
|
||||
tft.drawPixel(x, y, isset ? TFT_WHITE : TFT_BLACK);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Send a command to the display (low level function)
|
||||
@@ -52,12 +49,10 @@ bool TFTDisplay::connect()
|
||||
pinMode(ST7735_BACKLIGHT_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
tft.init();
|
||||
tft.setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
// tft.drawRect(0, 0, 40, 10, TFT_PURPLE); // wide rectangle in upper left
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
#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)
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
#define TRANSITION_FRAMERATE 30 // fps
|
||||
#define IDLE_FRAMERATE 1 // in fps
|
||||
#define COMPASS_DIAM 44
|
||||
|
||||
|
||||
+199
-124
@@ -1,42 +1,24 @@
|
||||
/*
|
||||
|
||||
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 "MeshService.h"
|
||||
#include "NMEAGPS.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "UBloxGPS.h"
|
||||
#include "concurrency/Periodic.h"
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "power.h"
|
||||
// #include "rom/rtc.h"
|
||||
#include "DSRRouter.h"
|
||||
// #include "debug.h"
|
||||
#include "RTC.h"
|
||||
#include "SPILock.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "concurrency/Periodic.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "meshwifi/meshhttp.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include <OneButton.h>
|
||||
@@ -54,8 +36,10 @@
|
||||
#include "variant.h"
|
||||
#endif
|
||||
|
||||
using namespace concurrency;
|
||||
|
||||
// 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
|
||||
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
|
||||
@@ -66,11 +50,12 @@ meshtastic::GPSStatus *gpsStatus = new meshtastic::GPSStatus();
|
||||
// Global Node status
|
||||
meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
|
||||
|
||||
bool ssd1306_found;
|
||||
/// The I2C address of our display (if found)
|
||||
uint8_t screen_found;
|
||||
|
||||
bool axp192_found;
|
||||
|
||||
DSRRouter realRouter;
|
||||
Router &router = realRouter; // Users of router don't care what sort of subclass implements that API
|
||||
Router *router = NULL; // Users of router don't care what sort of subclass implements that API
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Application
|
||||
@@ -89,9 +74,13 @@ void scanI2Cdevice(void)
|
||||
nDevices++;
|
||||
|
||||
if (addr == SSD1306_ADDRESS) {
|
||||
ssd1306_found = true;
|
||||
screen_found = addr;
|
||||
DEBUG_MSG("ssd1306 display found\n");
|
||||
}
|
||||
if (addr == ST7567_ADDRESS) {
|
||||
screen_found = addr;
|
||||
DEBUG_MSG("st7567 display found\n");
|
||||
}
|
||||
#ifdef AXP192_SLAVE_ADDRESS
|
||||
if (addr == AXP192_SLAVE_ADDRESS) {
|
||||
axp192_found = true;
|
||||
@@ -120,7 +109,7 @@ const char *getDeviceName()
|
||||
return name;
|
||||
}
|
||||
|
||||
static uint32_t ledBlinker()
|
||||
static int32_t ledBlinker()
|
||||
{
|
||||
static bool ledOn;
|
||||
ledOn ^= 1;
|
||||
@@ -128,26 +117,119 @@ static uint32_t ledBlinker()
|
||||
setLed(ledOn);
|
||||
|
||||
// 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
|
||||
#ifdef BUTTON_PIN
|
||||
OneButton userButton;
|
||||
OneButton userButton;
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
OneButton userButtonAlt;
|
||||
OneButton userButtonAlt;
|
||||
#endif
|
||||
void userButtonPressed()
|
||||
|
||||
public:
|
||||
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||
ButtonThread() : OSThread("Button")
|
||||
{
|
||||
#ifdef BUTTON_PIN
|
||||
userButton = OneButton(BUTTON_PIN, true, true);
|
||||
userButton.attachClick(userButtonPressed);
|
||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||
userButton.attachDoubleClick(userButtonDoublePressed);
|
||||
wakeOnIrq(BUTTON_PIN, FALLING);
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
|
||||
userButtonAlt.attachClick(userButtonPressed);
|
||||
userButtonAlt.attachDuringLongPress(userButtonPressedLong);
|
||||
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
|
||||
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 &= userButtonAlt.isIdle();
|
||||
#endif
|
||||
// if (!canSleep) DEBUG_MSG("Supressing sleep!\n");
|
||||
//else DEBUG_MSG("sleep ok\n");
|
||||
|
||||
return 5;
|
||||
}
|
||||
|
||||
private:
|
||||
static void userButtonPressed()
|
||||
{
|
||||
// DEBUG_MSG("press!\n");
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
}
|
||||
static void userButtonPressedLong()
|
||||
{
|
||||
DEBUG_MSG("Long press!\n");
|
||||
screen->adjustBrightness();
|
||||
}
|
||||
|
||||
static void userButtonDoublePressed()
|
||||
{
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
}
|
||||
void userButtonPressedLong()
|
||||
{
|
||||
screen.adjustBrightness();
|
||||
#ifndef NO_ESP32
|
||||
disablePin();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
static Periodic *ledPeriodic;
|
||||
static OSThread *powerFSMthread, *buttonThread;
|
||||
|
||||
RadioInterface *rIf = NULL;
|
||||
|
||||
void setup()
|
||||
{
|
||||
@@ -172,55 +254,58 @@ void setup()
|
||||
digitalWrite(RESET_OLED, 1);
|
||||
#endif
|
||||
|
||||
OSThread::setup();
|
||||
|
||||
ledPeriodic = new Periodic("Blink", ledBlinker);
|
||||
|
||||
router = new DSRRouter();
|
||||
|
||||
#ifdef I2C_SDA
|
||||
Wire.begin(I2C_SDA, I2C_SCL);
|
||||
#else
|
||||
Wire.begin();
|
||||
#endif
|
||||
// i2c still busted on new board
|
||||
#ifndef ARDUINO_NRF52840_PPR
|
||||
scanI2Cdevice();
|
||||
|
||||
#ifdef PIN_LCD_RESET
|
||||
// FIXME - move this someplace better, LCD is at address 0x3F
|
||||
pinMode(PIN_LCD_RESET, OUTPUT);
|
||||
digitalWrite(PIN_LCD_RESET, 0);
|
||||
delay(1);
|
||||
digitalWrite(PIN_LCD_RESET, 1);
|
||||
delay(1);
|
||||
#endif
|
||||
|
||||
scanI2Cdevice();
|
||||
|
||||
// Buttons & LED
|
||||
#ifdef BUTTON_PIN
|
||||
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
|
||||
buttonThread = new ButtonThread();
|
||||
|
||||
#ifdef LED_PIN
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
|
||||
#endif
|
||||
|
||||
ledPeriodic.setup();
|
||||
|
||||
// Hello
|
||||
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", optstr(APP_VERSION), optstr(HW_VERSION));
|
||||
|
||||
#ifndef NO_ESP32
|
||||
// Don't init display if we don't have one or we are waking headless due to a timer event
|
||||
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
|
||||
ssd1306_found = false; // forget we even have the hardware
|
||||
screen_found = 0; // forget we even have the hardware
|
||||
|
||||
esp32Setup();
|
||||
#endif
|
||||
|
||||
// Currently only the tbeam has a PMU
|
||||
power = new Power();
|
||||
power->setup();
|
||||
power->setStatusHandler(powerStatus);
|
||||
powerStatus->observe(&power->newStatus);
|
||||
|
||||
#ifdef NRF52_SERIES
|
||||
nrf52Setup();
|
||||
#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)
|
||||
initSPI();
|
||||
#ifdef NO_ESP32
|
||||
@@ -232,50 +317,58 @@ void setup()
|
||||
#endif
|
||||
|
||||
// Initialize the screen first so we can show the logo while we start up everything else.
|
||||
#ifdef ST7735_CS
|
||||
screen.setup();
|
||||
#else
|
||||
if (ssd1306_found)
|
||||
screen.setup();
|
||||
#endif
|
||||
|
||||
screen.print("Started...\n");
|
||||
screen = new graphics::Screen(screen_found);
|
||||
|
||||
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
|
||||
|
||||
// If we know we have a L80 GPS, don't try UBLOX
|
||||
#ifndef L80_RESET
|
||||
// If we don't have bidirectional comms, we can't even try talking to UBLOX
|
||||
UBloxGPS *ublox = NULL;
|
||||
#ifdef GPS_TX_PIN
|
||||
// Init GPS - first try ublox
|
||||
auto ublox = new UBloxGPS();
|
||||
ublox = new UBloxGPS();
|
||||
gps = ublox;
|
||||
if (!gps->setup()) {
|
||||
DEBUG_MSG("ERROR: No UBLOX GPS found\n");
|
||||
|
||||
delete ublox;
|
||||
gps = ublox = NULL;
|
||||
|
||||
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
|
||||
// assume NMEA at 9600 baud.
|
||||
DEBUG_MSG("Hoping that NMEA might work\n");
|
||||
|
||||
// dumb NMEA access only work for serial GPSes)
|
||||
gps = new NMEAGPS();
|
||||
gps->setup();
|
||||
}
|
||||
}
|
||||
#else
|
||||
gps = new NMEAGPS();
|
||||
gps->setup();
|
||||
#endif
|
||||
|
||||
if (!gps && 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
|
||||
// assume NMEA at 9600 baud.
|
||||
// dumb NMEA access only work for serial GPSes)
|
||||
DEBUG_MSG("Hoping that NMEA might work\n");
|
||||
|
||||
#ifdef HAS_AIR530_GPS
|
||||
gps = new Air530GPS();
|
||||
#else
|
||||
gps = new NMEAGPS();
|
||||
#endif
|
||||
gps->setup();
|
||||
}
|
||||
|
||||
if (gps)
|
||||
gpsStatus->observe(&gps->newStatus);
|
||||
else
|
||||
DEBUG_MSG("Warning: No GPS found - running without GPS\n");
|
||||
|
||||
nodeStatus->observe(&nodeDB.newStatus);
|
||||
|
||||
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 (screen_found)
|
||||
screen->setup();
|
||||
#endif
|
||||
|
||||
screen->print("Started...\n");
|
||||
|
||||
// We have now loaded our saved preferences from flash
|
||||
|
||||
// ONCE we will factory reset the GPS for bug #327
|
||||
@@ -292,8 +385,7 @@ void setup()
|
||||
digitalWrite(SX1262_ANT_SW, 1);
|
||||
#endif
|
||||
|
||||
// MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
||||
RadioInterface *rIf = NULL;
|
||||
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
||||
|
||||
#if defined(RF95_IRQ)
|
||||
if (!rIf) {
|
||||
@@ -328,13 +420,17 @@ void setup()
|
||||
}
|
||||
#endif
|
||||
|
||||
// Initialize Wifi
|
||||
initWifi();
|
||||
|
||||
if (!rIf)
|
||||
recordCriticalError(ErrNoRadio);
|
||||
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
|
||||
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
|
||||
setCPUFast(false); // 80MHz is fine for our slow peripherals
|
||||
@@ -357,21 +453,12 @@ uint32_t axpDebugRead()
|
||||
return 30 * 1000;
|
||||
}
|
||||
|
||||
concurrency::Periodic axpDebugOutput(axpDebugRead);
|
||||
Periodic axpDebugOutput(axpDebugRead);
|
||||
axpDebugOutput.setup();
|
||||
#endif
|
||||
|
||||
void loop()
|
||||
{
|
||||
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
|
||||
|
||||
if (gps)
|
||||
gps->loop(); // FIXME, remove from main, instead block on read
|
||||
router.loop();
|
||||
powerFSM.run_machine();
|
||||
service.loop();
|
||||
|
||||
concurrency::periodicScheduler.loop();
|
||||
// axpDebugOutput.loop();
|
||||
|
||||
#ifdef DEBUG_PORT
|
||||
@@ -383,23 +470,9 @@ void loop()
|
||||
#ifndef NO_ESP32
|
||||
esp32Loop();
|
||||
#endif
|
||||
#ifdef TBEAM_V10
|
||||
power->loop();
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
userButton.tick();
|
||||
#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 && (millis() > 3000)) {
|
||||
screen.stopBootScreen();
|
||||
showingBootScreen = false;
|
||||
}
|
||||
// For debugging
|
||||
// if (rIf) ((RadioLibInterface *)rIf)->isActivelyReceiving();
|
||||
|
||||
#ifdef DEBUG_STACK
|
||||
static uint32_t lastPrint = 0;
|
||||
@@ -409,16 +482,18 @@ void loop()
|
||||
}
|
||||
#endif
|
||||
|
||||
// Update the screen last, after we've figured out what to show.
|
||||
screen.debug_info()->setChannelNameStatus(getChannelName());
|
||||
// TODO: This should go into a thread handled by FreeRTOS.
|
||||
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);
|
||||
service.loop();
|
||||
|
||||
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons
|
||||
// feel slow
|
||||
msecstosleep = 10;
|
||||
long delayMsec = mainController.runOrDelay();
|
||||
|
||||
delay(msecstosleep);
|
||||
/* 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");
|
||||
}
|
||||
|
||||
+8
-11
@@ -1,26 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/Screen.h"
|
||||
#include "PowerStatus.h"
|
||||
#include "GPSStatus.h"
|
||||
#include "NodeStatus.h"
|
||||
#include "PowerStatus.h"
|
||||
#include "graphics/Screen.h"
|
||||
|
||||
extern bool axp192_found;
|
||||
extern bool ssd1306_found;
|
||||
extern bool isCharging;
|
||||
extern bool isUSBPowered;
|
||||
|
||||
// Global Screen singleton.
|
||||
extern graphics::Screen screen;
|
||||
//extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
||||
extern graphics::Screen *screen;
|
||||
// extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
||||
|
||||
//extern meshtastic::PowerStatus *powerStatus;
|
||||
//extern meshtastic::GPSStatus *gpsStatus;
|
||||
//extern meshtastic::NodeStatusHandler *nodeStatusHandler;
|
||||
// extern meshtastic::PowerStatus *powerStatus;
|
||||
// extern meshtastic::GPSStatus *gpsStatus;
|
||||
// extern meshtastic::NodeStatusHandler *nodeStatusHandler;
|
||||
|
||||
// Return a human readable string of the form "Meshtastic_ab13"
|
||||
const char *getDeviceName();
|
||||
|
||||
|
||||
|
||||
void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop();
|
||||
void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "PacketHistory.h"
|
||||
#include "../concurrency/PeriodicTask.h"
|
||||
#include "Router.h"
|
||||
|
||||
/**
|
||||
|
||||
+12
-71
@@ -6,76 +6,17 @@
|
||||
#include "configuration.h"
|
||||
#include "mesh.pb.h"
|
||||
|
||||
// US channel settings
|
||||
#define CH0_US 903.08f // MHz
|
||||
#define CH_SPACING_US 2.16f // MHz
|
||||
#define NUM_CHANNELS_US 13
|
||||
// Map from old region names to new region enums
|
||||
struct RegionInfo {
|
||||
RegionCode code;
|
||||
uint8_t numChannels;
|
||||
uint8_t powerLimit; // Or zero for not set
|
||||
float freq;
|
||||
float spacing;
|
||||
const char *name; // EU433 etc
|
||||
};
|
||||
|
||||
// EU433 channel settings
|
||||
#define CH0_EU433 433.175f // MHz
|
||||
#define CH_SPACING_EU433 0.2f // MHz
|
||||
#define NUM_CHANNELS_EU433 8
|
||||
extern const RegionInfo regions[];
|
||||
extern const RegionInfo *myRegion;
|
||||
|
||||
// EU865 channel settings
|
||||
#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
|
||||
|
||||
// AU/NZ channel settings 915-928MHz
|
||||
#define CH0_ANZ 916.0f // MHz - avoid overcrowding on 915.0
|
||||
#define CH_SPACING_ANZ 0.5f
|
||||
#define NUM_CHANNELS_ANZ 20
|
||||
|
||||
// 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
|
||||
#elif defined(HW_VERSION_ANZ)
|
||||
// Australia and NZ
|
||||
#define CH0 CH0_ANZ
|
||||
#define CH_SPACING CH_SPACING_ANZ
|
||||
#define NUM_CHANNELS NUM_CHANNELS_ANZ
|
||||
#else
|
||||
// HW version not set - assume US
|
||||
#define CH0 CH0_US
|
||||
#define CH_SPACING CH_SPACING_US
|
||||
#define NUM_CHANNELS NUM_CHANNELS_US
|
||||
#endif
|
||||
extern void initRegion();
|
||||
+31
-26
@@ -10,6 +10,7 @@
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "power.h"
|
||||
@@ -48,14 +49,14 @@ MeshService service;
|
||||
|
||||
#include "Router.h"
|
||||
|
||||
static uint32_t sendOwnerCb()
|
||||
static int32_t sendOwnerCb()
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -64,17 +65,18 @@ MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
|
||||
|
||||
void MeshService::init()
|
||||
{
|
||||
sendOwnerPeriod.setup();
|
||||
sendOwnerPeriod = new concurrency::Periodic("SendOwner", sendOwnerCb);
|
||||
|
||||
nodeDB.init();
|
||||
|
||||
if (gps)
|
||||
gpsObserver.observe(&gps->newStatus);
|
||||
packetReceivedObserver.observe(&router.notifyPacketReceived);
|
||||
packetReceivedObserver.observe(&router->notifyPacketReceived);
|
||||
}
|
||||
|
||||
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
MeshPacket *p = router.allocForSending();
|
||||
MeshPacket *p = router->allocForSending();
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
p->decoded.which_payload = SubPacket_user_tag;
|
||||
@@ -121,7 +123,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
|
||||
sendOurOwner(mp->from);
|
||||
|
||||
String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n";
|
||||
screen.print(lcd.c_str());
|
||||
screen->print(lcd.c_str());
|
||||
}
|
||||
|
||||
return mp;
|
||||
@@ -139,7 +141,7 @@ void MeshService::handleIncomingPosition(const MeshPacket *mp)
|
||||
tv.tv_sec = secs;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
perhapsSetRTC(&tv);
|
||||
perhapsSetRTC(RTCQualityFromNet, &tv);
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG("Ignoring incoming packet - not a position\n");
|
||||
@@ -150,12 +152,8 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
|
||||
{
|
||||
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 (!gps->isConnected)
|
||||
handleIncomingPosition(mp);
|
||||
else {
|
||||
DEBUG_MSG("Ignoring incoming time, because we have a GPS\n");
|
||||
}
|
||||
// If it is a position packet, perhaps set our clock - this must be before nodeDB.updateFrom
|
||||
handleIncomingPosition(mp);
|
||||
|
||||
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) {
|
||||
mp = handleFromRadioUser(mp);
|
||||
@@ -197,12 +195,17 @@ void MeshService::loop()
|
||||
}
|
||||
|
||||
/// 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
|
||||
nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings
|
||||
|
||||
// This will also update the region as needed
|
||||
bool didReset = nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings
|
||||
|
||||
configChanged.notifyObservers(NULL);
|
||||
nodeDB.saveToDisk();
|
||||
|
||||
return didReset;
|
||||
}
|
||||
|
||||
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
|
||||
@@ -227,8 +230,8 @@ void MeshService::handleToRadio(MeshPacket &p)
|
||||
if (p.id == 0)
|
||||
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
|
||||
// (so we update our nodedb for the local node)
|
||||
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
|
||||
// (so we update our nodedb for the local node)
|
||||
|
||||
// Send the packet into the mesh
|
||||
|
||||
@@ -248,10 +251,10 @@ void MeshService::sendToMesh(MeshPacket *p)
|
||||
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
|
||||
// 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.
|
||||
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);
|
||||
p->decoded.position.time = 0;
|
||||
} else
|
||||
@@ -259,7 +262,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
|
||||
router.sendLocal(p);
|
||||
router->sendLocal(p);
|
||||
}
|
||||
|
||||
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
|
||||
@@ -281,12 +284,13 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
|
||||
assert(node->has_position);
|
||||
|
||||
// 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->decoded.which_payload = SubPacket_position_tag;
|
||||
p->decoded.position = node->position;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -294,7 +298,7 @@ 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)
|
||||
MeshPacket *p = router.allocForSending();
|
||||
MeshPacket *p = router->allocForSending();
|
||||
p->decoded.which_payload = SubPacket_position_tag;
|
||||
|
||||
Position &pos = p->decoded.position;
|
||||
@@ -304,9 +308,10 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
||||
pos.altitude = gps->altitude;
|
||||
pos.latitude_i = gps->latitude;
|
||||
pos.longitude_i = gps->longitude;
|
||||
pos.time = getValidTime();
|
||||
}
|
||||
|
||||
pos.time = getValidTime(RTCQualityGPS);
|
||||
|
||||
// Include our current battery voltage in our position announcement
|
||||
pos.battery_level = powerStatus->getBatteryChargePercent();
|
||||
updateBatteryLevel(pos.battery_level);
|
||||
@@ -316,7 +321,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
||||
// We limit our GPS broadcasts to a max rate
|
||||
static uint32_t lastGpsSend;
|
||||
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;
|
||||
DEBUG_MSG("Sending position to mesh\n");
|
||||
|
||||
|
||||
@@ -63,8 +63,10 @@ class MeshService
|
||||
*/
|
||||
void handleToRadio(MeshPacket &p);
|
||||
|
||||
/// The radioConfig object just changed, call this to force the hw to change to the new settings
|
||||
void reloadConfig();
|
||||
/** The radioConfig object just changed, call this to force the hw to change to the new settings
|
||||
* @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
|
||||
void reloadOwner();
|
||||
|
||||
+60
-19
@@ -6,13 +6,16 @@
|
||||
|
||||
#include "CryptoEngine.h"
|
||||
#include "GPS.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PacketHistory.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
@@ -102,29 +105,27 @@ const char *getChannelName()
|
||||
|
||||
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)
|
||||
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
||||
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
||||
|
||||
if (radioConfig.preferences.sds_secs == 0) {
|
||||
DEBUG_MSG("RadioConfig reset!\n");
|
||||
radioConfig.preferences.send_owner_interval = 4; // per sw-design.md
|
||||
radioConfig.preferences.position_broadcast_secs = 15 * 60;
|
||||
radioConfig.preferences.wait_bluetooth_secs = 120;
|
||||
radioConfig.preferences.screen_on_secs = 5 * 60;
|
||||
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;
|
||||
if (radioConfig.preferences.factory_reset) {
|
||||
DEBUG_MSG("Performing factory reset!\n");
|
||||
installDefaultDeviceState();
|
||||
didFactoryReset = true;
|
||||
} else if (!channelSettings.psk.size) {
|
||||
DEBUG_MSG("Setting default preferences!\n");
|
||||
|
||||
radioConfig.has_channel_settings = true;
|
||||
radioConfig.has_preferences = true;
|
||||
|
||||
// radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
|
||||
// channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide bandwidth
|
||||
// so incompatible radios can talk together
|
||||
// channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide
|
||||
// bandwidth so incompatible radios can talk together
|
||||
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
||||
|
||||
channelSettings.tx_power = 0; // default
|
||||
@@ -146,13 +147,25 @@ void NodeDB::resetRadioConfig()
|
||||
radioConfig.preferences.wait_bluetooth_secs = 30;
|
||||
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
||||
radioConfig.preferences.ls_secs = 60;
|
||||
radioConfig.preferences.region = RegionCode_TW;
|
||||
}
|
||||
|
||||
// Update the global myRegion
|
||||
initRegion();
|
||||
|
||||
return didFactoryReset;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
*numNodes = 0; // Forget node DB
|
||||
|
||||
// init our devicestate with valid flags so protobuf writing/reading will work
|
||||
devicestate.has_my_node = true;
|
||||
devicestate.has_radio = true;
|
||||
@@ -181,6 +194,12 @@ void NodeDB::installDefaultDeviceState()
|
||||
// owner.short_name now
|
||||
sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]);
|
||||
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()
|
||||
@@ -213,13 +232,29 @@ void NodeDB::init()
|
||||
|
||||
// We set these _after_ loading from disk - because they come from the build and are more trusted than
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
|
||||
strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
|
||||
|
||||
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
|
||||
@@ -384,10 +419,10 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
|
||||
switch (p.which_payload) {
|
||||
case SubPacket_position_tag: {
|
||||
// we carefully preserve the old time, because we always trust our local timestamps more
|
||||
uint32_t oldtime = info->position.time;
|
||||
// we always trust our local timestamps more
|
||||
info->position = p.position;
|
||||
info->position.time = oldtime;
|
||||
if (mp.rx_time)
|
||||
info->position.time = mp.rx_time;
|
||||
info->has_position = true;
|
||||
updateGUIforNode = info;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
@@ -406,6 +441,12 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
updateTextMessage = true;
|
||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
|
||||
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;
|
||||
|
||||
+29
-3
@@ -46,8 +46,13 @@ class NodeDB
|
||||
/// write to flash
|
||||
void saveToDisk();
|
||||
|
||||
// Reinit radio config if needed, because sometimes a buggy android app might send us bogus settings
|
||||
void resetRadioConfig();
|
||||
/** Reinit radio config if needed, because either:
|
||||
* 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
|
||||
/// 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
|
||||
*/
|
||||
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)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user