mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-22 02:32:23 +00:00
Merge branch 'master' into master
This commit is contained in:
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -57,6 +57,7 @@
|
|||||||
"HFSR",
|
"HFSR",
|
||||||
"Meshtastic",
|
"Meshtastic",
|
||||||
"NEMAGPS",
|
"NEMAGPS",
|
||||||
|
"NMEAGPS",
|
||||||
"RDEF",
|
"RDEF",
|
||||||
"Ublox",
|
"Ublox",
|
||||||
"bkpt",
|
"bkpt",
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ COUNTRIES="US EU433 EU865 CN JP ANZ KR"
|
|||||||
#COUNTRIES=CN
|
#COUNTRIES=CN
|
||||||
|
|
||||||
BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
|
BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
|
||||||
|
# BOARDS_ESP32=tbeam
|
||||||
|
|
||||||
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
|
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
|
||||||
BOARDS_NRF52="lora-relay-v1"
|
BOARDS_NRF52="lora-relay-v1"
|
||||||
BOARDS="$BOARDS_ESP32 $BOARDS_NRF52"
|
|
||||||
#BOARDS=tbeam
|
|
||||||
|
|
||||||
OUTDIR=release/latest
|
OUTDIR=release/latest
|
||||||
|
|
||||||
@@ -22,22 +21,61 @@ ARCHIVEDIR=release/archive
|
|||||||
|
|
||||||
rm -f $OUTDIR/firmware*
|
rm -f $OUTDIR/firmware*
|
||||||
|
|
||||||
mkdir -p $OUTDIR/bins $OUTDIR/elfs
|
mkdir -p $OUTDIR/bins
|
||||||
rm -f $OUTDIR/bins/*
|
rm -r $OUTDIR/bins/*
|
||||||
|
mkdir -p $OUTDIR/bins/universal $OUTDIR/elfs/universal
|
||||||
|
|
||||||
# build the named environment and copy the bins to the release directory
|
# build the named environment and copy the bins to the release directory
|
||||||
function do_build {
|
function do_build() {
|
||||||
echo "Building for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
BOARD=$1
|
||||||
|
COUNTRY=$2
|
||||||
|
isNrf=$3
|
||||||
|
|
||||||
|
echo "Building $COUNTRY for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
||||||
rm -f .pio/build/$BOARD/firmware.*
|
rm -f .pio/build/$BOARD/firmware.*
|
||||||
|
|
||||||
# The shell vars the build tool expects to find
|
# The shell vars the build tool expects to find
|
||||||
export HW_VERSION="1.0-$COUNTRY"
|
|
||||||
export APP_VERSION=$VERSION
|
export APP_VERSION=$VERSION
|
||||||
export COUNTRY
|
|
||||||
|
# Are we building a universal/regionless rom?
|
||||||
|
if [ "x$COUNTRY" != "x" ]
|
||||||
|
then
|
||||||
|
export HW_VERSION="1.0-$COUNTRY"
|
||||||
|
export COUNTRY
|
||||||
|
basename=firmware-$BOARD-$COUNTRY-$VERSION
|
||||||
|
else
|
||||||
|
export HW_VERSION="1.0"
|
||||||
|
unset COUNTRY
|
||||||
|
basename=universal/firmware-$BOARD-$VERSION
|
||||||
|
fi
|
||||||
|
|
||||||
pio run --jobs 4 --environment $BOARD # -v
|
pio run --jobs 4 --environment $BOARD # -v
|
||||||
SRCELF=.pio/build/$BOARD/firmware.elf
|
SRCELF=.pio/build/$BOARD/firmware.elf
|
||||||
cp $SRCELF $OUTDIR/elfs/firmware-$BOARD-$COUNTRY-$VERSION.elf
|
cp $SRCELF $OUTDIR/elfs/$basename.elf
|
||||||
|
|
||||||
|
if [ "$isNrf" = "false" ]
|
||||||
|
then
|
||||||
|
echo "Copying ESP32 bin file"
|
||||||
|
SRCBIN=.pio/build/$BOARD/firmware.bin
|
||||||
|
cp $SRCBIN $OUTDIR/bins/$basename.bin
|
||||||
|
else
|
||||||
|
echo "Generating NRF52 uf2 file"
|
||||||
|
SRCHEX=.pio/build/$BOARD/firmware.hex
|
||||||
|
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/bins/$basename.uf2 -f 0xADA52840
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_boards() {
|
||||||
|
declare boards=$1
|
||||||
|
declare isNrf=$2
|
||||||
|
for board in $boards; do
|
||||||
|
for country in $COUNTRIES; do
|
||||||
|
do_build $board $country "$isNrf"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Build universal
|
||||||
|
do_build $board "" "$isNrf"
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Make sure our submodules are current
|
# Make sure our submodules are current
|
||||||
@@ -46,26 +84,16 @@ git submodule update
|
|||||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||||
platformio lib update
|
platformio lib update
|
||||||
|
|
||||||
for COUNTRY in $COUNTRIES; do
|
do_boards "$BOARDS_ESP32" "false"
|
||||||
for BOARD in $BOARDS; do
|
do_boards "$BOARDS_NRF52" "true"
|
||||||
do_build $BOARD
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Copying ESP32 bin files"
|
|
||||||
for BOARD in $BOARDS_ESP32; do
|
|
||||||
SRCBIN=.pio/build/$BOARD/firmware.bin
|
|
||||||
cp $SRCBIN $OUTDIR/bins/firmware-$BOARD-$COUNTRY-$VERSION.bin
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Generating NRF52 uf2 files"
|
|
||||||
for BOARD in $BOARDS_NRF52; do
|
|
||||||
SRCHEX=.pio/build/$BOARD/firmware.hex
|
|
||||||
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/bins/firmware-$BOARD-$COUNTRY-$VERSION.uf2 -f 0xADA52840
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
# keep the bins in archive also
|
# keep the bins in archive also
|
||||||
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $ARCHIVEDIR
|
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $OUTDIR/bins/universal/firmware* $OUTDIR/elfs/universal/firmware* $ARCHIVEDIR
|
||||||
|
|
||||||
|
echo Updating android bins $OUTDIR/forandroid
|
||||||
|
rm -rf $OUTDIR/forandroid
|
||||||
|
mkdir -p $OUTDIR/forandroid
|
||||||
|
cp -a $OUTDIR/bins/universal/*.bin $OUTDIR/forandroid/
|
||||||
|
|
||||||
cat >$OUTDIR/curfirmwareversion.xml <<XML
|
cat >$OUTDIR/curfirmwareversion.xml <<XML
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
@@ -79,6 +107,7 @@ Generated by bin/buildall.sh -->
|
|||||||
</resources>
|
</resources>
|
||||||
XML
|
XML
|
||||||
|
|
||||||
|
echo Generating $ARCHIVEDIR/firmware-$VERSION.zip
|
||||||
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
||||||
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
|
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,20 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
# apt install srecord
|
||||||
|
|
||||||
BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader
|
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
|
nrfjprog --eraseall -f nrf52
|
||||||
|
|
||||||
@@ -11,12 +24,12 @@ nrfjprog --eraseall -f nrf52
|
|||||||
# first 4 bytes should be 0x01 to indicate valid app image
|
# first 4 bytes should be 0x01 to indicate valid app image
|
||||||
# second 4 bytes should be 0x00 to indicate no CRC required for 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
|
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
|
srec_cat /tmp/bootconf.bin -binary -offset $BOOTSET -output /tmp/bootconf.hex -intel
|
||||||
|
|
||||||
echo Generating merged hex file
|
echo Generating merged hex file from .pio/build/$PROJ/firmware.hex
|
||||||
mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-124-g69bd8eb-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex
|
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
|
echo Telling bootloader app region is valid and telling CPU to run
|
||||||
nrfjprog --program ttgo_eink_full.hex -f nrf52 --reset
|
nrfjprog --program ${BOARD}_full.hex -f nrf52 --reset
|
||||||
|
|
||||||
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
||||||
|
|||||||
22
bin/install-eink.sh
Executable file
22
bin/install-eink.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
# You probably don't want to use this script, it programs a custom bootloader build onto a nrf52 board
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader
|
||||||
|
|
||||||
|
nrfjprog --eraseall -f nrf52
|
||||||
|
|
||||||
|
# this generates an intel hex file that can be programmed into a NRF52 to tell the adafruit bootloader that the current app image is valid
|
||||||
|
# Bootloader settings are at BOOTLOADER_SETTINGS (rw) : ORIGIN = 0xFF000, LENGTH = 0x1000
|
||||||
|
# first 4 bytes should be 0x01 to indicate valid app image
|
||||||
|
# second 4 bytes should be 0x00 to indicate no CRC required for image
|
||||||
|
echo "01 00 00 00 00 00 00 00" | xxd -r -p - >/tmp/bootconf.bin
|
||||||
|
srec_cat /tmp/bootconf.bin -binary -offset 0xff000 -output /tmp/bootconf.hex -intel
|
||||||
|
|
||||||
|
echo Generating merged hex file
|
||||||
|
mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-125-gf38f8f4-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex
|
||||||
|
|
||||||
|
echo Telling bootloader app region is valid and telling CPU to run
|
||||||
|
nrfjprog --program ttgo_eink_full.hex -f nrf52 --reset
|
||||||
|
|
||||||
|
# nrfjprog --readuicr /tmp/uicr.hex; objdump -s /tmp/uicr.hex | less
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA
|
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52832_XXAA
|
||||||
|
|||||||
3
bin/nrf52833-gdbserver.sh
Executable file
3
bin/nrf52833-gdbserver.sh
Executable file
@@ -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
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
echo "Converting to uf2 for NRF52 Adafruit bootloader"
|
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/
|
# cp flash.uf2 /media/kevinh/FTH*BOOT/
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
export VERSION=1.1.0
|
export VERSION=1.1.6
|
||||||
2
bin/view-map.sh
Executable file
2
bin/view-map.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
echo using amap tool to display memory map
|
||||||
|
amap .pio/build/output.map
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
},
|
},
|
||||||
"core": "nRF5",
|
"core": "nRF5",
|
||||||
"cpu": "cortex-m4",
|
"cpu": "cortex-m4",
|
||||||
"extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V1 -DNRF52840_XXAA",
|
"extra_flags": "-DARDUINO_NRF52840_TTGO_EINK -DNRF52840_XXAA",
|
||||||
"f_cpu": "64000000L",
|
"f_cpu": "64000000L",
|
||||||
"hwids": [
|
"hwids": [
|
||||||
[
|
[
|
||||||
|
|||||||
46
boards/lora-relay-v2.json
Normal file
46
boards/lora-relay-v2.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
46
boards/ppr1.json
Normal file
46
boards/ppr1.json
Normal file
@@ -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
data/style.css
Normal file
277
data/style.css
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Lato Regular'), local('Lato-Regular'), url(./Google.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #C5DDEB;
|
||||||
|
font: 14px/20px "Lato", Arial, sans-serif;
|
||||||
|
padding: 40px 0;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
1fr 4fr;
|
||||||
|
grid-template-areas:
|
||||||
|
"header header"
|
||||||
|
"sidebar content";
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 750px;
|
||||||
|
background: #444753;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {grid-area: header;}
|
||||||
|
.side {grid-area: sidebar;}
|
||||||
|
.main {grid-area: content;}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
border-bottom: 2px solid white;
|
||||||
|
}
|
||||||
|
.top-text {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side {
|
||||||
|
width: 260px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.side .side-header {
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 2px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side .side-header .side-text {
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-list ul {
|
||||||
|
padding: 20px;
|
||||||
|
height: 570px;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
.channel-list ul li {
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-list .channel-name {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-list .message-count {
|
||||||
|
padding-left: 16px;
|
||||||
|
color: #92959E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
stroke-width: 0;
|
||||||
|
stroke: currentColor;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-map-marker {
|
||||||
|
width: 0.5714285714285714em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-circle {
|
||||||
|
width: 0.8571428571428571em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
/* width: 490px; */
|
||||||
|
float: left;
|
||||||
|
background: #F2F5F8;
|
||||||
|
/* border-top-right-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px; */
|
||||||
|
color: #434651;
|
||||||
|
}
|
||||||
|
.content .content-header {
|
||||||
|
flex-grow: 0;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 2px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content .content-header .content-from {
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.content .content-header .content-from .content-from-highlight {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.content .content-header .content-num-messages {
|
||||||
|
color: #92959E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content .content-history {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 20px 20px 20px;
|
||||||
|
border-bottom: 2px solid white;
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: 375px;
|
||||||
|
}
|
||||||
|
.content .content-history ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-inline-start: 10px;
|
||||||
|
}
|
||||||
|
.content .content-history .message-data {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.content .content-history .message-data-time {
|
||||||
|
color: #a8aab1;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
.content .content-history .message {
|
||||||
|
color: white;
|
||||||
|
padding: 8px 10px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 7px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
width: 90%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.content .content-history .message:after {
|
||||||
|
bottom: 100%;
|
||||||
|
left: 7%;
|
||||||
|
border: solid transparent;
|
||||||
|
content: " ";
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
border-bottom-color: #86BB71;
|
||||||
|
border-width: 10px;
|
||||||
|
margin-left: -10px;
|
||||||
|
}
|
||||||
|
.content .content-history .my-message {
|
||||||
|
background: #86BB71;
|
||||||
|
}
|
||||||
|
.content .content-history .other-message {
|
||||||
|
background: #94C2ED;
|
||||||
|
}
|
||||||
|
.content .content-history .other-message:after {
|
||||||
|
border-bottom-color: #94C2ED;
|
||||||
|
left: 93%;
|
||||||
|
}
|
||||||
|
.content .content-message {
|
||||||
|
flex-grow: 0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.content .content-message textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 10px;
|
||||||
|
font: 14px/22px "Lato", Arial, sans-serif;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content .content-message button {
|
||||||
|
float: right;
|
||||||
|
color: #94C2ED;
|
||||||
|
font-size: 16px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
background: #F2F5F8;
|
||||||
|
}
|
||||||
|
.content .content-message button:hover {
|
||||||
|
color: #75b1e8;
|
||||||
|
}
|
||||||
|
/* Tooltip container */
|
||||||
|
.tooltip {
|
||||||
|
color: #86BB71;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
|
||||||
|
}
|
||||||
|
/* Tooltip text */
|
||||||
|
.tooltip .tooltiptext {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 120px;
|
||||||
|
background-color: #444753;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
/* Position the tooltip text - see examples below! */
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the tooltip text when you mouse over the tooltip container */
|
||||||
|
.tooltip:hover .tooltiptext {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.online, .offline, .me {
|
||||||
|
margin-right: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.online {
|
||||||
|
color: #86BB71;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline {
|
||||||
|
color: #E38968;
|
||||||
|
}
|
||||||
|
|
||||||
|
.me {
|
||||||
|
color: #94C2ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix:after {
|
||||||
|
visibility: hidden;
|
||||||
|
display: block;
|
||||||
|
font-size: 0;
|
||||||
|
content: " ";
|
||||||
|
clear: both;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
BIN
data/style.css.gz
Normal file
BIN
data/style.css.gz
Normal file
Binary file not shown.
BIN
docs/hardware/WIFI_LoRa_32_V2(868-915).PDF
Normal file
BIN
docs/hardware/WIFI_LoRa_32_V2(868-915).PDF
Normal file
Binary file not shown.
Binary file not shown.
38977
docs/hardware/u-blox8-M8_ReceiverDescrProtSpec_(UBX-13003221).pdf
Normal file
38977
docs/hardware/u-blox8-M8_ReceiverDescrProtSpec_(UBX-13003221).pdf
Normal file
File diff suppressed because one or more lines are too long
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
You probably don't care about this section - skip to the next one.
|
You probably don't care about this section - skip to the next one.
|
||||||
|
|
||||||
|
Threading tasks:
|
||||||
|
|
||||||
|
- Use https://github.com/ivanseidel/ArduinoThread? rather than full coroutines
|
||||||
|
- clean up main loop()
|
||||||
|
- check that we are mostly asleep, show which thread is causing us to wake
|
||||||
|
-
|
||||||
|
- use tickless idle on nrf52, and sleep X msec or until an interrupt occurs or the cooperative scheduling changes. https://devzone.nordicsemi.com/f/nordic-q-a/12363/nrf52-freertos-power-consumption-tickless-idle
|
||||||
|
- BAD IDEA: use vTaskDelay and https://www.freertos.org/xTaskAbortDelay.html if scheduling changes. (define INCLUDE_xTaskAbortDelay on ESP32 and NRF52 - seems impossible to find?)
|
||||||
|
- GOOD IDEA: use xSemaphoreTake to take a semaphore using a timeout. Expect semaphore to not be set, but set it to indicate scheduling has changed.
|
||||||
|
|
||||||
Nimble tasks:
|
Nimble tasks:
|
||||||
|
|
||||||
- readerror.txt stress test bug
|
- readerror.txt stress test bug
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ Expected sequence for initial download:
|
|||||||
- Read a RadioConfig from "radio" - used to get the channel and radio settings
|
- Read a RadioConfig from "radio" - used to get the channel and radio settings
|
||||||
- Read a User from "user" - to get the username for this node
|
- Read a User from "user" - to get the username for this node
|
||||||
- Read a MyNodeInfo from "mynode" to get information about this local device
|
- Read a MyNodeInfo from "mynode" to get information about this local device
|
||||||
- Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine
|
|
||||||
- Read a series of NodeInfo packets to build the phone's copy of the current NodeDB for the mesh
|
- Read a series of NodeInfo packets to build the phone's copy of the current NodeDB for the mesh
|
||||||
- Read a endConfig packet that indicates that the entire state you need has been sent.
|
- Read a endConfig packet that indicates that the entire state you need has been sent.
|
||||||
- Read a series of MeshPackets until it returns empty to get any messages that arrived for this node while the phone was away
|
- Read a series of MeshPackets until it returns empty to get any messages that arrived for this node while the phone was away
|
||||||
|
|||||||
17
docs/software/gps-todo.txt
Normal file
17
docs/software/gps-todo.txt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
You probably don't care about this ugly file of personal notes ;-)
|
||||||
|
|
||||||
|
for taiwan region:
|
||||||
|
bin/run.sh --set region 8
|
||||||
|
|
||||||
|
time only mode
|
||||||
|
./bin/run.sh --set gps_operation 3
|
||||||
|
|
||||||
|
ublox parsing failure
|
||||||
|
|
||||||
|
record power measurements and update spreadsheet
|
||||||
|
|
||||||
|
have loop methods return allowable sleep time (from their perspective)
|
||||||
|
increase main cpu sleep time
|
||||||
|
|
||||||
|
warn people about crummy gps antennas - add to faq
|
||||||
|
|
||||||
@@ -5,7 +5,27 @@
|
|||||||
|
|
||||||
## RAK815
|
## 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
|
- shrink soft device RAM usage
|
||||||
- get nrf52832 working again (currently OOM)
|
- get nrf52832 working again (currently OOM)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ From lower to higher power consumption.
|
|||||||
|
|
||||||
- full on (ON) - Everything is on, can eventually timeout and lower to a lower power state
|
- full on (ON) - Everything is on, can eventually timeout and lower to a lower power state
|
||||||
onEntry: setBluetoothOn(true), screen.setOn(true)
|
onEntry: setBluetoothOn(true), screen.setOn(true)
|
||||||
onExit: screen.setOn(false)
|
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
|
- 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
|
onEntry: setBluetooth off, screen on
|
||||||
|
|||||||
@@ -30,10 +30,12 @@ build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/n
|
|||||||
-DHW_VERSION_${sysenv.COUNTRY}
|
-DHW_VERSION_${sysenv.COUNTRY}
|
||||||
-DAPP_VERSION=${sysenv.APP_VERSION}
|
-DAPP_VERSION=${sysenv.APP_VERSION}
|
||||||
-DHW_VERSION=${sysenv.HW_VERSION}
|
-DHW_VERSION=${sysenv.HW_VERSION}
|
||||||
|
-DUSE_THREAD_NAMES
|
||||||
|
|
||||||
; leave this commented out to avoid breaking Windows
|
; leave this commented out to avoid breaking Windows
|
||||||
;upload_port = /dev/ttyUSB0
|
;upload_port = /dev/ttyUSB0
|
||||||
;monitor_port = /dev/ttyUSB0
|
;monitor_port = /dev/ttyUSB0
|
||||||
|
|
||||||
;upload_port = /dev/cu.SLAB_USBtoUART
|
;upload_port = /dev/cu.SLAB_USBtoUART
|
||||||
;monitor_port = /dev/cu.SLAB_USBtoUART
|
;monitor_port = /dev/cu.SLAB_USBtoUART
|
||||||
|
|
||||||
@@ -57,15 +59,16 @@ debug_tool = jlink
|
|||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||||
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
|
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/arduino-fsm.git#2f106146071fc7bc620e1e8d4b88dc4e0266ce39
|
||||||
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
|
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
|
||||||
https://github.com/meshtastic/RadioLib.git#ac7feac00f5e0bd95a3ac5d5852b4cc7344cf95c
|
https://github.com/meshtastic/RadioLib.git#8657380241bce681c33aab46598bbf13b11f876c
|
||||||
https://github.com/meshtastic/TinyGPSPlus.git
|
https://github.com/meshtastic/TinyGPSPlus.git
|
||||||
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
|
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
|
||||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||||
SPI
|
SPI
|
||||||
|
https://github.com/geeksville/ArduinoThread.git#333ffd09b596977c217ba25da4258f588b462ac6
|
||||||
|
|
||||||
; Common settings for conventional (non Portduino) Ardino targets
|
; Common settings for conventional (non Portduino) Ardino targets
|
||||||
[arduino_base]
|
[arduino_base]
|
||||||
@@ -91,6 +94,9 @@ build_flags =
|
|||||||
${arduino_base.build_flags} -Wall -Wextra -Isrc/esp32 -Isrc/esp32-mfix-esp32-psram-cache-issue -lnimble -std=c++11
|
${arduino_base.build_flags} -Wall -Wextra -Isrc/esp32 -Isrc/esp32-mfix-esp32-psram-cache-issue -lnimble -std=c++11
|
||||||
-DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
-DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||||
-DAXP_DEBUG_PORT=Serial
|
-DAXP_DEBUG_PORT=Serial
|
||||||
|
lib_deps =
|
||||||
|
${arduino_base.lib_deps}
|
||||||
|
https://github.com/meshtastic/esp32_https_server.git
|
||||||
# Hmm - this doesn't work yet
|
# Hmm - this doesn't work yet
|
||||||
# board_build.ldscript = linker/esp32.extram.bss.ld
|
# board_build.ldscript = linker/esp32.extram.bss.ld
|
||||||
lib_ignore = segger_rtt
|
lib_ignore = segger_rtt
|
||||||
@@ -113,7 +119,7 @@ board_build.partitions = partition-table.csv
|
|||||||
extends = esp32_base
|
extends = esp32_base
|
||||||
board = ttgo-t-beam
|
board = ttgo-t-beam
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${arduino_base.lib_deps}
|
${esp32_base.lib_deps}
|
||||||
build_flags =
|
build_flags =
|
||||||
${esp32_base.build_flags} -D TBEAM_V10
|
${esp32_base.build_flags} -D TBEAM_V10
|
||||||
|
|
||||||
@@ -230,6 +236,15 @@ lib_deps =
|
|||||||
${arduino_base.lib_deps}
|
${arduino_base.lib_deps}
|
||||||
UC1701
|
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
|
; Prototype eink/nrf52840/sx1262 device
|
||||||
[env:eink]
|
[env:eink]
|
||||||
extends = nrf52_base
|
extends = nrf52_base
|
||||||
@@ -265,7 +280,30 @@ lib_deps =
|
|||||||
${arduino_base.lib_deps}
|
${arduino_base.lib_deps}
|
||||||
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
||||||
TFT_eSPI
|
TFT_eSPI
|
||||||
# Adafruit ST7735 and ST7789 Library
|
|
||||||
|
; 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
|
; The Portduino based sim environment on top of linux
|
||||||
[env:linux]
|
[env:linux]
|
||||||
|
|||||||
2
proto
2
proto
Submodule proto updated: 5cdd7bff56...a0b8d88896
149
src/Power.cpp
149
src/Power.cpp
@@ -18,6 +18,21 @@ Power *power;
|
|||||||
|
|
||||||
using namespace meshtastic;
|
using namespace meshtastic;
|
||||||
|
|
||||||
|
#if defined(NRF52_SERIES)
|
||||||
|
/*
|
||||||
|
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
|
||||||
|
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
|
||||||
|
*
|
||||||
|
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
|
||||||
|
* VDD/4, VDD/2 or VDD for the ADC levels.
|
||||||
|
*
|
||||||
|
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
|
||||||
|
*/
|
||||||
|
#define AREF_VOLTAGE 3.6
|
||||||
|
#else
|
||||||
|
#define AREF_VOLTAGE 3.3
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this board has a battery level sensor, set this to a valid implementation
|
* If this board has a battery level sensor, set this to a valid implementation
|
||||||
*/
|
*/
|
||||||
@@ -37,10 +52,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
{
|
{
|
||||||
float v = getBattVoltage() / 1000;
|
float v = getBattVoltage() / 1000;
|
||||||
|
|
||||||
if (v < 2.1)
|
if (v < noBatVolt)
|
||||||
return -1; // If voltage is super low assume no battery installed
|
return -1; // If voltage is super low assume no battery installed
|
||||||
|
|
||||||
return 100 * (v - 3.27) / (4.2 - 3.27);
|
if (v > chargingVolt)
|
||||||
|
return 0; // While charging we can't report % full on the battery
|
||||||
|
|
||||||
|
return 100 * (v - emptyVolt) / (fullVolt - emptyVolt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,9 +66,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
*/
|
*/
|
||||||
virtual float getBattVoltage()
|
virtual float getBattVoltage()
|
||||||
{
|
{
|
||||||
|
// Tested ttgo eink nrf52 board and the reported value is perfect
|
||||||
|
// DEBUG_MSG("raw val %u", raw);
|
||||||
return
|
return
|
||||||
#ifdef BATTERY_PIN
|
#ifdef BATTERY_PIN
|
||||||
1000.0 * analogRead(BATTERY_PIN) * 2.0 * (3.3 / 1024.0);
|
1000.0 * 2.0 * (AREF_VOLTAGE / 1024.0) * analogRead(BATTERY_PIN);
|
||||||
#else
|
#else
|
||||||
NAN;
|
NAN;
|
||||||
#endif
|
#endif
|
||||||
@@ -59,17 +79,40 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
/**
|
/**
|
||||||
* return true if there is a battery installed in this unit
|
* return true if there is a battery installed in this unit
|
||||||
*/
|
*/
|
||||||
virtual bool isBatteryConnect() { return getBattVoltage() != -1; }
|
virtual bool isBatteryConnect() { return getBattPercentage() != -1; }
|
||||||
|
|
||||||
|
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||||
|
/// in power
|
||||||
|
virtual bool isVBUSPlug() { return getBattVoltage() > chargingVolt; }
|
||||||
|
|
||||||
|
/// Assume charging if we have a battery and external power is connected.
|
||||||
|
/// we can't be smart enough to say 'full'?
|
||||||
|
virtual bool isChargeing() { return isBatteryConnect() && isVBUSPlug(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||||
|
/// in power
|
||||||
|
const float fullVolt = 4.2, emptyVolt = 3.27, chargingVolt = 4.3, noBatVolt = 2.1;
|
||||||
} analogLevel;
|
} analogLevel;
|
||||||
|
|
||||||
|
Power::Power() : OSThread("Power") {}
|
||||||
|
|
||||||
bool Power::analogInit()
|
bool Power::analogInit()
|
||||||
{
|
{
|
||||||
#ifdef BATTERY_PIN
|
#ifdef BATTERY_PIN
|
||||||
DEBUG_MSG("Using analog input for battery level\n");
|
DEBUG_MSG("Using analog input for battery level\n");
|
||||||
|
|
||||||
|
// disable any internal pullups
|
||||||
|
pinMode(BATTERY_PIN, INPUT);
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
// ESP32 needs special analog stuff
|
// ESP32 needs special analog stuff
|
||||||
adcAttachPin(BATTERY_PIN);
|
adcAttachPin(BATTERY_PIN);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef NRF52_SERIES
|
||||||
|
analogReference(AR_INTERNAL); // 3.6V
|
||||||
|
#endif
|
||||||
|
|
||||||
// adcStart(BATTERY_PIN);
|
// adcStart(BATTERY_PIN);
|
||||||
analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
|
analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
|
||||||
batteryLevel = &analogLevel;
|
batteryLevel = &analogLevel;
|
||||||
@@ -86,10 +129,7 @@ bool Power::setup()
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
found = analogInit();
|
found = analogInit();
|
||||||
}
|
}
|
||||||
if (found) {
|
enabled = found;
|
||||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
|
||||||
setPeriod(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
@@ -122,7 +162,8 @@ void Power::readPowerStatus()
|
|||||||
const PowerStatus powerStatus =
|
const PowerStatus powerStatus =
|
||||||
PowerStatus(hasBattery ? OptTrue : OptFalse, batteryLevel->isVBUSPlug() ? OptTrue : OptFalse,
|
PowerStatus(hasBattery ? OptTrue : OptFalse, batteryLevel->isVBUSPlug() ? OptTrue : OptFalse,
|
||||||
batteryLevel->isChargeing() ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
|
batteryLevel->isChargeing() ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
|
||||||
DEBUG_MSG("Read power stat %d\n", powerStatus.getHasUSB());
|
DEBUG_MSG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus.getHasUSB(),
|
||||||
|
powerStatus.getIsCharging(), powerStatus.getBatteryVoltageMv(), powerStatus.getBatteryChargePercent());
|
||||||
newStatus.notifyObservers(&powerStatus);
|
newStatus.notifyObservers(&powerStatus);
|
||||||
|
|
||||||
// If we have a battery at all and it is less than 10% full, force deep sleep
|
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||||
@@ -135,13 +176,47 @@ void Power::readPowerStatus()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Power::doTask()
|
int32_t Power::runOnce()
|
||||||
{
|
{
|
||||||
readPowerStatus();
|
readPowerStatus();
|
||||||
|
|
||||||
|
#ifdef TBEAM_V10
|
||||||
|
// WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll
|
||||||
|
// the IRQ status by reading the registers over I2C
|
||||||
|
axp.readIRQ();
|
||||||
|
|
||||||
|
if (axp.isVbusRemoveIRQ()) {
|
||||||
|
DEBUG_MSG("USB unplugged\n");
|
||||||
|
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
||||||
|
}
|
||||||
|
if (axp.isVbusPlugInIRQ()) {
|
||||||
|
DEBUG_MSG("USB plugged In\n");
|
||||||
|
powerFSM.trigger(EVENT_POWER_CONNECTED);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Other things we could check if we cared...
|
||||||
|
|
||||||
|
if (axp.isChargingIRQ()) {
|
||||||
|
DEBUG_MSG("Battery start charging\n");
|
||||||
|
}
|
||||||
|
if (axp.isChargingDoneIRQ()) {
|
||||||
|
DEBUG_MSG("Battery fully charged\n");
|
||||||
|
}
|
||||||
|
if (axp.isBattPlugInIRQ()) {
|
||||||
|
DEBUG_MSG("Battery inserted\n");
|
||||||
|
}
|
||||||
|
if (axp.isBattRemoveIRQ()) {
|
||||||
|
DEBUG_MSG("Battery removed\n");
|
||||||
|
}
|
||||||
|
if (axp.isPEKShortPressIRQ()) {
|
||||||
|
DEBUG_MSG("PEK short button press\n");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
axp.clearIRQ();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Only read once every 20 seconds once the power status for the app has been initialized
|
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||||
if (statusHandler && statusHandler->isInitialized())
|
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
||||||
setPeriod(1000 * 20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,7 +247,7 @@ bool Power::axp192Init()
|
|||||||
DEBUG_MSG("----------------------------------------\n");
|
DEBUG_MSG("----------------------------------------\n");
|
||||||
|
|
||||||
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
||||||
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
// axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power - now turned on in setGpsPower
|
||||||
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
||||||
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
||||||
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
||||||
@@ -204,9 +279,11 @@ bool Power::axp192Init()
|
|||||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||||
|
|
||||||
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
||||||
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
// we do not look for AXP202_CHARGING_FINISHED_IRQ & AXP202_CHARGING_IRQ because it occurs repeatedly while there is
|
||||||
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
|
// no battery also it could cause inadvertent waking from light sleep just because the battery filled
|
||||||
1);
|
// we don't look for AXP202_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed
|
||||||
|
// we don't look at AXP202_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus
|
||||||
|
axp.enableIRQ(AXP202_BATT_CONNECT_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, 1);
|
||||||
|
|
||||||
axp.clearIRQ();
|
axp.clearIRQ();
|
||||||
#endif
|
#endif
|
||||||
@@ -223,43 +300,3 @@ bool Power::axp192Init()
|
|||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Power::loop()
|
|
||||||
{
|
|
||||||
#ifdef PMU_IRQ
|
|
||||||
if (pmu_irq) {
|
|
||||||
pmu_irq = false;
|
|
||||||
axp.readIRQ();
|
|
||||||
|
|
||||||
DEBUG_MSG("pmu irq!\n");
|
|
||||||
|
|
||||||
if (axp.isChargingIRQ()) {
|
|
||||||
DEBUG_MSG("Battery start charging\n");
|
|
||||||
}
|
|
||||||
if (axp.isChargingDoneIRQ()) {
|
|
||||||
DEBUG_MSG("Battery fully charged\n");
|
|
||||||
}
|
|
||||||
if (axp.isVbusRemoveIRQ()) {
|
|
||||||
DEBUG_MSG("USB unplugged\n");
|
|
||||||
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
|
||||||
}
|
|
||||||
if (axp.isVbusPlugInIRQ()) {
|
|
||||||
DEBUG_MSG("USB plugged In\n");
|
|
||||||
powerFSM.trigger(EVENT_POWER_CONNECTED);
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
122
src/PowerFSM.cpp
122
src/PowerFSM.cpp
@@ -12,7 +12,7 @@
|
|||||||
static void sdsEnter()
|
static void sdsEnter()
|
||||||
{
|
{
|
||||||
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
||||||
doDeepSleep(radioConfig.preferences.sds_secs * 1000LL);
|
doDeepSleep(getPref_sds_secs() * 1000LL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
@@ -21,8 +21,8 @@ static uint32_t secsSlept;
|
|||||||
|
|
||||||
static void lsEnter()
|
static void lsEnter()
|
||||||
{
|
{
|
||||||
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", getPref_ls_secs());
|
||||||
screen.setOn(false);
|
screen->setOn(false);
|
||||||
secsSlept = 0; // How long have we been sleeping this time
|
secsSlept = 0; // How long have we been sleeping this time
|
||||||
|
|
||||||
DEBUG_MSG("lsEnter end\n");
|
DEBUG_MSG("lsEnter end\n");
|
||||||
@@ -30,13 +30,13 @@ static void lsEnter()
|
|||||||
|
|
||||||
static void lsIdle()
|
static void lsIdle()
|
||||||
{
|
{
|
||||||
// DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
// DEBUG_MSG("lsIdle begin ls_secs=%u\n", getPref_ls_secs());
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
||||||
|
|
||||||
// Do we have more sleeping to do?
|
// Do we have more sleeping to do?
|
||||||
if (secsSlept < radioConfig.preferences.ls_secs) {
|
if (secsSlept < getPref_ls_secs()) {
|
||||||
// Briefly come out of sleep long enough to blink the led once every few seconds
|
// Briefly come out of sleep long enough to blink the led once every few seconds
|
||||||
uint32_t sleepTime = 30;
|
uint32_t sleepTime = 30;
|
||||||
|
|
||||||
@@ -45,7 +45,8 @@ static void lsIdle()
|
|||||||
setLed(false); // Never leave led on while in light sleep
|
setLed(false); // Never leave led on while in light sleep
|
||||||
wakeCause = doLightSleep(sleepTime * 1000LL);
|
wakeCause = doLightSleep(sleepTime * 1000LL);
|
||||||
|
|
||||||
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {
|
switch (wakeCause) {
|
||||||
|
case ESP_SLEEP_WAKEUP_TIMER:
|
||||||
// Normal case: timer expired, we should just go back to sleep ASAP
|
// Normal case: timer expired, we should just go back to sleep ASAP
|
||||||
|
|
||||||
setLed(true); // briefly turn on led
|
setLed(true); // briefly turn on led
|
||||||
@@ -53,12 +54,15 @@ static void lsIdle()
|
|||||||
|
|
||||||
secsSlept += sleepTime;
|
secsSlept += sleepTime;
|
||||||
// DEBUG_MSG("sleeping, flash led!\n");
|
// DEBUG_MSG("sleeping, flash led!\n");
|
||||||
}
|
break;
|
||||||
if (wakeCause == ESP_SLEEP_WAKEUP_UART) {
|
|
||||||
|
case ESP_SLEEP_WAKEUP_UART:
|
||||||
// Not currently used (because uart triggers in hw have problems)
|
// Not currently used (because uart triggers in hw have problems)
|
||||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||||
} else {
|
break;
|
||||||
// We woke for some other reason (button press, uart, device interrupt)
|
|
||||||
|
default:
|
||||||
|
// We woke for some other reason (button press, device interrupt)
|
||||||
// uint64_t status = esp_sleep_get_ext1_wakeup_status();
|
// uint64_t status = esp_sleep_get_ext1_wakeup_status();
|
||||||
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
||||||
|
|
||||||
@@ -72,8 +76,10 @@ static void lsIdle()
|
|||||||
powerFSM.trigger(EVENT_PRESS);
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
||||||
|
// we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code
|
||||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so
|
// Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so
|
||||||
@@ -91,12 +97,12 @@ static void lsIdle()
|
|||||||
static void lsExit()
|
static void lsExit()
|
||||||
{
|
{
|
||||||
// setGPSPower(true); // restore GPS power
|
// setGPSPower(true); // restore GPS power
|
||||||
gps->startLock();
|
gps->forceWake(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbEnter()
|
static void nbEnter()
|
||||||
{
|
{
|
||||||
screen.setOn(false);
|
screen->setOn(false);
|
||||||
setBluetoothEnable(false);
|
setBluetoothEnable(false);
|
||||||
|
|
||||||
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
|
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
|
||||||
@@ -105,24 +111,33 @@ static void nbEnter()
|
|||||||
static void darkEnter()
|
static void darkEnter()
|
||||||
{
|
{
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
screen.setOn(false);
|
screen->setOn(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void serialEnter()
|
static void serialEnter()
|
||||||
{
|
{
|
||||||
setBluetoothEnable(false);
|
setBluetoothEnable(false);
|
||||||
screen.setOn(true);
|
screen->setOn(true);
|
||||||
|
screen->print("Using API...\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void powerEnter()
|
static void powerEnter()
|
||||||
{
|
{
|
||||||
screen.setOn(true);
|
screen->setOn(true);
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
|
screen->print("Powered...\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void powerExit()
|
||||||
|
{
|
||||||
|
screen->setOn(true);
|
||||||
|
setBluetoothEnable(true);
|
||||||
|
screen->print("Unpowered...\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onEnter()
|
static void onEnter()
|
||||||
{
|
{
|
||||||
screen.setOn(true);
|
screen->setOn(true);
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
|
|
||||||
static uint32_t lastPingMs;
|
static uint32_t lastPingMs;
|
||||||
@@ -134,13 +149,12 @@ static void onEnter()
|
|||||||
service.sendNetworkPing(displayedNodeNum, true); // Refresh the currently displayed node
|
service.sendNetworkPing(displayedNodeNum, true); // Refresh the currently displayed node
|
||||||
lastPingMs = now;
|
lastPingMs = now;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void wakeForPing() {}
|
}
|
||||||
|
|
||||||
static void screenPress()
|
static void screenPress()
|
||||||
{
|
{
|
||||||
screen.onPress();
|
screen->onPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bootEnter() {}
|
static void bootEnter() {}
|
||||||
@@ -152,20 +166,25 @@ State stateDARK(darkEnter, NULL, NULL, "DARK");
|
|||||||
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
|
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
|
||||||
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
|
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
|
||||||
State stateON(onEnter, NULL, NULL, "ON");
|
State stateON(onEnter, NULL, NULL, "ON");
|
||||||
State statePOWER(powerEnter, NULL, NULL, "POWER");
|
State statePOWER(powerEnter, NULL, powerExit, "POWER");
|
||||||
Fsm powerFSM(&stateBOOT);
|
Fsm powerFSM(&stateBOOT);
|
||||||
|
|
||||||
void PowerFSM_setup()
|
void PowerFSM_setup()
|
||||||
{
|
{
|
||||||
// If we already have AC power go to POWER state after init, otherwise go to ON
|
// If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON
|
||||||
bool hasPower = powerStatus && powerStatus->getHasUSB();
|
// 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);
|
DEBUG_MSG("PowerFSM init, USB power=%d\n", hasPower);
|
||||||
powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout");
|
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
|
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB or dark and
|
||||||
// it handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
// then it handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
|
powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
|
||||||
|
|
||||||
@@ -189,24 +208,31 @@ void PowerFSM_setup()
|
|||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
// if we are a router we don't turn the screen on for these things
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
if (!isRouter) {
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
// show the latest node when we get a new node db update
|
||||||
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||||
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||||
|
powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
// Show the received text message
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||||
|
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
|
||||||
|
}
|
||||||
|
|
||||||
powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||||
powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||||
powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||||
powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
if (!isLowPower) {
|
||||||
powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||||
powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||||
powerFSM.add_transition(&stateON, &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(&statePOWER, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
||||||
powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
||||||
@@ -217,22 +243,28 @@ void PowerFSM_setup()
|
|||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateON, &stateDARK, radioConfig.preferences.screen_on_secs * 1000, NULL, "Screen-on timeout");
|
powerFSM.add_timed_transition(&stateON, &stateDARK, getPref_screen_on_secs() * 1000, NULL, "Screen-on timeout");
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateDARK, &stateNB, radioConfig.preferences.phone_timeout_secs * 1000, NULL, "Phone timeout");
|
// On most boards we use light-sleep to be our main state, but on NRF52 we just stay in DARK
|
||||||
|
State *lowPowerState = &stateLS;
|
||||||
|
|
||||||
#ifndef NRF52_SERIES
|
#ifndef NRF52_SERIES
|
||||||
// We never enter light-sleep state on NRF52 (because the CPU uses so little power normally)
|
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
|
||||||
powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout");
|
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL,
|
lowPowerState = &stateDARK;
|
||||||
"Bluetooth timeout");
|
|
||||||
|
powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout");
|
||||||
|
|
||||||
|
powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout");
|
||||||
|
|
||||||
|
powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL,
|
auto meshSds = getPref_mesh_sds_timeout_secs();
|
||||||
"mesh timeout");
|
if (meshSds != UINT32_MAX)
|
||||||
|
powerFSM.add_timed_transition(lowPowerState, &stateSDS, meshSds * 1000, NULL, "mesh timeout");
|
||||||
// removing for now, because some users don't even have phones
|
// removing for now, because some users don't even have phones
|
||||||
// powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.phone_sds_timeout_sec * 1000, NULL, "phone
|
// powerFSM.add_timed_transition(lowPowerState, &stateSDS, getPref_phone_sds_timeout_sec() * 1000, NULL, "phone
|
||||||
// timeout");
|
// timeout");
|
||||||
|
|
||||||
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
||||||
|
|||||||
@@ -20,5 +20,6 @@
|
|||||||
#define EVENT_POWER_DISCONNECTED 14
|
#define EVENT_POWER_DISCONNECTED 14
|
||||||
|
|
||||||
extern Fsm powerFSM;
|
extern Fsm powerFSM;
|
||||||
|
extern State statePOWER, stateSERIAL;
|
||||||
|
|
||||||
void PowerFSM_setup();
|
void PowerFSM_setup();
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class PowerStatus : public Status
|
|||||||
isCharging = newStatus->isCharging;
|
isCharging = newStatus->isCharging;
|
||||||
}
|
}
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
// DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||||
onNewStatus.notifyObservers(this);
|
onNewStatus.notifyObservers(this);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -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
|
|
||||||
39
src/concurrency/BinarySemaphoreFreeRTOS.cpp
Normal file
39
src/concurrency/BinarySemaphoreFreeRTOS.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include "concurrency/BinarySemaphoreFreeRTOS.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#ifdef HAS_FREE_RTOS
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
BinarySemaphoreFreeRTOS::BinarySemaphoreFreeRTOS()
|
||||||
|
{
|
||||||
|
semaphore = xSemaphoreCreateBinary();
|
||||||
|
}
|
||||||
|
|
||||||
|
BinarySemaphoreFreeRTOS::~BinarySemaphoreFreeRTOS()
|
||||||
|
{
|
||||||
|
vSemaphoreDelete(semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we were interrupted
|
||||||
|
*/
|
||||||
|
bool BinarySemaphoreFreeRTOS::take(uint32_t msec)
|
||||||
|
{
|
||||||
|
return xSemaphoreTake(semaphore, pdMS_TO_TICKS(msec));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BinarySemaphoreFreeRTOS::give()
|
||||||
|
{
|
||||||
|
xSemaphoreGive(semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
IRAM_ATTR void BinarySemaphoreFreeRTOS::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||||
|
{
|
||||||
|
xSemaphoreGiveFromISR(semaphore, pxHigherPriorityTaskWoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
|
|
||||||
|
#endif
|
||||||
31
src/concurrency/BinarySemaphoreFreeRTOS.h
Normal file
31
src/concurrency/BinarySemaphoreFreeRTOS.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "../freertosinc.h"
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef HAS_FREE_RTOS
|
||||||
|
|
||||||
|
class BinarySemaphoreFreeRTOS
|
||||||
|
{
|
||||||
|
SemaphoreHandle_t semaphore;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BinarySemaphoreFreeRTOS();
|
||||||
|
~BinarySemaphoreFreeRTOS();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we timed out
|
||||||
|
*/
|
||||||
|
bool take(uint32_t msec);
|
||||||
|
|
||||||
|
void give();
|
||||||
|
|
||||||
|
void giveFromISR(BaseType_t *pxHigherPriorityTaskWoken);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
36
src/concurrency/BinarySemaphorePosix.cpp
Normal file
36
src/concurrency/BinarySemaphorePosix.cpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#include "concurrency/BinarySemaphorePosix.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#ifndef HAS_FREE_RTOS
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
BinarySemaphorePosix::BinarySemaphorePosix()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
BinarySemaphorePosix::~BinarySemaphorePosix()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we timed out
|
||||||
|
*/
|
||||||
|
bool BinarySemaphorePosix::take(uint32_t msec)
|
||||||
|
{
|
||||||
|
delay(msec); // FIXME
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BinarySemaphorePosix::give()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
|
|
||||||
|
#endif
|
||||||
31
src/concurrency/BinarySemaphorePosix.h
Normal file
31
src/concurrency/BinarySemaphorePosix.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "../freertosinc.h"
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifndef HAS_FREE_RTOS
|
||||||
|
|
||||||
|
class BinarySemaphorePosix
|
||||||
|
{
|
||||||
|
// SemaphoreHandle_t semaphore;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BinarySemaphorePosix();
|
||||||
|
~BinarySemaphorePosix();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we timed out
|
||||||
|
*/
|
||||||
|
bool take(uint32_t msec);
|
||||||
|
|
||||||
|
void give();
|
||||||
|
|
||||||
|
void giveFromISR(BaseType_t *pxHigherPriorityTaskWoken);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
35
src/concurrency/InterruptableDelay.cpp
Normal file
35
src/concurrency/InterruptableDelay.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include "concurrency/InterruptableDelay.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
InterruptableDelay::InterruptableDelay() {}
|
||||||
|
|
||||||
|
InterruptableDelay::~InterruptableDelay() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we were interrupted
|
||||||
|
*/
|
||||||
|
bool InterruptableDelay::delay(uint32_t msec)
|
||||||
|
{
|
||||||
|
// DEBUG_MSG("delay %u ", msec);
|
||||||
|
|
||||||
|
// sem take will return false if we timed out (i.e. were not interrupted)
|
||||||
|
bool r = semaphore.take(msec);
|
||||||
|
|
||||||
|
// DEBUG_MSG("interrupt=%d\n", r);
|
||||||
|
return !r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterruptableDelay::interrupt()
|
||||||
|
{
|
||||||
|
semaphore.give();
|
||||||
|
}
|
||||||
|
|
||||||
|
IRAM_ATTR void InterruptableDelay::interruptFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||||
|
{
|
||||||
|
semaphore.giveFromISR(pxHigherPriorityTaskWoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
42
src/concurrency/InterruptableDelay.h
Normal file
42
src/concurrency/InterruptableDelay.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../freertosinc.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAS_FREE_RTOS
|
||||||
|
#include "concurrency/BinarySemaphoreFreeRTOS.h"
|
||||||
|
#define BinarySemaphore BinarySemaphoreFreeRTOS
|
||||||
|
#else
|
||||||
|
#include "concurrency/BinarySemaphorePosix.h"
|
||||||
|
#define BinarySemaphore BinarySemaphorePosix
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that provides delay(msec) like functionality, but can be interrupted by calling interrupt().
|
||||||
|
*
|
||||||
|
* Useful for they top level loop() delay call to keep the CPU powered down until our next scheduled event or some external event.
|
||||||
|
*
|
||||||
|
* This is implmented for FreeRTOS but should be easy to port to other operating systems.
|
||||||
|
*/
|
||||||
|
class InterruptableDelay
|
||||||
|
{
|
||||||
|
BinarySemaphore semaphore;
|
||||||
|
|
||||||
|
public:
|
||||||
|
InterruptableDelay();
|
||||||
|
~InterruptableDelay();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if we were interrupted
|
||||||
|
*/
|
||||||
|
bool delay(uint32_t msec);
|
||||||
|
|
||||||
|
void interrupt();
|
||||||
|
|
||||||
|
void interruptFromISR(BaseType_t *pxHigherPriorityTaskWoken);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
85
src/concurrency/NotifiedWorkerThread.cpp
Normal file
85
src/concurrency/NotifiedWorkerThread.cpp
Normal file
@@ -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
|
#pragma once
|
||||||
|
|
||||||
#include "FreeRtosNotifiedWorkerThread.h"
|
#include "OSThread.h"
|
||||||
#include "PosixNotifiedWorkerThread.h"
|
|
||||||
|
|
||||||
namespace concurrency
|
namespace concurrency
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifdef HAS_FREE_RTOS
|
/**
|
||||||
typedef FreeRtosNotifiedWorkerThread NotifiedWorkerThread;
|
* @brief A worker thread that waits on a freertos notification
|
||||||
#endif
|
*/
|
||||||
|
class NotifiedWorkerThread : public OSThread
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The notification that was most recently used to wake the thread. Read from runOnce()
|
||||||
|
*/
|
||||||
|
uint32_t notification = 0;
|
||||||
|
|
||||||
#ifdef __unix__
|
public:
|
||||||
typedef PosixNotifiedWorkerThread NotifiedWorkerThread;
|
NotifiedWorkerThread(const char *name) : OSThread(name) {}
|
||||||
#endif
|
|
||||||
|
/**
|
||||||
|
* 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
|
} // namespace concurrency
|
||||||
|
|||||||
79
src/concurrency/OSThread.cpp
Normal file
79
src/concurrency/OSThread.cpp
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#include "OSThread.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Show debugging info for disabled threads
|
||||||
|
bool OSThread::showDisabled;
|
||||||
|
|
||||||
|
/// Show debugging info for threads when we run them
|
||||||
|
bool OSThread::showRun = false;
|
||||||
|
|
||||||
|
/// Show debugging info for threads we decide not to run;
|
||||||
|
bool OSThread::showWaiting = false;
|
||||||
|
|
||||||
|
ThreadController mainController, timerController;
|
||||||
|
InterruptableDelay mainDelay;
|
||||||
|
|
||||||
|
void OSThread::setup()
|
||||||
|
{
|
||||||
|
mainController.ThreadName = "mainController";
|
||||||
|
timerController.ThreadName = "timerController";
|
||||||
|
}
|
||||||
|
|
||||||
|
OSThread::OSThread(const char *_name, uint32_t period, ThreadController *_controller)
|
||||||
|
: Thread(NULL, period), controller(_controller)
|
||||||
|
{
|
||||||
|
ThreadName = _name;
|
||||||
|
|
||||||
|
if (controller)
|
||||||
|
controller->add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
OSThread::~OSThread()
|
||||||
|
{
|
||||||
|
if (controller)
|
||||||
|
controller->remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait a specified number msecs starting from the current time (rather than the last time we were run)
|
||||||
|
*/
|
||||||
|
void OSThread::setIntervalFromNow(unsigned long _interval)
|
||||||
|
{
|
||||||
|
// Save interval
|
||||||
|
interval = _interval;
|
||||||
|
|
||||||
|
// Cache the next run based on the last_run
|
||||||
|
_cached_next_run = millis() + interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OSThread::shouldRun(unsigned long time)
|
||||||
|
{
|
||||||
|
bool r = Thread::shouldRun(time);
|
||||||
|
|
||||||
|
if (showRun && r)
|
||||||
|
DEBUG_MSG("Thread %s: run\n", ThreadName.c_str());
|
||||||
|
|
||||||
|
if (showWaiting && enabled && !r)
|
||||||
|
DEBUG_MSG("Thread %s: wait %lu\n", ThreadName.c_str(), interval);
|
||||||
|
|
||||||
|
if (showDisabled && !enabled)
|
||||||
|
DEBUG_MSG("Thread %s: disabled\n", ThreadName.c_str());
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OSThread::run()
|
||||||
|
{
|
||||||
|
auto newDelay = runOnce();
|
||||||
|
|
||||||
|
runned();
|
||||||
|
|
||||||
|
if (newDelay >= 0)
|
||||||
|
setInterval(newDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
70
src/concurrency/OSThread.h
Normal file
70
src/concurrency/OSThread.h
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "Thread.h"
|
||||||
|
#include "ThreadController.h"
|
||||||
|
#include "concurrency/InterruptableDelay.h"
|
||||||
|
|
||||||
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
|
extern ThreadController mainController, timerController;
|
||||||
|
extern InterruptableDelay mainDelay;
|
||||||
|
|
||||||
|
#define RUN_SAME -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Base threading
|
||||||
|
*
|
||||||
|
* This is a pseudo threading layer that is super easy to port, well suited to our slow network and very ram & power efficient.
|
||||||
|
*
|
||||||
|
* TODO FIXME @geeksville
|
||||||
|
*
|
||||||
|
* move more things into OSThreads
|
||||||
|
* remove lock/lockguard
|
||||||
|
*
|
||||||
|
* move typedQueue into concurrency
|
||||||
|
* remove freertos from typedqueue
|
||||||
|
*/
|
||||||
|
class OSThread : public Thread
|
||||||
|
{
|
||||||
|
ThreadController *controller;
|
||||||
|
|
||||||
|
/// Show debugging info for disabled threads
|
||||||
|
static bool showDisabled;
|
||||||
|
|
||||||
|
/// Show debugging info for threads when we run them
|
||||||
|
static bool showRun;
|
||||||
|
|
||||||
|
/// Show debugging info for threads we decide not to run;
|
||||||
|
static bool showWaiting;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OSThread(const char *name, uint32_t period = 0, ThreadController *controller = &mainController);
|
||||||
|
|
||||||
|
virtual ~OSThread();
|
||||||
|
|
||||||
|
virtual bool shouldRun(unsigned long time);
|
||||||
|
|
||||||
|
static void setup();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait a specified number msecs starting from the current time (rather than the last time we were run)
|
||||||
|
*/
|
||||||
|
void setIntervalFromNow(unsigned long _interval);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* The method that will be called each time our thread gets a chance to run
|
||||||
|
*
|
||||||
|
* Returns desired period for next invocation (or RUN_SAME for no change)
|
||||||
|
*/
|
||||||
|
virtual int32_t runOnce() = 0;
|
||||||
|
|
||||||
|
// Do not override this
|
||||||
|
virtual void run();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace concurrency
|
||||||
@@ -1,26 +1,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "PeriodicTask.h"
|
#include "concurrency/OSThread.h"
|
||||||
|
|
||||||
namespace concurrency {
|
namespace concurrency
|
||||||
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Periodically invoke a callback. This just provides C-style callback conventions
|
* @brief Periodically invoke a callback. This just provides C-style callback conventions
|
||||||
* rather than a virtual function - FIXME, remove?
|
* rather than a virtual function - FIXME, remove?
|
||||||
*/
|
*/
|
||||||
class Periodic : public PeriodicTask
|
class Periodic : public OSThread
|
||||||
{
|
{
|
||||||
uint32_t (*callback)();
|
int32_t (*callback)();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||||
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
|
Periodic(const char *name, int32_t (*_callback)()) : OSThread(name), callback(_callback) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void doTask() {
|
int32_t runOnce() { return callback(); }
|
||||||
uint32_t p = callback();
|
|
||||||
setPeriod(p);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace concurrency
|
} // namespace concurrency
|
||||||
|
|||||||
@@ -1,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
|
|
||||||
@@ -139,6 +139,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
#define SSD1306_ADDRESS 0x3C
|
#define SSD1306_ADDRESS 0x3C
|
||||||
|
#define ST7567_ADDRESS 0x3F
|
||||||
|
|
||||||
// The SH1106 controller is almost, but not quite, the same as SSD1306
|
// 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.
|
// Define this if you know you have that controller or your "SSD1306" misbehaves.
|
||||||
@@ -146,12 +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
|
// 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.
|
// devices. Comment this out to not rotate screen 180 degrees.
|
||||||
#define FLIP_SCREEN_VERTICALLY
|
#define SCREEN_FLIP_VERTICALLY
|
||||||
|
|
||||||
// DEBUG LED
|
// Define if screen should be mirrored left to right
|
||||||
#ifndef LED_INVERTED
|
// #define SCREEN_MIRROR
|
||||||
#define LED_INVERTED 0 // define as 1 if LED is active low (on)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// GPS
|
// GPS
|
||||||
@@ -171,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 38 // The middle button GPIO on the T-Beam
|
||||||
#define BUTTON_PIN_ALT 13 // Alternate GPIO for an external button if needed
|
#define BUTTON_PIN_ALT 13 // Alternate GPIO for an external button if needed
|
||||||
|
|
||||||
|
#define LED_INVERTED 1
|
||||||
|
#define LED_PIN 4 // Newer tbeams (1.1) have an extra led on GPIO4
|
||||||
|
|
||||||
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
|
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
|
||||||
// not found then probe for SX1262
|
// not found then probe for SX1262
|
||||||
#define USE_RF95
|
#define USE_RF95
|
||||||
@@ -192,8 +194,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// code)
|
// code)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Leave undefined to disable our PMU IRQ handler
|
// Leave undefined to disable our PMU IRQ handler. DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts
|
||||||
#define PMU_IRQ 35
|
// and waking from light sleep
|
||||||
|
// #define PMU_IRQ 35
|
||||||
#define AXP192_SLAVE_ADDRESS 0x34
|
#define AXP192_SLAVE_ADDRESS 0x34
|
||||||
|
|
||||||
#elif defined(TBEAM_V07)
|
#elif defined(TBEAM_V07)
|
||||||
@@ -369,6 +372,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// DEBUG LED
|
||||||
|
#ifndef LED_INVERTED
|
||||||
|
#define LED_INVERTED 0 // define as 1 if LED is active low (on)
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_RF95
|
#ifdef USE_RF95
|
||||||
#define RF95_RESET LORA_RESET
|
#define RF95_RESET LORA_RESET
|
||||||
#define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0
|
#define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
|||||||
crc.update(data, len);
|
crc.update(data, len);
|
||||||
Update.write(data, len);
|
Update.write(data, len);
|
||||||
updateActualSize += len;
|
updateActualSize += len;
|
||||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); // Not exactly correct, but we want to force the device to not sleep now
|
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ void WiFiServerAPI::loop()
|
|||||||
|
|
||||||
#define MESHTASTIC_PORTNUM 4403
|
#define MESHTASTIC_PORTNUM 4403
|
||||||
|
|
||||||
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM) {}
|
WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM), concurrency::OSThread("ApiServer") {}
|
||||||
|
|
||||||
void WiFiServerPort::init()
|
void WiFiServerPort::init()
|
||||||
{
|
{
|
||||||
@@ -48,7 +48,7 @@ void WiFiServerPort::init()
|
|||||||
begin();
|
begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiServerPort::loop()
|
int32_t WiFiServerPort::runOnce()
|
||||||
{
|
{
|
||||||
auto client = available();
|
auto client = available();
|
||||||
if (client) {
|
if (client) {
|
||||||
@@ -59,7 +59,10 @@ void WiFiServerPort::loop()
|
|||||||
openAPI = new WiFiServerAPI(client);
|
openAPI = new WiFiServerAPI(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openAPI)
|
if (openAPI) {
|
||||||
// Allow idle processing so the API can read from its incoming stream
|
// Allow idle processing so the API can read from its incoming stream
|
||||||
openAPI->loop();
|
openAPI->loop();
|
||||||
|
return 0; // run fast while our API server is running
|
||||||
|
} else
|
||||||
|
return 100; // only check occasionally for incoming connections
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "StreamAPI.h"
|
#include "StreamAPI.h"
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,7 +28,7 @@ class WiFiServerAPI : public StreamAPI
|
|||||||
/**
|
/**
|
||||||
* Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed
|
* Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed
|
||||||
*/
|
*/
|
||||||
class WiFiServerPort : public WiFiServer
|
class WiFiServerPort : public WiFiServer, private concurrency::OSThread
|
||||||
{
|
{
|
||||||
/** The currently open port
|
/** The currently open port
|
||||||
*
|
*
|
||||||
@@ -41,5 +42,5 @@ class WiFiServerPort : public WiFiServer
|
|||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void loop();
|
int32_t runOnce();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,9 +65,8 @@ void Air530GPS::sendCommand(const char *cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Air530GPS::sleep() {
|
void Air530GPS::sleep() {
|
||||||
|
NMEAGPS::sleep();
|
||||||
#ifdef PIN_GPS_WAKE
|
#ifdef PIN_GPS_WAKE
|
||||||
digitalWrite(PIN_GPS_WAKE, 0);
|
|
||||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
|
||||||
sendCommand("$PGKC105,4");
|
sendCommand("$PGKC105,4");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -76,10 +75,7 @@ void Air530GPS::sleep() {
|
|||||||
void Air530GPS::wake()
|
void Air530GPS::wake()
|
||||||
{
|
{
|
||||||
#if 1
|
#if 1
|
||||||
#ifdef PIN_GPS_WAKE
|
NMEAGPS::wake();
|
||||||
digitalWrite(PIN_GPS_WAKE, 1);
|
|
||||||
pinMode(PIN_GPS_WAKE, OUTPUT);
|
|
||||||
#endif
|
|
||||||
#else
|
#else
|
||||||
// For power testing - keep GPS sleeping forever
|
// For power testing - keep GPS sleeping forever
|
||||||
sleep();
|
sleep();
|
||||||
|
|||||||
301
src/gps/GPS.cpp
301
src/gps/GPS.cpp
@@ -1,14 +1,16 @@
|
|||||||
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include "sleep.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
// If we have a serial GPS port it will not be null
|
// If we have a serial GPS port it will not be null
|
||||||
#ifdef GPS_RX_PIN
|
#ifdef GPS_RX_PIN
|
||||||
HardwareSerial _serial_gps_real(GPS_SERIAL_NUM);
|
HardwareSerial _serial_gps_real(GPS_SERIAL_NUM);
|
||||||
HardwareSerial *GPS::_serial_gps = &_serial_gps_real;
|
HardwareSerial *GPS::_serial_gps = &_serial_gps_real;
|
||||||
#elif defined(NRF52840_XXAA)
|
#elif defined(NRF52840_XXAA) || defined(NRF52833_XXAA)
|
||||||
// Assume NRF52840
|
// Assume NRF52840
|
||||||
HardwareSerial *GPS::_serial_gps = &Serial1;
|
HardwareSerial *GPS::_serial_gps = &Serial1;
|
||||||
#else
|
#else
|
||||||
@@ -21,69 +23,84 @@ uint8_t GPS::i2cAddress = GPS_I2C_ADDRESS;
|
|||||||
uint8_t GPS::i2cAddress = 0;
|
uint8_t GPS::i2cAddress = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep
|
|
||||||
|
|
||||||
GPS *gps;
|
GPS *gps;
|
||||||
|
|
||||||
// stuff that really should be in in the instance instead...
|
bool GPS::setupGPS()
|
||||||
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 (_serial_gps) {
|
||||||
|
#ifdef GPS_RX_PIN
|
||||||
if (!gettimeofday(&tv, NULL)) {
|
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||||
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);
|
|
||||||
#else
|
#else
|
||||||
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
|
_serial_gps->begin(GPS_BAUDRATE);
|
||||||
#endif
|
#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
|
if (n != numSatellites) {
|
||||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
numSatellites = n;
|
||||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
shouldPublish = true;
|
||||||
*/
|
}
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getTime()
|
|
||||||
{
|
|
||||||
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getValidTime()
|
|
||||||
{
|
|
||||||
return timeSetFromGPS ? getTime() : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,14 +108,174 @@ uint32_t getValidTime()
|
|||||||
*
|
*
|
||||||
* calls sleep/wake
|
* calls sleep/wake
|
||||||
*/
|
*/
|
||||||
void GPS::setWantLocation(bool on)
|
void GPS::setAwake(bool on)
|
||||||
{
|
{
|
||||||
if (wantNewLocation != on) {
|
if (!wakeAllowed && on) {
|
||||||
wantNewLocation = on;
|
DEBUG_MSG("Inhibiting because !wakeAllowed\n");
|
||||||
|
on = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAwake != on) {
|
||||||
DEBUG_MSG("WANT GPS=%d\n", on);
|
DEBUG_MSG("WANT GPS=%d\n", on);
|
||||||
if (on)
|
if (on) {
|
||||||
|
lastWakeStartMsec = millis();
|
||||||
wake();
|
wake();
|
||||||
else
|
} else {
|
||||||
|
lastSleepStartMsec = millis();
|
||||||
sleep();
|
sleep();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
isAwake = on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GpsOperation GPS::getGpsOp() const
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
130
src/gps/GPS.h
130
src/gps/GPS.h
@@ -1,36 +1,35 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include "GPSStatus.h"
|
#include "GPSStatus.h"
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "sys/time.h"
|
#include "concurrency/OSThread.h"
|
||||||
|
|
||||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
|
||||||
void perhapsSetRTC(const struct timeval *tv);
|
|
||||||
void perhapsSetRTC(struct tm &t);
|
|
||||||
|
|
||||||
// Generate a string representation of DOP
|
// Generate a string representation of DOP
|
||||||
const char *getDOPString(uint32_t dop);
|
const char *getDOPString(uint32_t dop);
|
||||||
|
|
||||||
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
|
|
||||||
uint32_t getTime();
|
|
||||||
|
|
||||||
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
|
|
||||||
uint32_t getValidTime();
|
|
||||||
|
|
||||||
void readFromRTC();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
|
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
|
||||||
*
|
*
|
||||||
* When new data is available it will notify observers.
|
* When new data is available it will notify observers.
|
||||||
*/
|
*/
|
||||||
class GPS
|
class GPS : private concurrency::OSThread
|
||||||
{
|
{
|
||||||
protected:
|
private:
|
||||||
|
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastWhileActiveMsec = 0;
|
||||||
|
|
||||||
bool hasValidLocation = false; // default to false, until we complete our first read
|
bool hasValidLocation = false; // default to false, until we complete our first read
|
||||||
|
|
||||||
bool wantNewLocation = false; // true if we want a location right now
|
bool isAwake = false; // true if we want a location right now
|
||||||
|
|
||||||
|
bool wakeAllowed = true; // false if gps must be forced to sleep regardless of what time it is
|
||||||
|
|
||||||
|
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
|
||||||
|
|
||||||
|
bool hasGPS = false; // Do we have a GPS we are talking to
|
||||||
|
|
||||||
|
uint8_t numSatellites = 0;
|
||||||
|
|
||||||
|
CallbackObserver<GPS, void *> notifySleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareSleep);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** If !NULL we will use this serial port to construct our GPS */
|
/** If !NULL we will use this serial port to construct our GPS */
|
||||||
@@ -44,11 +43,10 @@ class GPS
|
|||||||
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
|
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
|
||||||
// scaling before use)
|
// scaling before use)
|
||||||
uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
|
uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
|
||||||
uint32_t numSatellites = 0;
|
|
||||||
|
|
||||||
bool isConnected = false; // Do we have a GPS we are talking to
|
GPS() : concurrency::OSThread("GPS") {}
|
||||||
|
|
||||||
virtual ~GPS() {}
|
virtual ~GPS() {} // FIXME, we really should unregister our sleep observer
|
||||||
|
|
||||||
/** We will notify this observable anytime GPS state has changed meaningfully */
|
/** We will notify this observable anytime GPS state has changed meaningfully */
|
||||||
Observable<const meshtastic::GPSStatus *> newStatus;
|
Observable<const meshtastic::GPSStatus *> newStatus;
|
||||||
@@ -56,32 +54,90 @@ class GPS
|
|||||||
/**
|
/**
|
||||||
* Returns true if we succeeded
|
* Returns true if we succeeded
|
||||||
*/
|
*/
|
||||||
virtual bool setup() { return true; }
|
virtual bool setup();
|
||||||
|
|
||||||
/// A loop callback for subclasses that need it. FIXME, instead just block on serial reads
|
|
||||||
virtual void loop() {}
|
|
||||||
|
|
||||||
/// Returns ture if we have acquired GPS lock.
|
/// Returns ture if we have acquired GPS lock.
|
||||||
bool hasLock() const { return hasValidLocation; }
|
bool hasLock() const { return hasValidLocation; }
|
||||||
|
|
||||||
/**
|
/// Return true if we are connected to a GPS
|
||||||
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
bool isConnected() const { return hasGPS; }
|
||||||
*
|
|
||||||
* calls sleep/wake
|
|
||||||
*/
|
|
||||||
void setWantLocation(bool on);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
||||||
* called after the CPU wakes from light-sleep state */
|
* called after the CPU wakes from light-sleep state
|
||||||
virtual void startLock() {}
|
*
|
||||||
|
* Or set to false, to disallow any sort of waking
|
||||||
|
* */
|
||||||
|
void forceWake(bool on);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// If possible force the GPS into sleep/low power mode
|
/// Do gps chipset specific init, return true for success
|
||||||
virtual void sleep() {}
|
virtual bool setupGPS();
|
||||||
|
|
||||||
/// wake the GPS into normal operation mode
|
/// If possible force the GPS into sleep/low power mode
|
||||||
virtual void wake() {}
|
virtual void sleep();
|
||||||
|
|
||||||
|
/// wake the GPS into normal operation mode
|
||||||
|
virtual void wake();
|
||||||
|
|
||||||
|
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||||
|
*
|
||||||
|
* Return true if we received a valid message from the GPS
|
||||||
|
*/
|
||||||
|
virtual bool whileIdle() = 0;
|
||||||
|
|
||||||
|
/** Idle processing while GPS is looking for lock, called once per secondish */
|
||||||
|
virtual void whileActive() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a time
|
||||||
|
*/
|
||||||
|
virtual bool lookForTime() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
virtual bool lookForLocation() = 0;
|
||||||
|
|
||||||
|
/// Record that we have a GPS
|
||||||
|
void setConnected();
|
||||||
|
|
||||||
|
void setNumSatellites(uint8_t n);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||||
|
/// always returns 0 to indicate okay to sleep
|
||||||
|
int prepareSleep(void *unused);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
|
||||||
|
*
|
||||||
|
* calls sleep/wake
|
||||||
|
*/
|
||||||
|
void setAwake(bool on);
|
||||||
|
|
||||||
|
/** Get how long we should stay looking for each aquisition
|
||||||
|
*/
|
||||||
|
uint32_t getWakeTime() const;
|
||||||
|
|
||||||
|
/** Get how long we should sleep between aqusition attempts
|
||||||
|
*/
|
||||||
|
uint32_t getSleepTime() const;
|
||||||
|
|
||||||
|
GpsOperation getGpsOp() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell users we have new GPS readings
|
||||||
|
*/
|
||||||
|
void publishUpdate();
|
||||||
|
|
||||||
|
virtual int32_t runOnce();
|
||||||
};
|
};
|
||||||
|
|
||||||
extern GPS *gps;
|
extern GPS *gps;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "NMEAGPS.h"
|
#include "NMEAGPS.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
|
|
||||||
static int32_t toDegInt(RawDegrees d)
|
static int32_t toDegInt(RawDegrees d)
|
||||||
{
|
{
|
||||||
int32_t degMult = 10000000; // 1e7
|
int32_t degMult = 10000000; // 1e7
|
||||||
@@ -11,8 +11,10 @@ static int32_t toDegInt(RawDegrees d)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NMEAGPS::setup()
|
bool NMEAGPS::setupGPS()
|
||||||
{
|
{
|
||||||
|
GPS::setupGPS();
|
||||||
|
|
||||||
#ifdef PIN_GPS_PPS
|
#ifdef PIN_GPS_PPS
|
||||||
// pulse per second
|
// pulse per second
|
||||||
// FIXME - move into shared GPS code
|
// FIXME - move into shared GPS code
|
||||||
@@ -22,89 +24,89 @@ bool NMEAGPS::setup()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NMEAGPS::loop()
|
/**
|
||||||
|
* 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
|
// First consume any chars that have piled up at the receiver
|
||||||
while (_serial_gps->available() > 0) {
|
while (_serial_gps->available() > 0) {
|
||||||
int c = _serial_gps->read();
|
int c = _serial_gps->read();
|
||||||
// DEBUG_MSG("%c", c);
|
// DEBUG_MSG("%c", c);
|
||||||
bool isValid = reader.encode(c);
|
isValid |= reader.encode(c);
|
||||||
|
|
||||||
// if we have received valid NMEA claim we are connected
|
|
||||||
if (isValid)
|
|
||||||
isConnected = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are overdue for an update, turn on the GPS and at least publish the current status
|
return isValid;
|
||||||
uint32_t now = millis();
|
}
|
||||||
bool mustPublishUpdate = false;
|
|
||||||
if ((now - lastUpdateMsec) > 30 * 1000 && !wantNewLocation) {
|
|
||||||
// Ugly hack for now - limit update checks to once every 30 secs
|
|
||||||
setWantLocation(true);
|
|
||||||
mustPublishUpdate =
|
|
||||||
true; // Even if we don't have an update this time, we at least want to occasionally publish the current state
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only bother looking at GPS state if we are interested in what it has to say
|
|
||||||
if (wantNewLocation) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t fixtype = reader.fixQuality();
|
|
||||||
hasValidLocation = ((fixtype >= 1) && (fixtype <= 5));
|
|
||||||
|
|
||||||
if (reader.location.isUpdated()) {
|
|
||||||
lastUpdateMsec = now;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Once we get a location we no longer desperately want an update
|
|
||||||
setWantLocation(false);
|
|
||||||
}
|
|
||||||
// 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=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7,
|
|
||||||
altitude, dop * 1e-2, heading * 1e-5);
|
|
||||||
mustPublishUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mustPublishUpdate) {
|
|
||||||
// Notify any status instances that are observing us
|
|
||||||
const meshtastic::GPSStatus status =
|
|
||||||
meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
|
|
||||||
newStatus.notifyObservers(&status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "TinyGPS++.h"
|
#include "TinyGPS++.h"
|
||||||
@@ -14,10 +13,29 @@ class NMEAGPS : public GPS
|
|||||||
{
|
{
|
||||||
TinyGPSPlus reader;
|
TinyGPSPlus reader;
|
||||||
|
|
||||||
uint32_t lastUpdateMsec = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual bool setup();
|
virtual bool setupGPS();
|
||||||
|
|
||||||
virtual void loop();
|
protected:
|
||||||
|
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||||
|
*
|
||||||
|
* Return true if we received a valid message from the GPS
|
||||||
|
*/
|
||||||
|
virtual bool whileIdle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a time
|
||||||
|
*/
|
||||||
|
virtual bool lookForTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
virtual bool lookForLocation();
|
||||||
};
|
};
|
||||||
|
|||||||
77
src/gps/RTC.cpp
Normal file
77
src/gps/RTC.cpp
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#include "RTC.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
static RTCQuality currentQuality = RTCQualityNone;
|
||||||
|
|
||||||
|
RTCQuality getRTCQuality()
|
||||||
|
{
|
||||||
|
return currentQuality;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stuff that really should be in in the instance instead...
|
||||||
|
static uint32_t
|
||||||
|
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
|
||||||
|
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
|
||||||
|
|
||||||
|
void readFromRTC()
|
||||||
|
{
|
||||||
|
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
||||||
|
|
||||||
|
if (!gettimeofday(&tv, NULL)) {
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
DEBUG_MSG("Read RTC time as %ld (cur millis %u) quality=%d\n", tv.tv_sec, now, currentQuality);
|
||||||
|
timeStartMsec = now;
|
||||||
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||||
|
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
|
||||||
|
{
|
||||||
|
if (q > currentQuality) {
|
||||||
|
currentQuality = q;
|
||||||
|
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
||||||
|
#ifndef NO_ESP32
|
||||||
|
settimeofday(tv, NULL);
|
||||||
|
#else
|
||||||
|
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
|
||||||
|
#endif
|
||||||
|
readFromRTC();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||||
|
{
|
||||||
|
/* Convert to unix time
|
||||||
|
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||||
|
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||||
|
*/
|
||||||
|
time_t res = mktime(&t);
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = res;
|
||||||
|
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
|
||||||
|
|
||||||
|
// DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
||||||
|
if (t.tm_year < 0 || t.tm_year >= 300) {
|
||||||
|
// DEBUG_MSG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return perhapsSetRTC(q, &tv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getTime()
|
||||||
|
{
|
||||||
|
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getValidTime(RTCQuality minQuality)
|
||||||
|
{
|
||||||
|
return (currentQuality >= minQuality) ? getTime() : 0;
|
||||||
|
}
|
||||||
30
src/gps/RTC.h
Normal file
30
src/gps/RTC.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "sys/time.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
enum RTCQuality {
|
||||||
|
/// We haven't had our RTC set yet
|
||||||
|
RTCQualityNone = 0,
|
||||||
|
|
||||||
|
/// Some other node gave us a time we can use
|
||||||
|
RTCQualityFromNet = 1,
|
||||||
|
|
||||||
|
/// Our time is based on our own GPS
|
||||||
|
RTCQualityGPS = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
RTCQuality getRTCQuality();
|
||||||
|
|
||||||
|
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||||
|
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv);
|
||||||
|
bool perhapsSetRTC(RTCQuality q, struct tm &t);
|
||||||
|
|
||||||
|
/// Return time since 1970 in secs. While quality is RTCQualityNone we will be returning time based at zero
|
||||||
|
uint32_t getTime();
|
||||||
|
|
||||||
|
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
||||||
|
uint32_t getValidTime(RTCQuality minQuality);
|
||||||
|
|
||||||
|
void readFromRTC();
|
||||||
@@ -1,48 +1,37 @@
|
|||||||
#include "UBloxGPS.h"
|
#include "UBloxGPS.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
UBloxGPS::UBloxGPS() : concurrency::PeriodicTask()
|
UBloxGPS::UBloxGPS() {}
|
||||||
{
|
|
||||||
notifySleepObserver.observe(¬ifySleep);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UBloxGPS::tryConnect()
|
bool UBloxGPS::tryConnect()
|
||||||
{
|
{
|
||||||
isConnected = false;
|
bool c = false;
|
||||||
|
|
||||||
if (_serial_gps)
|
if (_serial_gps)
|
||||||
isConnected = ublox.begin(*_serial_gps);
|
c = ublox.begin(*_serial_gps);
|
||||||
|
|
||||||
if (!isConnected && i2cAddress) {
|
if (!c && i2cAddress) {
|
||||||
extern bool neo6M; // Super skanky - if we are talking to the device i2c we assume it is a neo7 on a RAK815, which
|
extern bool neo6M; // Super skanky - if we are talking to the device i2c we assume it is a neo7 on a RAK815, which
|
||||||
// supports the newer API
|
// supports the newer API
|
||||||
neo6M = true;
|
neo6M = true;
|
||||||
|
|
||||||
isConnected = ublox.begin(Wire, i2cAddress);
|
c = ublox.begin(Wire, i2cAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isConnected;
|
if (c)
|
||||||
|
setConnected();
|
||||||
|
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UBloxGPS::setup()
|
bool UBloxGPS::setupGPS()
|
||||||
{
|
{
|
||||||
if (_serial_gps) {
|
GPS::setupGPS();
|
||||||
#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
|
|
||||||
|
|
||||||
|
// uncomment to see debug info
|
||||||
// ublox.enableDebugging(Serial);
|
// ublox.enableDebugging(Serial);
|
||||||
|
|
||||||
// try a second time, the ublox lib serial parsing is buggy?
|
// try a second time, the ublox lib serial parsing is buggy?
|
||||||
@@ -50,14 +39,12 @@ bool UBloxGPS::setup()
|
|||||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||||
delay(500);
|
delay(500);
|
||||||
|
|
||||||
if (isConnected) {
|
if (isConnected()) {
|
||||||
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
|
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
|
||||||
|
|
||||||
if (!setUBXMode())
|
if (!setUBXMode())
|
||||||
recordCriticalError(UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
recordCriticalError(UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
||||||
|
|
||||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@@ -113,96 +100,118 @@ bool UBloxGPS::factoryReset()
|
|||||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||||
delay(500);
|
delay(500);
|
||||||
|
|
||||||
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected);
|
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected());
|
||||||
if (isConnected)
|
if (isConnected())
|
||||||
ok = setUBXMode();
|
ok = setUBXMode();
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
/** Idle processing while GPS is looking for lock */
|
||||||
int UBloxGPS::prepareSleep(void *unused)
|
void UBloxGPS::whileActive()
|
||||||
{
|
{
|
||||||
if (isConnected)
|
ublox.getT(maxWait()); // ask for new time data - hopefully ready when we come back
|
||||||
ublox.powerOff();
|
|
||||||
|
|
||||||
return 0;
|
// Ask for a new position fix - hopefully it will have results ready by next time
|
||||||
|
// the order here is important, because we only check for has latitude when reading
|
||||||
|
ublox.getSIV(maxWait());
|
||||||
|
ublox.getPDOP(maxWait());
|
||||||
|
ublox.getP(maxWait());
|
||||||
|
|
||||||
|
// Update fixtype
|
||||||
|
if (ublox.moduleQueried.fixType) {
|
||||||
|
fixType = ublox.getFixType(0);
|
||||||
|
// DEBUG_MSG("GPS fix type %d, numSats %d\n", fixType, numSatellites);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UBloxGPS::doTask()
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
bool UBloxGPS::lookForTime()
|
||||||
{
|
{
|
||||||
if (isConnected) {
|
if (ublox.moduleQueried.gpsSecond) {
|
||||||
// Consume all characters that have arrived
|
/* Convert to unix time
|
||||||
|
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January
|
||||||
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
|
1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||||
|
*/
|
||||||
// if using i2c or serial look too see if any chars are ready
|
struct tm t;
|
||||||
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
t.tm_sec = ublox.getSecond(0);
|
||||||
|
t.tm_min = ublox.getMinute(0);
|
||||||
// If we don't have a fix (a quick check), don't try waiting for a solution)
|
t.tm_hour = ublox.getHour(0);
|
||||||
// Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions
|
t.tm_mday = ublox.getDay(0);
|
||||||
// turn off for now
|
t.tm_mon = ublox.getMonth(0) - 1;
|
||||||
uint16_t maxWait = i2cAddress ? 300 : 0; // If using i2c we must poll with wait
|
t.tm_year = ublox.getYear(0) - 1900;
|
||||||
fixtype = ublox.getFixType(maxWait);
|
t.tm_isdst = false;
|
||||||
DEBUG_MSG("GPS fix type %d\n", fixtype);
|
perhapsSetRTC(RTCQualityGPS, t);
|
||||||
|
return true;
|
||||||
// DEBUG_MSG("sec %d\n", ublox.getSecond());
|
|
||||||
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
|
|
||||||
|
|
||||||
// any fix that has time
|
|
||||||
|
|
||||||
if (ublox.getT(maxWait)) {
|
|
||||||
/* Convert to unix time
|
|
||||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January
|
|
||||||
1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
|
||||||
*/
|
|
||||||
struct tm t;
|
|
||||||
t.tm_sec = ublox.getSecond(0);
|
|
||||||
t.tm_min = ublox.getMinute(0);
|
|
||||||
t.tm_hour = ublox.getHour(0);
|
|
||||||
t.tm_mday = ublox.getDay(0);
|
|
||||||
t.tm_mon = ublox.getMonth(0) - 1;
|
|
||||||
t.tm_year = ublox.getYear(0) - 1900;
|
|
||||||
t.tm_isdst = false;
|
|
||||||
perhapsSetRTC(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
latitude = ublox.getLatitude(0);
|
|
||||||
longitude = ublox.getLongitude(0);
|
|
||||||
altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters
|
|
||||||
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
|
||||||
heading = ublox.getHeading(0);
|
|
||||||
numSatellites = ublox.getSIV(0);
|
|
||||||
|
|
||||||
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
|
||||||
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
|
||||||
hasValidLocation =
|
|
||||||
(latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000) && (numSatellites > 0);
|
|
||||||
|
|
||||||
// we only notify if position has changed due to a new fix
|
|
||||||
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(maxWait)) // rd fixes only
|
|
||||||
{
|
|
||||||
if (hasValidLocation) {
|
|
||||||
setWantLocation(false);
|
|
||||||
// ublox.powerOff();
|
|
||||||
}
|
|
||||||
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
|
||||||
setWantLocation(true);
|
|
||||||
|
|
||||||
// Notify any status instances that are observing us
|
|
||||||
const meshtastic::GPSStatus status =
|
|
||||||
meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
|
|
||||||
newStatus.notifyObservers(&status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 10s until we have something
|
return false;
|
||||||
// over the serial
|
|
||||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UBloxGPS::startLock()
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
bool UBloxGPS::lookForLocation()
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Looking for GPS lock\n");
|
bool foundLocation = false;
|
||||||
wantNewLocation = true;
|
|
||||||
setPeriod(1);
|
if (ublox.moduleQueried.SIV)
|
||||||
|
setNumSatellites(ublox.getSIV(0));
|
||||||
|
|
||||||
|
if (ublox.moduleQueried.pDOP)
|
||||||
|
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
||||||
|
|
||||||
|
// we only notify if position has changed due to a new fix
|
||||||
|
if ((fixType >= 3 && fixType <= 4)) {
|
||||||
|
if (ublox.moduleQueried.latitude) // rd fixes only
|
||||||
|
{
|
||||||
|
latitude = ublox.getLatitude(0);
|
||||||
|
longitude = ublox.getLongitude(0);
|
||||||
|
altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters
|
||||||
|
|
||||||
|
// Note: heading is only currently implmented in the ublox for the 8m chipset - therefore
|
||||||
|
// don't read it here - it will generate an ignored getPVT command on the 6ms
|
||||||
|
// heading = ublox.getHeading(0);
|
||||||
|
|
||||||
|
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
||||||
|
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
||||||
|
foundLocation = (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UBloxGPS::whileIdle()
|
||||||
|
{
|
||||||
|
// if using i2c or serial look too see if any chars are ready
|
||||||
|
return ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If possible force the GPS into sleep/low power mode
|
||||||
|
/// Note: ublox doesn't need a wake method, because as soon as we send chars to the GPS it will wake up
|
||||||
|
void UBloxGPS::sleep()
|
||||||
|
{
|
||||||
|
// Tell GPS to power down until we send it characters on serial port (we leave vcc connected)
|
||||||
|
ublox.powerOff();
|
||||||
|
// setGPSPower(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBloxGPS::wake()
|
||||||
|
{
|
||||||
|
fixType = 0; // assume we hace no fix yet
|
||||||
|
|
||||||
|
setGPSPower(true);
|
||||||
|
|
||||||
|
// Note: no delay needed because now we leave gps power on always and instead use ublox.powerOff()
|
||||||
|
// Give time for the GPS to boot
|
||||||
|
// delay(200);
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "SparkFun_Ublox_Arduino_Library.h"
|
#include "SparkFun_Ublox_Arduino_Library.h"
|
||||||
@@ -10,27 +9,14 @@
|
|||||||
*
|
*
|
||||||
* When new data is available it will notify observers.
|
* When new data is available it will notify observers.
|
||||||
*/
|
*/
|
||||||
class UBloxGPS : public GPS, public concurrency::PeriodicTask
|
class UBloxGPS : public GPS
|
||||||
{
|
{
|
||||||
SFE_UBLOX_GPS ublox;
|
SFE_UBLOX_GPS ublox;
|
||||||
|
uint8_t fixType = 0;
|
||||||
CallbackObserver<UBloxGPS, void *> notifySleepObserver = CallbackObserver<UBloxGPS, void *>(this, &UBloxGPS::prepareSleep);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UBloxGPS();
|
UBloxGPS();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if we succeeded
|
|
||||||
*/
|
|
||||||
virtual bool setup();
|
|
||||||
|
|
||||||
virtual void doTask();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
|
||||||
* called after the CPU wakes from light-sleep state */
|
|
||||||
virtual void startLock();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset our GPS back to factory settings
|
* Reset our GPS back to factory settings
|
||||||
*
|
*
|
||||||
@@ -38,15 +24,48 @@ class UBloxGPS : public GPS, public concurrency::PeriodicTask
|
|||||||
*/
|
*/
|
||||||
bool factoryReset();
|
bool factoryReset();
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
/**
|
||||||
/// always returns 0 to indicate okay to sleep
|
* Returns true if we succeeded
|
||||||
int prepareSleep(void *unused);
|
*/
|
||||||
|
virtual bool setupGPS();
|
||||||
|
|
||||||
|
/** Subclasses should look for serial rx characters here and feed it to their GPS parser
|
||||||
|
*
|
||||||
|
* Return true if we received a valid message from the GPS
|
||||||
|
*/
|
||||||
|
virtual bool whileIdle();
|
||||||
|
|
||||||
|
/** Idle processing while GPS is looking for lock */
|
||||||
|
virtual void whileActive();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a time
|
||||||
|
*/
|
||||||
|
virtual bool lookForTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
|
* Override this method to check for new locations
|
||||||
|
*
|
||||||
|
* @return true if we've acquired a new location
|
||||||
|
*/
|
||||||
|
virtual bool lookForLocation();
|
||||||
|
|
||||||
|
/// If possible force the GPS into sleep/low power mode
|
||||||
|
virtual void sleep();
|
||||||
|
virtual void wake();
|
||||||
|
|
||||||
|
private:
|
||||||
/// Attempt to connect to our GPS, returns false if no gps is present
|
/// Attempt to connect to our GPS, returns false if no gps is present
|
||||||
bool tryConnect();
|
bool tryConnect();
|
||||||
|
|
||||||
/// Switch to our desired operating mode and save the settings to flash
|
/// Switch to our desired operating mode and save the settings to flash
|
||||||
/// returns true for success
|
/// returns true for success
|
||||||
bool setUBXMode();
|
bool setUBXMode();
|
||||||
|
|
||||||
|
uint16_t maxWait() const { return i2cAddress ? 300 : 0; /*If using i2c we must poll with wait */ }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,15 +46,18 @@ void updateDisplay(uint8_t *blackFrame = framePtr)
|
|||||||
|
|
||||||
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl)
|
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl)
|
||||||
{
|
{
|
||||||
setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will
|
setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT);
|
||||||
// work ie GEOMETRY_RAWMODE
|
// 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
|
// FIXME quick hack to limit drawing to a very slow rate
|
||||||
uint32_t lastDrawMsec;
|
uint32_t lastDrawMsec;
|
||||||
|
|
||||||
// Write the buffer to the display memory
|
/**
|
||||||
void EInkDisplay::display(void)
|
* 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
|
// No need to grab this lock because we are on our own SPI bus
|
||||||
// concurrency::LockGuard g(spiLock);
|
// concurrency::LockGuard g(spiLock);
|
||||||
@@ -62,16 +65,16 @@ void EInkDisplay::display(void)
|
|||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
uint32_t sinceLast = now - lastDrawMsec;
|
uint32_t sinceLast = now - lastDrawMsec;
|
||||||
|
|
||||||
if (framePtr && (sinceLast > 60 * 1000 || lastDrawMsec == 0)) {
|
if (framePtr && (sinceLast > msecLimit || lastDrawMsec == 0)) {
|
||||||
lastDrawMsec = now;
|
lastDrawMsec = now;
|
||||||
|
|
||||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||||
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
||||||
for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) {
|
for (uint8_t y = 0; y < displayHeight; y++) {
|
||||||
for (uint8_t x = 0; x < SCREEN_WIDTH; x++) {
|
for (uint8_t x = 0; x < displayWidth; x++) {
|
||||||
|
|
||||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
||||||
auto b = buffer[x + (y / 8) * SCREEN_WIDTH];
|
auto b = buffer[x + (y / 8) * displayWidth];
|
||||||
auto isset = b & (1 << (y & 7));
|
auto isset = b & (1 << (y & 7));
|
||||||
frame.drawPixel(x, y, isset ? INK : PAPER);
|
frame.drawPixel(x, y, isset ? INK : PAPER);
|
||||||
}
|
}
|
||||||
@@ -83,11 +86,25 @@ void EInkDisplay::display(void)
|
|||||||
updateDisplay(); // Send image to display and refresh
|
updateDisplay(); // Send image to display and refresh
|
||||||
DEBUG_MSG("done\n");
|
DEBUG_MSG("done\n");
|
||||||
|
|
||||||
// Put screen to sleep to save power
|
// Put screen to sleep to save power
|
||||||
ePaper.Sleep();
|
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)
|
// Send a command to the display (low level function)
|
||||||
void EInkDisplay::sendCommand(uint8_t com)
|
void EInkDisplay::sendCommand(uint8_t com)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,15 +14,26 @@
|
|||||||
*/
|
*/
|
||||||
class EInkDisplay : public OLEDDisplay
|
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:
|
public:
|
||||||
/* constructor
|
/* constructor
|
||||||
FIXME - the parameters are not used, just a temporary hack to keep working like the old displays
|
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);
|
EInkDisplay(uint8_t address, int sda, int scl);
|
||||||
|
|
||||||
// Write the buffer to the display memory
|
// Write the buffer to the display memory (for eink we only do this occasionally)
|
||||||
virtual void display(void);
|
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:
|
protected:
|
||||||
// the header size of the buffer used, e.g. for the SPI command header
|
// the header size of the buffer used, e.g. for the SPI command header
|
||||||
virtual int getBufferOffset(void) { return 0; }
|
virtual int getBufferOffset(void) { return 0; }
|
||||||
@@ -33,3 +44,5 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
// Connect to the display
|
// Connect to the display
|
||||||
virtual bool connect();
|
virtual bool connect();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -58,39 +58,73 @@ static char ourId[5];
|
|||||||
static bool heartbeat = false;
|
static bool heartbeat = false;
|
||||||
#endif
|
#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)
|
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
// draw an xbm image.
|
// draw an xbm image.
|
||||||
// Please note that everything that should be transitioned
|
// Please note that everything that should be transitioned
|
||||||
// needs to be drawn relative to x and y
|
// needs to be drawn relative to x and y
|
||||||
display->drawXbm(x + 32, y, icon_width, icon_height, (const uint8_t *)icon_bits);
|
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_16);
|
// draw centered left to right and centered above the one line of app text
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
|
||||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
icon_width, icon_height, (const uint8_t *)icon_bits);
|
||||||
display->setFont(ArialMT_Plain_10);
|
|
||||||
const char *region = xstr(HW_VERSION);
|
display->setFont(FONT_MEDIUM);
|
||||||
if (*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
region += 4;
|
const char *title = "meshtastic.org";
|
||||||
|
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
|
const char *region = myRegion ? myRegion->name : NULL;
|
||||||
|
if (region)
|
||||||
|
display->drawString(x + 0, y + 0, region);
|
||||||
|
|
||||||
char buf[16];
|
char buf[16];
|
||||||
snprintf(buf, sizeof(buf), "%s",
|
snprintf(buf, sizeof(buf), "%s",
|
||||||
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
||||||
display->drawString(SCREEN_WIDTH - 20, 0, buf);
|
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf);
|
||||||
|
screen->forceDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(FONT_MEDIUM);
|
||||||
display->drawString(64 + x, y, "Bluetooth");
|
display->drawString(64 + x, y, "Bluetooth");
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
display->drawString(64 + x, FONT_HEIGHT + y + 2, "Enter this code");
|
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Enter this code");
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_24);
|
display->setFont(FONT_LARGE);
|
||||||
display->drawString(64 + x, 26 + y, btPIN);
|
display->drawString(64 + x, 26 + y, btPIN);
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
char buf[30];
|
char buf[30];
|
||||||
const char *name = "Name: ";
|
const char *name = "Name: ";
|
||||||
strcpy(buf, name);
|
strcpy(buf, name);
|
||||||
@@ -112,10 +146,10 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
|||||||
// with the third parameter you can define the width after which words will
|
// with the third parameter you can define the width after which words will
|
||||||
// be wrapped. Currently only spaces and "-" are allowed for wrapping
|
// be wrapped. Currently only spaces and "-" are allowed for wrapping
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(FONT_MEDIUM);
|
||||||
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
||||||
display->drawString(0 + x, 0 + y, sender);
|
display->drawString(0 + x, 0 + y, sender);
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// the max length of this buffer is much longer than we can possibly print
|
// the max length of this buffer is much longer than we can possibly print
|
||||||
static char tempBuf[96];
|
static char tempBuf[96];
|
||||||
@@ -135,8 +169,8 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
|||||||
int xo = x, yo = y;
|
int xo = x, yo = y;
|
||||||
while (*f) {
|
while (*f) {
|
||||||
display->drawString(xo, yo, *f);
|
display->drawString(xo, yo, *f);
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT_SMALL;
|
||||||
if (yo > SCREEN_HEIGHT - FONT_HEIGHT) {
|
if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) {
|
||||||
xo += SCREEN_WIDTH / 2;
|
xo += SCREEN_WIDTH / 2;
|
||||||
yo = 0;
|
yo = 0;
|
||||||
}
|
}
|
||||||
@@ -162,14 +196,14 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
|||||||
// Wrap to next row, if needed.
|
// Wrap to next row, if needed.
|
||||||
if (++col >= COLUMNS) {
|
if (++col >= COLUMNS) {
|
||||||
xo = x;
|
xo = x;
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT_SMALL;
|
||||||
col = 0;
|
col = 0;
|
||||||
}
|
}
|
||||||
f++;
|
f++;
|
||||||
}
|
}
|
||||||
if (col != 0) {
|
if (col != 0) {
|
||||||
// Include last incomplete line in our total.
|
// Include last incomplete line in our total.
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT_SMALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return yo;
|
return yo;
|
||||||
@@ -236,7 +270,7 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus
|
|||||||
display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
|
display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
|
||||||
|
|
||||||
// Draw the number of satellites
|
// Draw the number of satellites
|
||||||
sprintf(satsString, "%d", gps->getNumSatellites());
|
sprintf(satsString, "%lu", gps->getNumSatellites());
|
||||||
display->drawString(x + 34, y - 2, satsString);
|
display->drawString(x + 34, y - 2, satsString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,7 +510,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
|
|
||||||
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
|
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
@@ -489,11 +523,11 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
uint32_t agoSecs = sinceLastSeen(node);
|
uint32_t agoSecs = sinceLastSeen(node);
|
||||||
static char lastStr[20];
|
static char lastStr[20];
|
||||||
if (agoSecs < 120) // last 2 mins?
|
if (agoSecs < 120) // last 2 mins?
|
||||||
snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs);
|
snprintf(lastStr, sizeof(lastStr), "%lu seconds ago", agoSecs);
|
||||||
else if (agoSecs < 120 * 60) // last 2 hrs
|
else if (agoSecs < 120 * 60) // last 2 hrs
|
||||||
snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60);
|
snprintf(lastStr, sizeof(lastStr), "%lu minutes ago", agoSecs / 60);
|
||||||
else
|
else
|
||||||
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
|
snprintf(lastStr, sizeof(lastStr), "%lu hours ago", agoSecs / 60 / 60);
|
||||||
|
|
||||||
static char distStr[20];
|
static char distStr[20];
|
||||||
strcpy(distStr, "? km"); // might not have location data
|
strcpy(distStr, "? km"); // might not have location data
|
||||||
@@ -531,7 +565,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
// direction to node is unknown so display question mark
|
// direction to node is unknown so display question mark
|
||||||
// Debug info for gps lock errors
|
// Debug info for gps lock errors
|
||||||
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
||||||
display->drawString(compassX - FONT_HEIGHT / 4, compassY - FONT_HEIGHT / 2, "?");
|
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
|
||||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||||
|
|
||||||
// Must be after distStr is populated
|
// Must be after distStr is populated
|
||||||
@@ -561,7 +595,10 @@ void _screen_header()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Screen::Screen(uint8_t address, int sda, int scl) : cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {}
|
Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev)
|
||||||
|
{
|
||||||
|
cmdQueue.setReader(this);
|
||||||
|
}
|
||||||
|
|
||||||
void Screen::handleSetOn(bool on)
|
void Screen::handleSetOn(bool on)
|
||||||
{
|
{
|
||||||
@@ -573,9 +610,12 @@ void Screen::handleSetOn(bool on)
|
|||||||
DEBUG_MSG("Turning on screen\n");
|
DEBUG_MSG("Turning on screen\n");
|
||||||
dispdev.displayOn();
|
dispdev.displayOn();
|
||||||
dispdev.displayOn();
|
dispdev.displayOn();
|
||||||
|
enabled = true;
|
||||||
|
setInterval(0); // Draw ASAP
|
||||||
} else {
|
} else {
|
||||||
DEBUG_MSG("Turning off screen\n");
|
DEBUG_MSG("Turning off screen\n");
|
||||||
dispdev.displayOff();
|
dispdev.displayOff();
|
||||||
|
enabled = false;
|
||||||
}
|
}
|
||||||
screenOn = on;
|
screenOn = on;
|
||||||
}
|
}
|
||||||
@@ -583,17 +623,21 @@ void Screen::handleSetOn(bool on)
|
|||||||
|
|
||||||
void Screen::setup()
|
void Screen::setup()
|
||||||
{
|
{
|
||||||
concurrency::PeriodicTask::setup();
|
|
||||||
|
|
||||||
// We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device
|
// We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device
|
||||||
// is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device.
|
// is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device.
|
||||||
useDisplay = true;
|
useDisplay = true;
|
||||||
|
|
||||||
dispdev.resetOrientation();
|
// I think this is not needed - redundant with ui.init
|
||||||
|
// dispdev.resetOrientation();
|
||||||
|
|
||||||
// Initialising the UI will init the display too.
|
// Initialising the UI will init the display too.
|
||||||
ui.init();
|
ui.init();
|
||||||
ui.setTimePerTransition(300); // msecs
|
|
||||||
|
displayWidth = dispdev.width();
|
||||||
|
displayHeight = dispdev.height();
|
||||||
|
|
||||||
|
ui.setTimePerTransition(SCREEN_TRANSITION_MSECS);
|
||||||
|
|
||||||
ui.setIndicatorPosition(BOTTOM);
|
ui.setIndicatorPosition(BOTTOM);
|
||||||
// Defines where the first frame is located in the bar.
|
// Defines where the first frame is located in the bar.
|
||||||
ui.setIndicatorDirection(LEFT_RIGHT);
|
ui.setIndicatorDirection(LEFT_RIGHT);
|
||||||
@@ -619,7 +663,9 @@ void Screen::setup()
|
|||||||
// Set up a log buffer with 3 lines, 32 chars each.
|
// Set up a log buffer with 3 lines, 32 chars each.
|
||||||
dispdev.setLogBuffer(3, 32);
|
dispdev.setLogBuffer(3, 32);
|
||||||
|
|
||||||
#ifdef FLIP_SCREEN_VERTICALLY
|
#ifdef SCREEN_MIRROR
|
||||||
|
dispdev.mirrorScreen();
|
||||||
|
#elif defined(SCREEN_FLIP_VERTICALLY)
|
||||||
dispdev.flipScreenVertically();
|
dispdev.flipScreenVertically();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -642,14 +688,33 @@ void Screen::setup()
|
|||||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::doTask()
|
void Screen::forceDisplay()
|
||||||
|
{
|
||||||
|
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
|
||||||
|
#ifdef HAS_EINK
|
||||||
|
dispdev.forceDisplay();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Screen::runOnce()
|
||||||
{
|
{
|
||||||
// If we don't have a screen, don't ever spend any CPU for us.
|
// If we don't have a screen, don't ever spend any CPU for us.
|
||||||
if (!useDisplay) {
|
if (!useDisplay) {
|
||||||
setPeriod(0);
|
enabled = false;
|
||||||
return;
|
return RUN_SAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show boot screen for first 3 seconds, then switch to normal operation.
|
||||||
|
static bool showingBootScreen = true;
|
||||||
|
if (showingBootScreen && (millis() > 5000)) {
|
||||||
|
DEBUG_MSG("Done with boot screen...\n");
|
||||||
|
stopBootScreen();
|
||||||
|
showingBootScreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the screen last, after we've figured out what to show.
|
||||||
|
debug_info()->setChannelNameStatus(getChannelName());
|
||||||
|
|
||||||
// Process incoming commands.
|
// Process incoming commands.
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ScreenCmd cmd;
|
ScreenCmd cmd;
|
||||||
@@ -684,10 +749,14 @@ void Screen::doTask()
|
|||||||
|
|
||||||
if (!screenOn) { // If we didn't just wake and the screen is still off, then
|
if (!screenOn) { // If we didn't just wake and the screen is still off, then
|
||||||
// stop updating until it is on again
|
// stop updating until it is on again
|
||||||
setPeriod(0);
|
enabled = false;
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this must be before the frameState == FIXED check, because we always
|
||||||
|
// want to draw at least one FIXED frame before doing forceDisplay
|
||||||
|
ui.update();
|
||||||
|
|
||||||
// Switch to a low framerate (to save CPU) when we are not in transition
|
// Switch to a low framerate (to save CPU) when we are not in transition
|
||||||
// but we should only call setTargetFPS when framestate changes, because
|
// but we should only call setTargetFPS when framestate changes, because
|
||||||
// otherwise that breaks animations.
|
// otherwise that breaks animations.
|
||||||
@@ -696,6 +765,7 @@ void Screen::doTask()
|
|||||||
DEBUG_MSG("Setting idle framerate\n");
|
DEBUG_MSG("Setting idle framerate\n");
|
||||||
targetFramerate = IDLE_FRAMERATE;
|
targetFramerate = IDLE_FRAMERATE;
|
||||||
ui.setTargetFPS(targetFramerate);
|
ui.setTargetFPS(targetFramerate);
|
||||||
|
forceDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
// While showing the bootscreen or Bluetooth pair screen all of our
|
// While showing the bootscreen or Bluetooth pair screen all of our
|
||||||
@@ -704,14 +774,12 @@ void Screen::doTask()
|
|||||||
// standard screen loop handling here
|
// standard screen loop handling here
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.update();
|
|
||||||
|
|
||||||
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
|
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
|
||||||
// ui.getUiState()->frameState); If we are scrolling we need to be called
|
// ui.getUiState()->frameState); If we are scrolling we need to be called
|
||||||
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
|
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
|
||||||
// as fast as we really need so that any rounding errors still result with
|
// as fast as we really need so that any rounding errors still result with
|
||||||
// the correct framerate
|
// the correct framerate
|
||||||
setPeriod(1000 / targetFramerate);
|
return (1000 / targetFramerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
@@ -772,6 +840,8 @@ void Screen::setFrames()
|
|||||||
|
|
||||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
||||||
// just changed)
|
// just changed)
|
||||||
|
|
||||||
|
setFastFramerate(); // Draw ASAP
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
||||||
@@ -781,16 +851,17 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
|||||||
|
|
||||||
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
||||||
|
|
||||||
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
|
snprintf(btPIN, sizeof(btPIN), "%06lu", pin);
|
||||||
|
|
||||||
ui.disableAllIndicators();
|
ui.disableAllIndicators();
|
||||||
ui.setFrames(btFrames, 1);
|
ui.setFrames(btFrames, 1);
|
||||||
|
setFastFramerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::handlePrint(const char *text)
|
void Screen::handlePrint(const char *text)
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Screen: %s", text);
|
DEBUG_MSG("Screen: %s", text);
|
||||||
if (!useDisplay)
|
if (!useDisplay || !showingNormalScreen)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dispdev.print(text);
|
dispdev.print(text);
|
||||||
@@ -801,22 +872,31 @@ void Screen::handleOnPress()
|
|||||||
// If screen was off, just wake it, otherwise advance to next frame
|
// If screen was off, just wake it, otherwise advance to next frame
|
||||||
// If we are in a transition, the press must have bounced, drop it.
|
// If we are in a transition, the press must have bounced, drop it.
|
||||||
if (ui.getUiState()->frameState == FIXED) {
|
if (ui.getUiState()->frameState == FIXED) {
|
||||||
setPeriod(1); // redraw ASAP
|
|
||||||
ui.nextFrame();
|
ui.nextFrame();
|
||||||
|
|
||||||
DEBUG_MSG("Setting fast framerate\n");
|
setFastFramerate();
|
||||||
|
|
||||||
// We are about to start a transition so speed up fps
|
|
||||||
targetFramerate = TRANSITION_FRAMERATE;
|
|
||||||
ui.setTargetFPS(targetFramerate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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)
|
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
displayedNodeNum = 0; // Not currently showing a node pane
|
displayedNodeNum = 0; // Not currently showing a node pane
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
@@ -838,13 +918,13 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
||||||
|
|
||||||
// Draw the channel name
|
// Draw the channel name
|
||||||
display->drawString(x, y + FONT_HEIGHT, channelStr);
|
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
|
||||||
// Draw our hardware ID to assist with bluetooth pairing
|
// Draw our hardware ID to assist with bluetooth pairing
|
||||||
display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT, 8, 8, imgInfo);
|
display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo);
|
||||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT, ourId);
|
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId);
|
||||||
|
|
||||||
// Draw any log messages
|
// Draw any log messages
|
||||||
display->drawLogBuffer(x, y + (FONT_HEIGHT * 2));
|
display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2));
|
||||||
|
|
||||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||||
#ifdef SHOW_REDRAWS
|
#ifdef SHOW_REDRAWS
|
||||||
@@ -863,7 +943,7 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
|||||||
|
|
||||||
displayedNodeNum = 0; // Not currently showing a node pane
|
displayedNodeNum = 0; // Not currently showing a node pane
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
@@ -894,86 +974,90 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
|||||||
|
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
if (radioConfig.preferences.wifi_ap_mode) {
|
if (radioConfig.preferences.wifi_ap_mode) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "IP: " + String(WiFi.softAPIP().toString().c_str()));
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.softAPIP().toString().c_str()));
|
||||||
} else {
|
} else {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "IP: " + String(WiFi.localIP().toString().c_str()));
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str()));
|
||||||
}
|
}
|
||||||
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
|
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "SSID Not Found");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found");
|
||||||
} else if (WiFi.status() == WL_CONNECTION_LOST) {
|
} else if (WiFi.status() == WL_CONNECTION_LOST) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Connection Lost");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost");
|
||||||
} else if (WiFi.status() == WL_CONNECT_FAILED) {
|
} else if (WiFi.status() == WL_CONNECT_FAILED) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Connection Failed");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed");
|
||||||
//} else if (WiFi.status() == WL_DISCONNECTED) {
|
//} else if (WiFi.status() == WL_DISCONNECTED) {
|
||||||
// display->drawString(x, y + FONT_HEIGHT * 1, "Disconnected");
|
// display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disconnected");
|
||||||
} else if (WiFi.status() == WL_IDLE_STATUS) {
|
} else if (WiFi.status() == WL_IDLE_STATUS) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Idle ... Reconnecting");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting");
|
||||||
} else {
|
} else {
|
||||||
// Codes:
|
// Codes:
|
||||||
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
|
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
|
||||||
if (getWifiDisconnectReason() == 2) {
|
if (getWifiDisconnectReason() == 2) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Authentication Invalid");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Authentication Invalid");
|
||||||
} else if (getWifiDisconnectReason() == 3) {
|
} else if (getWifiDisconnectReason() == 3) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "De-authenticated");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "De-authenticated");
|
||||||
} else if (getWifiDisconnectReason() == 4) {
|
} else if (getWifiDisconnectReason() == 4) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Disassociated Expired");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disassociated Expired");
|
||||||
} else if (getWifiDisconnectReason() == 5) {
|
} else if (getWifiDisconnectReason() == 5) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "AP - Too Many Clients");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP - Too Many Clients");
|
||||||
} else if (getWifiDisconnectReason() == 6) {
|
} else if (getWifiDisconnectReason() == 6) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "NOT_AUTHED");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "NOT_AUTHED");
|
||||||
} else if (getWifiDisconnectReason() == 7) {
|
} else if (getWifiDisconnectReason() == 7) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "NOT_ASSOCED");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "NOT_ASSOCED");
|
||||||
} else if (getWifiDisconnectReason() == 8) {
|
} else if (getWifiDisconnectReason() == 8) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Disassociated");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Disassociated");
|
||||||
} else if (getWifiDisconnectReason() == 9) {
|
} else if (getWifiDisconnectReason() == 9) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "ASSOC_NOT_AUTHED");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "ASSOC_NOT_AUTHED");
|
||||||
} else if (getWifiDisconnectReason() == 10) {
|
} else if (getWifiDisconnectReason() == 10) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "DISASSOC_PWRCAP_BAD");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "DISASSOC_PWRCAP_BAD");
|
||||||
} else if (getWifiDisconnectReason() == 11) {
|
} else if (getWifiDisconnectReason() == 11) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "DISASSOC_SUPCHAN_BAD");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "DISASSOC_SUPCHAN_BAD");
|
||||||
} else if (getWifiDisconnectReason() == 13) {
|
} else if (getWifiDisconnectReason() == 13) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "IE_INVALID");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IE_INVALID");
|
||||||
} else if (getWifiDisconnectReason() == 14) {
|
} else if (getWifiDisconnectReason() == 14) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "MIC_FAILURE");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "MIC_FAILURE");
|
||||||
} else if (getWifiDisconnectReason() == 15) {
|
} else if (getWifiDisconnectReason() == 15) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "AP Handshake Timeout");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP Handshake Timeout");
|
||||||
} else if (getWifiDisconnectReason() == 16) {
|
} else if (getWifiDisconnectReason() == 16) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "GROUP_KEY_UPDATE_TIMEOUT");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "GROUP_KEY_UPDATE_TIMEOUT");
|
||||||
} else if (getWifiDisconnectReason() == 17) {
|
} else if (getWifiDisconnectReason() == 17) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "IE_IN_4WAY_DIFFERS");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IE_IN_4WAY_DIFFERS");
|
||||||
} else if (getWifiDisconnectReason() == 18) {
|
} else if (getWifiDisconnectReason() == 18) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Invalid Group Cipher");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Invalid Group Cipher");
|
||||||
} else if (getWifiDisconnectReason() == 19) {
|
} else if (getWifiDisconnectReason() == 19) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Invalid Pairwise Cipher");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Invalid Pairwise Cipher");
|
||||||
} else if (getWifiDisconnectReason() == 20) {
|
} else if (getWifiDisconnectReason() == 20) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "AKMP_INVALID");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AKMP_INVALID");
|
||||||
} else if (getWifiDisconnectReason() == 21) {
|
} else if (getWifiDisconnectReason() == 21) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "UNSUPP_RSN_IE_VERSION");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "UNSUPP_RSN_IE_VERSION");
|
||||||
} else if (getWifiDisconnectReason() == 22) {
|
} else if (getWifiDisconnectReason() == 22) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "INVALID_RSN_IE_CAP");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "INVALID_RSN_IE_CAP");
|
||||||
} else if (getWifiDisconnectReason() == 23) {
|
} else if (getWifiDisconnectReason() == 23) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "802_1X_AUTH_FAILED");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "802_1X_AUTH_FAILED");
|
||||||
} else if (getWifiDisconnectReason() == 24) {
|
} else if (getWifiDisconnectReason() == 24) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "CIPHER_SUITE_REJECTED");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "CIPHER_SUITE_REJECTED");
|
||||||
} else if (getWifiDisconnectReason() == 200) {
|
} else if (getWifiDisconnectReason() == 200) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "BEACON_TIMEOUT");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "BEACON_TIMEOUT");
|
||||||
} else if (getWifiDisconnectReason() == 201) {
|
} else if (getWifiDisconnectReason() == 201) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "AP Not Found");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AP Not Found");
|
||||||
} else if (getWifiDisconnectReason() == 202) {
|
} else if (getWifiDisconnectReason() == 202) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "AUTH_FAIL");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "AUTH_FAIL");
|
||||||
} else if (getWifiDisconnectReason() == 203) {
|
} else if (getWifiDisconnectReason() == 203) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "ASSOC_FAIL");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "ASSOC_FAIL");
|
||||||
} else if (getWifiDisconnectReason() == 204) {
|
} else if (getWifiDisconnectReason() == 204) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "HANDSHAKE_TIMEOUT");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "HANDSHAKE_TIMEOUT");
|
||||||
} else if (getWifiDisconnectReason() == 205) {
|
} else if (getWifiDisconnectReason() == 205) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Connection Failed");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed");
|
||||||
} else {
|
} else {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1, "Unknown Status");
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unknown Status");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
display->drawString(x, y + FONT_HEIGHT * 2, "SSID: " + String(wifiName));
|
if ((millis() / 1000) % 2) {
|
||||||
display->drawString(x, y + FONT_HEIGHT * 3, "PWD: " + String(wifiPsw));
|
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 */
|
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||||
#ifdef SHOW_REDRAWS
|
#ifdef SHOW_REDRAWS
|
||||||
@@ -988,7 +1072,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
{
|
{
|
||||||
displayedNodeNum = 0; // Not currently showing a node pane
|
displayedNodeNum = 0; // Not currently showing a node pane
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
@@ -1022,15 +1106,21 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
minutes %= 60;
|
minutes %= 60;
|
||||||
hours %= 24;
|
hours %= 24;
|
||||||
|
|
||||||
display->drawString(x, y + FONT_HEIGHT * 1,
|
display->drawString(x, y + FONT_HEIGHT_SMALL * 1,
|
||||||
String(days) + "d " + (hours < 10 ? "0" : "") + String(hours) + ":" + (minutes < 10 ? "0" : "") +
|
String(days) + "d " + (hours < 10 ? "0" : "") + String(hours) + ":" + (minutes < 10 ? "0" : "") +
|
||||||
String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds));
|
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
|
// Line 3
|
||||||
drawGPSAltitude(display, x, y + FONT_HEIGHT * 2, gpsStatus);
|
drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||||||
|
|
||||||
// Line 4
|
// Line 4
|
||||||
drawGPScoordinates(display, x, y + FONT_HEIGHT * 3, gpsStatus);
|
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
|
||||||
|
|
||||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||||
#ifdef SHOW_REDRAWS
|
#ifdef SHOW_REDRAWS
|
||||||
@@ -1059,10 +1149,8 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
|||||||
// DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
// DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
||||||
switch (arg->getStatusType()) {
|
switch (arg->getStatusType()) {
|
||||||
case STATUS_TYPE_NODE:
|
case STATUS_TYPE_NODE:
|
||||||
if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
|
if (showingNormalScreen && (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal())) {
|
||||||
setFrames(); // Regen the list of screens
|
setFrames(); // Regen the list of screens
|
||||||
prevFrame = -1; // Force a GUI update
|
|
||||||
setPeriod(1); // Update the screen right away
|
|
||||||
}
|
}
|
||||||
nodeDB.updateGUI = false;
|
nodeDB.updateGUI = false;
|
||||||
nodeDB.updateTextMessage = false;
|
nodeDB.updateTextMessage = false;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#ifdef USE_SH1106
|
#ifdef USE_SH1106
|
||||||
#include <SH1106Wire.h>
|
#include <SH1106Wire.h>
|
||||||
|
#elif defined(USE_ST7567)
|
||||||
|
#include <ST7567Wire.h>
|
||||||
#else
|
#else
|
||||||
#include <SSD1306Wire.h>
|
#include <SSD1306Wire.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -15,10 +17,15 @@
|
|||||||
#include "TypedQueue.h"
|
#include "TypedQueue.h"
|
||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
#include "concurrency/LockGuard.h"
|
#include "concurrency/LockGuard.h"
|
||||||
#include "concurrency/PeriodicTask.h"
|
#include "concurrency/OSThread.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
// 0 to 255, though particular variants might define different defaults
|
||||||
|
#ifndef BRIGHTNESS_DEFAULT
|
||||||
|
#define BRIGHTNESS_DEFAULT 150
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -62,7 +69,7 @@ class DebugInfo
|
|||||||
* multiple times simultaneously. All state-changing calls are queued and executed
|
* multiple times simultaneously. All state-changing calls are queued and executed
|
||||||
* when the main loop calls us.
|
* when the main loop calls us.
|
||||||
*/
|
*/
|
||||||
class Screen : public concurrency::PeriodicTask
|
class Screen : public concurrency::OSThread
|
||||||
{
|
{
|
||||||
CallbackObserver<Screen, const meshtastic::Status *> powerStatusObserver =
|
CallbackObserver<Screen, const meshtastic::Status *> powerStatusObserver =
|
||||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||||
@@ -97,7 +104,7 @@ class Screen : public concurrency::PeriodicTask
|
|||||||
|
|
||||||
// Implementation to Adjust Brightness
|
// Implementation to Adjust Brightness
|
||||||
void adjustBrightness();
|
void adjustBrightness();
|
||||||
uint8_t brightness = 150;
|
uint8_t brightness = BRIGHTNESS_DEFAULT;
|
||||||
|
|
||||||
/// Starts showing the Bluetooth PIN screen.
|
/// Starts showing the Bluetooth PIN screen.
|
||||||
//
|
//
|
||||||
@@ -180,11 +187,14 @@ class Screen : public concurrency::PeriodicTask
|
|||||||
|
|
||||||
int handleStatusUpdate(const meshtastic::Status *arg);
|
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||||
|
|
||||||
|
/// Used to force (super slow) eink displays to draw critical frames
|
||||||
|
void forceDisplay();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Updates the UI.
|
/// Updates the UI.
|
||||||
//
|
//
|
||||||
// Called periodically from the main loop.
|
// Called periodically from the main loop.
|
||||||
void doTask() final;
|
int32_t runOnce() final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ScreenCmd {
|
struct ScreenCmd {
|
||||||
@@ -202,7 +212,7 @@ class Screen : public concurrency::PeriodicTask
|
|||||||
return true; // claim success if our display is not in use
|
return true; // claim success if our display is not in use
|
||||||
else {
|
else {
|
||||||
bool success = cmdQueue.enqueue(cmd, 0);
|
bool success = cmdQueue.enqueue(cmd, 0);
|
||||||
setPeriod(1); // handle ASAP
|
enabled = true; // handle ASAP (we are the registered reader for cmdQueue, but might have been disabled)
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,6 +226,9 @@ class Screen : public concurrency::PeriodicTask
|
|||||||
/// Rebuilds our list of frames (screens) to default ones.
|
/// Rebuilds our list of frames (screens) to default ones.
|
||||||
void setFrames();
|
void setFrames();
|
||||||
|
|
||||||
|
/// Try to start drawing ASAP
|
||||||
|
void setFastFramerate();
|
||||||
|
|
||||||
/// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame.
|
/// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame.
|
||||||
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
@@ -244,6 +257,8 @@ class Screen : public concurrency::PeriodicTask
|
|||||||
EInkDisplay dispdev;
|
EInkDisplay dispdev;
|
||||||
#elif defined(USE_SH1106)
|
#elif defined(USE_SH1106)
|
||||||
SH1106Wire dispdev;
|
SH1106Wire dispdev;
|
||||||
|
#elif defined(USE_ST7567)
|
||||||
|
ST7567Wire dispdev;
|
||||||
#else
|
#else
|
||||||
SSD1306Wire dispdev;
|
SSD1306Wire dispdev;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ static TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.
|
|||||||
|
|
||||||
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl)
|
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl)
|
||||||
{
|
{
|
||||||
setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will
|
setGeometry(GEOMETRY_RAWMODE, 160, 80);
|
||||||
// work ie GEOMETRY_RAWMODE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the buffer to the display memory
|
// Write the buffer to the display memory
|
||||||
@@ -22,11 +21,11 @@ void TFTDisplay::display(void)
|
|||||||
|
|
||||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||||
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
||||||
for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) {
|
for (uint8_t y = 0; y < displayHeight; y++) {
|
||||||
for (uint8_t x = 0; x < SCREEN_WIDTH; x++) {
|
for (uint8_t x = 0; x < displayWidth; x++) {
|
||||||
|
|
||||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
||||||
auto b = buffer[x + (y / 8) * SCREEN_WIDTH];
|
auto b = buffer[x + (y / 8) * displayWidth];
|
||||||
auto isset = b & (1 << (y & 7));
|
auto isset = b & (1 << (y & 7));
|
||||||
tft.drawPixel(x, y, isset ? TFT_WHITE : TFT_BLACK);
|
tft.drawPixel(x, y, isset ? TFT_WHITE : TFT_BLACK);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,7 @@
|
|||||||
|
|
||||||
#include "fonts.h"
|
#include "fonts.h"
|
||||||
|
|
||||||
#define FONT_HEIGHT 14 // actually 13 for "Arial 10" but want a little extra space
|
|
||||||
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
|
||||||
// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
|
// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
|
||||||
#define SCREEN_WIDTH 128
|
|
||||||
#define SCREEN_HEIGHT 64
|
|
||||||
#define TRANSITION_FRAMERATE 30 // fps
|
|
||||||
#define IDLE_FRAMERATE 1 // in fps
|
#define IDLE_FRAMERATE 1 // in fps
|
||||||
#define COMPASS_DIAM 44
|
#define COMPASS_DIAM 44
|
||||||
|
|
||||||
|
|||||||
309
src/main.cpp
309
src/main.cpp
@@ -1,40 +1,20 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Main module
|
|
||||||
|
|
||||||
# Modified by Kyle T. Gabriel to fix issue with incorrect GPS data for TTNMapper
|
|
||||||
|
|
||||||
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
#include "Air530GPS.h"
|
||||||
#include "MeshRadio.h"
|
#include "MeshRadio.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "Air530GPS.h"
|
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "UBloxGPS.h"
|
#include "UBloxGPS.h"
|
||||||
#include "concurrency/Periodic.h"
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
// #include "rom/rtc.h"
|
// #include "rom/rtc.h"
|
||||||
#include "DSRRouter.h"
|
#include "DSRRouter.h"
|
||||||
// #include "debug.h"
|
// #include "debug.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
|
#include "concurrency/Periodic.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "meshwifi/meshhttp.h"
|
#include "meshwifi/meshhttp.h"
|
||||||
@@ -56,8 +36,10 @@
|
|||||||
#include "variant.h"
|
#include "variant.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
using namespace concurrency;
|
||||||
|
|
||||||
// We always create a screen object, but we only init it if we find the hardware
|
// We always create a screen object, but we only init it if we find the hardware
|
||||||
graphics::Screen screen(SSD1306_ADDRESS);
|
graphics::Screen *screen;
|
||||||
|
|
||||||
// Global power status
|
// Global power status
|
||||||
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
|
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
|
||||||
@@ -68,11 +50,12 @@ meshtastic::GPSStatus *gpsStatus = new meshtastic::GPSStatus();
|
|||||||
// Global Node status
|
// Global Node status
|
||||||
meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
|
meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
|
||||||
|
|
||||||
bool ssd1306_found;
|
/// The I2C address of our display (if found)
|
||||||
|
uint8_t screen_found;
|
||||||
|
|
||||||
bool axp192_found;
|
bool axp192_found;
|
||||||
|
|
||||||
DSRRouter realRouter;
|
Router *router = NULL; // Users of router don't care what sort of subclass implements that API
|
||||||
Router &router = realRouter; // Users of router don't care what sort of subclass implements that API
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Application
|
// Application
|
||||||
@@ -91,9 +74,13 @@ void scanI2Cdevice(void)
|
|||||||
nDevices++;
|
nDevices++;
|
||||||
|
|
||||||
if (addr == SSD1306_ADDRESS) {
|
if (addr == SSD1306_ADDRESS) {
|
||||||
ssd1306_found = true;
|
screen_found = addr;
|
||||||
DEBUG_MSG("ssd1306 display found\n");
|
DEBUG_MSG("ssd1306 display found\n");
|
||||||
}
|
}
|
||||||
|
if (addr == ST7567_ADDRESS) {
|
||||||
|
screen_found = addr;
|
||||||
|
DEBUG_MSG("st7567 display found\n");
|
||||||
|
}
|
||||||
#ifdef AXP192_SLAVE_ADDRESS
|
#ifdef AXP192_SLAVE_ADDRESS
|
||||||
if (addr == AXP192_SLAVE_ADDRESS) {
|
if (addr == AXP192_SLAVE_ADDRESS) {
|
||||||
axp192_found = true;
|
axp192_found = true;
|
||||||
@@ -122,7 +109,7 @@ const char *getDeviceName()
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t ledBlinker()
|
static int32_t ledBlinker()
|
||||||
{
|
{
|
||||||
static bool ledOn;
|
static bool ledOn;
|
||||||
ledOn ^= 1;
|
ledOn ^= 1;
|
||||||
@@ -130,32 +117,119 @@ static uint32_t ledBlinker()
|
|||||||
setLed(ledOn);
|
setLed(ledOn);
|
||||||
|
|
||||||
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
||||||
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 2 : 1000);
|
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
concurrency::Periodic ledPeriodic(ledBlinker);
|
/// Wrapper to convert our powerFSM stuff into a 'thread'
|
||||||
|
class PowerFSMThread : public OSThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||||
|
PowerFSMThread() : OSThread("PowerFSM") {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int32_t runOnce()
|
||||||
|
{
|
||||||
|
powerFSM.run_machine();
|
||||||
|
|
||||||
|
/// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake
|
||||||
|
/// cpu for serial rx - FIXME)
|
||||||
|
auto state = powerFSM.getState();
|
||||||
|
canSleep = (state != &statePOWER) && (state != &stateSERIAL);
|
||||||
|
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch a GPIO and if we get an IRQ, wake the main thread.
|
||||||
|
* Use to add wake on button press
|
||||||
|
*/
|
||||||
|
void wakeOnIrq(int irq, int mode)
|
||||||
|
{
|
||||||
|
attachInterrupt(
|
||||||
|
irq,
|
||||||
|
[] {
|
||||||
|
BaseType_t higherWake = 0;
|
||||||
|
mainDelay.interruptFromISR(&higherWake);
|
||||||
|
},
|
||||||
|
FALLING);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ButtonThread : public OSThread
|
||||||
|
{
|
||||||
// Prepare for button presses
|
// Prepare for button presses
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
OneButton userButton;
|
OneButton userButton;
|
||||||
#endif
|
#endif
|
||||||
#ifdef BUTTON_PIN_ALT
|
#ifdef BUTTON_PIN_ALT
|
||||||
OneButton userButtonAlt;
|
OneButton userButtonAlt;
|
||||||
#endif
|
#endif
|
||||||
void userButtonPressed()
|
|
||||||
{
|
public:
|
||||||
powerFSM.trigger(EVENT_PRESS);
|
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||||
}
|
ButtonThread() : OSThread("Button")
|
||||||
void userButtonPressedLong()
|
{
|
||||||
{
|
#ifdef BUTTON_PIN
|
||||||
screen.adjustBrightness();
|
userButton = OneButton(BUTTON_PIN, true, true);
|
||||||
}
|
userButton.attachClick(userButtonPressed);
|
||||||
void userButtonDoublePressed()
|
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()
|
||||||
{
|
{
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
disablePin();
|
disablePin();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static Periodic *ledPeriodic;
|
||||||
|
static OSThread *powerFSMthread, *buttonThread;
|
||||||
|
|
||||||
|
RadioInterface *rIf = NULL;
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
@@ -180,43 +254,44 @@ void setup()
|
|||||||
digitalWrite(RESET_OLED, 1);
|
digitalWrite(RESET_OLED, 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
OSThread::setup();
|
||||||
|
|
||||||
|
ledPeriodic = new Periodic("Blink", ledBlinker);
|
||||||
|
|
||||||
|
router = new DSRRouter();
|
||||||
|
|
||||||
#ifdef I2C_SDA
|
#ifdef I2C_SDA
|
||||||
Wire.begin(I2C_SDA, I2C_SCL);
|
Wire.begin(I2C_SDA, I2C_SCL);
|
||||||
#else
|
#else
|
||||||
Wire.begin();
|
Wire.begin();
|
||||||
#endif
|
#endif
|
||||||
// i2c still busted on new board
|
|
||||||
#ifndef ARDUINO_NRF52840_PPR
|
#ifdef PIN_LCD_RESET
|
||||||
scanI2Cdevice();
|
// 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
|
#endif
|
||||||
|
|
||||||
|
scanI2Cdevice();
|
||||||
|
|
||||||
// Buttons & LED
|
// Buttons & LED
|
||||||
#ifdef BUTTON_PIN
|
buttonThread = new ButtonThread();
|
||||||
userButton = OneButton(BUTTON_PIN, true, true);
|
|
||||||
userButton.attachClick(userButtonPressed);
|
|
||||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
|
||||||
userButton.attachDoubleClick(userButtonDoublePressed);
|
|
||||||
#endif
|
|
||||||
#ifdef BUTTON_PIN_ALT
|
|
||||||
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
|
|
||||||
userButtonAlt.attachClick(userButtonPressed);
|
|
||||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
|
||||||
userButton.attachDoubleClick(userButtonDoublePressed);
|
|
||||||
#endif
|
|
||||||
#ifdef LED_PIN
|
#ifdef LED_PIN
|
||||||
pinMode(LED_PIN, OUTPUT);
|
pinMode(LED_PIN, OUTPUT);
|
||||||
digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
|
digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ledPeriodic.setup();
|
|
||||||
|
|
||||||
// Hello
|
// Hello
|
||||||
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", optstr(APP_VERSION), optstr(HW_VERSION));
|
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", optstr(APP_VERSION), optstr(HW_VERSION));
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
// Don't init display if we don't have one or we are waking headless due to a timer event
|
// 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)
|
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();
|
esp32Setup();
|
||||||
#endif
|
#endif
|
||||||
@@ -242,54 +317,58 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Initialize the screen first so we can show the logo while we start up everything else.
|
// Initialize the screen first so we can show the logo while we start up everything else.
|
||||||
#if defined(ST7735_CS) || defined(HAS_EINK)
|
screen = new graphics::Screen(screen_found);
|
||||||
screen.setup();
|
|
||||||
#else
|
|
||||||
if (ssd1306_found)
|
|
||||||
screen.setup();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
screen.print("Started...\n");
|
|
||||||
|
|
||||||
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
|
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
|
||||||
|
|
||||||
// If we know we have a L80 GPS, don't try UBLOX
|
// If we don't have bidirectional comms, we can't even try talking to UBLOX
|
||||||
#ifndef L80_RESET
|
UBloxGPS *ublox = NULL;
|
||||||
|
#ifdef GPS_TX_PIN
|
||||||
// Init GPS - first try ublox
|
// Init GPS - first try ublox
|
||||||
auto ublox = new UBloxGPS();
|
ublox = new UBloxGPS();
|
||||||
gps = ublox;
|
gps = ublox;
|
||||||
if (!gps->setup()) {
|
if (!gps->setup()) {
|
||||||
DEBUG_MSG("ERROR: No UBLOX GPS found\n");
|
DEBUG_MSG("ERROR: No UBLOX GPS found\n");
|
||||||
|
|
||||||
delete ublox;
|
delete ublox;
|
||||||
gps = ublox = NULL;
|
gps = ublox = NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (GPS::_serial_gps) {
|
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
|
// 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.
|
// assume NMEA at 9600 baud.
|
||||||
// dumb NMEA access only work for serial GPSes)
|
// dumb NMEA access only work for serial GPSes)
|
||||||
DEBUG_MSG("Hoping that NMEA might work\n");
|
DEBUG_MSG("Hoping that NMEA might work\n");
|
||||||
|
|
||||||
#ifdef HAS_AIR530_GPS
|
#ifdef HAS_AIR530_GPS
|
||||||
gps = new Air530GPS();
|
gps = new Air530GPS();
|
||||||
#else
|
#else
|
||||||
gps = new NMEAGPS();
|
gps = new NMEAGPS();
|
||||||
#endif
|
#endif
|
||||||
gps->setup();
|
gps->setup();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
gps = new NMEAGPS();
|
|
||||||
gps->setup();
|
|
||||||
#endif
|
|
||||||
if (gps)
|
if (gps)
|
||||||
gpsStatus->observe(&gps->newStatus);
|
gpsStatus->observe(&gps->newStatus);
|
||||||
else
|
else
|
||||||
DEBUG_MSG("Warning: No GPS found - running without GPS\n");
|
DEBUG_MSG("Warning: No GPS found - running without GPS\n");
|
||||||
|
|
||||||
nodeStatus->observe(&nodeDB.newStatus);
|
nodeStatus->observe(&nodeDB.newStatus);
|
||||||
|
|
||||||
service.init();
|
service.init();
|
||||||
|
|
||||||
|
// Don't call screen setup until after nodedb is setup (because we need
|
||||||
|
// the current region name)
|
||||||
|
#if defined(ST7735_CS) || defined(HAS_EINK)
|
||||||
|
screen->setup();
|
||||||
|
#else
|
||||||
|
if (screen_found)
|
||||||
|
screen->setup();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
screen->print("Started...\n");
|
||||||
|
|
||||||
// We have now loaded our saved preferences from flash
|
// We have now loaded our saved preferences from flash
|
||||||
|
|
||||||
// ONCE we will factory reset the GPS for bug #327
|
// ONCE we will factory reset the GPS for bug #327
|
||||||
@@ -306,8 +385,7 @@ void setup()
|
|||||||
digitalWrite(SX1262_ANT_SW, 1);
|
digitalWrite(SX1262_ANT_SW, 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
||||||
RadioInterface *rIf = NULL;
|
|
||||||
|
|
||||||
#if defined(RF95_IRQ)
|
#if defined(RF95_IRQ)
|
||||||
if (!rIf) {
|
if (!rIf) {
|
||||||
@@ -348,10 +426,11 @@ void setup()
|
|||||||
if (!rIf)
|
if (!rIf)
|
||||||
recordCriticalError(ErrNoRadio);
|
recordCriticalError(ErrNoRadio);
|
||||||
else
|
else
|
||||||
router.addInterface(rIf);
|
router->addInterface(rIf);
|
||||||
|
|
||||||
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
|
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
|
||||||
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
|
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
|
||||||
|
powerFSMthread = new PowerFSMThread();
|
||||||
|
|
||||||
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
|
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
|
||||||
setCPUFast(false); // 80MHz is fine for our slow peripherals
|
setCPUFast(false); // 80MHz is fine for our slow peripherals
|
||||||
@@ -374,21 +453,12 @@ uint32_t axpDebugRead()
|
|||||||
return 30 * 1000;
|
return 30 * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
concurrency::Periodic axpDebugOutput(axpDebugRead);
|
Periodic axpDebugOutput(axpDebugRead);
|
||||||
axpDebugOutput.setup();
|
axpDebugOutput.setup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
|
|
||||||
|
|
||||||
if (gps)
|
|
||||||
gps->loop(); // FIXME, remove from main, instead block on read
|
|
||||||
router.loop();
|
|
||||||
powerFSM.run_machine();
|
|
||||||
service.loop();
|
|
||||||
|
|
||||||
concurrency::periodicScheduler.loop();
|
|
||||||
// axpDebugOutput.loop();
|
// axpDebugOutput.loop();
|
||||||
|
|
||||||
#ifdef DEBUG_PORT
|
#ifdef DEBUG_PORT
|
||||||
@@ -400,25 +470,9 @@ void loop()
|
|||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
esp32Loop();
|
esp32Loop();
|
||||||
#endif
|
#endif
|
||||||
#ifdef TBEAM_V10
|
|
||||||
power->loop();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef BUTTON_PIN
|
// For debugging
|
||||||
userButton.tick();
|
// if (rIf) ((RadioLibInterface *)rIf)->isActivelyReceiving();
|
||||||
#endif
|
|
||||||
#ifdef BUTTON_PIN_ALT
|
|
||||||
userButtonAlt.tick();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
loopWifi();
|
|
||||||
|
|
||||||
// Show boot screen for first 3 seconds, then switch to normal operation.
|
|
||||||
static bool showingBootScreen = true;
|
|
||||||
if (showingBootScreen && (millis() > 3000)) {
|
|
||||||
screen.stopBootScreen();
|
|
||||||
showingBootScreen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_STACK
|
#ifdef DEBUG_STACK
|
||||||
static uint32_t lastPrint = 0;
|
static uint32_t lastPrint = 0;
|
||||||
@@ -428,19 +482,18 @@ void loop()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Update the screen last, after we've figured out what to show.
|
|
||||||
screen.debug_info()->setChannelNameStatus(getChannelName());
|
|
||||||
|
|
||||||
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
|
||||||
// i.e. don't just keep spinning in loop as fast as we can.
|
|
||||||
// DEBUG_MSG("msecs %d\n", msecstosleep);
|
|
||||||
|
|
||||||
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons
|
|
||||||
// feel slow
|
|
||||||
msecstosleep = 10;
|
|
||||||
|
|
||||||
// TODO: This should go into a thread handled by FreeRTOS.
|
// TODO: This should go into a thread handled by FreeRTOS.
|
||||||
handleWebResponse();
|
handleWebResponse();
|
||||||
|
|
||||||
delay(msecstosleep);
|
service.loop();
|
||||||
|
|
||||||
|
long delayMsec = mainController.runOrDelay();
|
||||||
|
|
||||||
|
/* if (mainController.nextThread && delayMsec)
|
||||||
|
DEBUG_MSG("Next %s in %ld\n", mainController.nextThread->ThreadName.c_str(),
|
||||||
|
mainController.nextThread->tillRun(millis())); */
|
||||||
|
|
||||||
|
// We want to sleep as long as possible here - because it saves power
|
||||||
|
mainDelay.delay(delayMsec);
|
||||||
|
// if (didWake) DEBUG_MSG("wake!\n");
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/main.h
19
src/main.h
@@ -1,28 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "graphics/Screen.h"
|
|
||||||
#include "PowerStatus.h"
|
|
||||||
#include "GPSStatus.h"
|
#include "GPSStatus.h"
|
||||||
#include "NodeStatus.h"
|
#include "NodeStatus.h"
|
||||||
|
#include "PowerStatus.h"
|
||||||
|
#include "graphics/Screen.h"
|
||||||
|
|
||||||
extern bool axp192_found;
|
extern bool axp192_found;
|
||||||
extern bool ssd1306_found;
|
|
||||||
extern bool isCharging;
|
extern bool isCharging;
|
||||||
extern bool isUSBPowered;
|
extern bool isUSBPowered;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Global Screen singleton.
|
// Global Screen singleton.
|
||||||
extern graphics::Screen screen;
|
extern graphics::Screen *screen;
|
||||||
//extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
// extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
||||||
|
|
||||||
//extern meshtastic::PowerStatus *powerStatus;
|
// extern meshtastic::PowerStatus *powerStatus;
|
||||||
//extern meshtastic::GPSStatus *gpsStatus;
|
// extern meshtastic::GPSStatus *gpsStatus;
|
||||||
//extern meshtastic::NodeStatusHandler *nodeStatusHandler;
|
// extern meshtastic::NodeStatusHandler *nodeStatusHandler;
|
||||||
|
|
||||||
// Return a human readable string of the form "Meshtastic_ab13"
|
// Return a human readable string of the form "Meshtastic_ab13"
|
||||||
const char *getDeviceName();
|
const char *getDeviceName();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop();
|
void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "PacketHistory.h"
|
#include "PacketHistory.h"
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,4 +16,7 @@ struct RegionInfo {
|
|||||||
const char *name; // EU433 etc
|
const char *name; // EU433 etc
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const RegionInfo regions[];
|
extern const RegionInfo regions[];
|
||||||
|
extern const RegionInfo *myRegion;
|
||||||
|
|
||||||
|
extern void initRegion();
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
@@ -48,14 +49,14 @@ MeshService service;
|
|||||||
|
|
||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
|
|
||||||
static uint32_t sendOwnerCb()
|
static int32_t sendOwnerCb()
|
||||||
{
|
{
|
||||||
service.sendOurOwner();
|
service.sendOurOwner();
|
||||||
|
|
||||||
return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000;
|
return getPref_send_owner_interval() * getPref_position_broadcast_secs() * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static concurrency::Periodic sendOwnerPeriod(sendOwnerCb);
|
static concurrency::Periodic *sendOwnerPeriod;
|
||||||
|
|
||||||
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
|
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
|
||||||
{
|
{
|
||||||
@@ -64,17 +65,18 @@ MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
|
|||||||
|
|
||||||
void MeshService::init()
|
void MeshService::init()
|
||||||
{
|
{
|
||||||
sendOwnerPeriod.setup();
|
sendOwnerPeriod = new concurrency::Periodic("SendOwner", sendOwnerCb);
|
||||||
|
|
||||||
nodeDB.init();
|
nodeDB.init();
|
||||||
|
|
||||||
if (gps)
|
if (gps)
|
||||||
gpsObserver.observe(&gps->newStatus);
|
gpsObserver.observe(&gps->newStatus);
|
||||||
packetReceivedObserver.observe(&router.notifyPacketReceived);
|
packetReceivedObserver.observe(&router->notifyPacketReceived);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
|
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
|
||||||
{
|
{
|
||||||
MeshPacket *p = router.allocForSending();
|
MeshPacket *p = router->allocForSending();
|
||||||
p->to = dest;
|
p->to = dest;
|
||||||
p->decoded.want_response = wantReplies;
|
p->decoded.want_response = wantReplies;
|
||||||
p->decoded.which_payload = SubPacket_user_tag;
|
p->decoded.which_payload = SubPacket_user_tag;
|
||||||
@@ -121,7 +123,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
|
|||||||
sendOurOwner(mp->from);
|
sendOurOwner(mp->from);
|
||||||
|
|
||||||
String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n";
|
String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n";
|
||||||
screen.print(lcd.c_str());
|
screen->print(lcd.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mp;
|
return mp;
|
||||||
@@ -139,7 +141,7 @@ void MeshService::handleIncomingPosition(const MeshPacket *mp)
|
|||||||
tv.tv_sec = secs;
|
tv.tv_sec = secs;
|
||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
perhapsSetRTC(&tv);
|
perhapsSetRTC(RTCQualityFromNet, &tv);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DEBUG_MSG("Ignoring incoming packet - not a position\n");
|
DEBUG_MSG("Ignoring incoming packet - not a position\n");
|
||||||
@@ -150,12 +152,8 @@ int MeshService::handleFromRadio(const MeshPacket *mp)
|
|||||||
{
|
{
|
||||||
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
|
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
|
||||||
|
|
||||||
// If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work)
|
// If it is a position packet, perhaps set our clock - this must be before nodeDB.updateFrom
|
||||||
if (!gps->isConnected)
|
handleIncomingPosition(mp);
|
||||||
handleIncomingPosition(mp);
|
|
||||||
else {
|
|
||||||
DEBUG_MSG("Ignoring incoming time, because we have a GPS\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) {
|
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) {
|
||||||
mp = handleFromRadioUser(mp);
|
mp = handleFromRadioUser(mp);
|
||||||
@@ -229,8 +227,8 @@ void MeshService::handleToRadio(MeshPacket &p)
|
|||||||
if (p.id == 0)
|
if (p.id == 0)
|
||||||
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
||||||
|
|
||||||
p.rx_time = getValidTime(); // Record the time the packet arrived from the phone
|
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
|
||||||
// (so we update our nodedb for the local node)
|
// (so we update our nodedb for the local node)
|
||||||
|
|
||||||
// Send the packet into the mesh
|
// Send the packet into the mesh
|
||||||
|
|
||||||
@@ -250,10 +248,10 @@ void MeshService::sendToMesh(MeshPacket *p)
|
|||||||
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
|
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
|
||||||
|
|
||||||
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
|
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
|
||||||
// nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless
|
// nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless
|
||||||
// devices can get time.
|
// devices can get time.
|
||||||
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) {
|
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) {
|
||||||
if (!gps->isConnected) {
|
if (getRTCQuality() < RTCQualityGPS) {
|
||||||
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
|
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
|
||||||
p->decoded.position.time = 0;
|
p->decoded.position.time = 0;
|
||||||
} else
|
} else
|
||||||
@@ -261,7 +259,7 @@ void MeshService::sendToMesh(MeshPacket *p)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
|
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
|
||||||
router.sendLocal(p);
|
router->sendLocal(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
|
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
|
||||||
@@ -283,12 +281,13 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
|
|||||||
assert(node->has_position);
|
assert(node->has_position);
|
||||||
|
|
||||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||||
MeshPacket *p = router.allocForSending();
|
MeshPacket *p = router->allocForSending();
|
||||||
p->to = dest;
|
p->to = dest;
|
||||||
p->decoded.which_payload = SubPacket_position_tag;
|
p->decoded.which_payload = SubPacket_position_tag;
|
||||||
p->decoded.position = node->position;
|
p->decoded.position = node->position;
|
||||||
p->decoded.want_response = wantReplies;
|
p->decoded.want_response = wantReplies;
|
||||||
p->decoded.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
p->decoded.position.time =
|
||||||
|
getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||||
sendToMesh(p);
|
sendToMesh(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +295,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)
|
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||||
MeshPacket *p = router.allocForSending();
|
MeshPacket *p = router->allocForSending();
|
||||||
p->decoded.which_payload = SubPacket_position_tag;
|
p->decoded.which_payload = SubPacket_position_tag;
|
||||||
|
|
||||||
Position &pos = p->decoded.position;
|
Position &pos = p->decoded.position;
|
||||||
@@ -306,9 +305,10 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
|||||||
pos.altitude = gps->altitude;
|
pos.altitude = gps->altitude;
|
||||||
pos.latitude_i = gps->latitude;
|
pos.latitude_i = gps->latitude;
|
||||||
pos.longitude_i = gps->longitude;
|
pos.longitude_i = gps->longitude;
|
||||||
pos.time = getValidTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pos.time = getValidTime(RTCQualityGPS);
|
||||||
|
|
||||||
// Include our current battery voltage in our position announcement
|
// Include our current battery voltage in our position announcement
|
||||||
pos.battery_level = powerStatus->getBatteryChargePercent();
|
pos.battery_level = powerStatus->getBatteryChargePercent();
|
||||||
updateBatteryLevel(pos.battery_level);
|
updateBatteryLevel(pos.battery_level);
|
||||||
@@ -318,7 +318,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
|||||||
// We limit our GPS broadcasts to a max rate
|
// We limit our GPS broadcasts to a max rate
|
||||||
static uint32_t lastGpsSend;
|
static uint32_t lastGpsSend;
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000) {
|
if (lastGpsSend == 0 || now - lastGpsSend > getPref_position_broadcast_secs() * 1000) {
|
||||||
lastGpsSend = now;
|
lastGpsSend = now;
|
||||||
DEBUG_MSG("Sending position to mesh\n");
|
DEBUG_MSG("Sending position to mesh\n");
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PacketHistory.h"
|
#include "PacketHistory.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
|
#include "RTC.h"
|
||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
@@ -116,18 +117,9 @@ bool NodeDB::resetRadioConfig()
|
|||||||
DEBUG_MSG("Performing factory reset!\n");
|
DEBUG_MSG("Performing factory reset!\n");
|
||||||
installDefaultDeviceState();
|
installDefaultDeviceState();
|
||||||
didFactoryReset = true;
|
didFactoryReset = true;
|
||||||
} else if (radioConfig.preferences.sds_secs == 0) {
|
} else if (!channelSettings.psk.size) {
|
||||||
DEBUG_MSG("Fixing bogus RadioConfig!\n");
|
DEBUG_MSG("Setting default preferences!\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;
|
|
||||||
radioConfig.has_channel_settings = true;
|
radioConfig.has_channel_settings = true;
|
||||||
radioConfig.has_preferences = true;
|
radioConfig.has_preferences = true;
|
||||||
|
|
||||||
@@ -155,6 +147,7 @@ bool NodeDB::resetRadioConfig()
|
|||||||
radioConfig.preferences.wait_bluetooth_secs = 30;
|
radioConfig.preferences.wait_bluetooth_secs = 30;
|
||||||
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
||||||
radioConfig.preferences.ls_secs = 60;
|
radioConfig.preferences.ls_secs = 60;
|
||||||
|
radioConfig.preferences.region = RegionCode_TW;
|
||||||
}
|
}
|
||||||
|
|
||||||
return didFactoryReset;
|
return didFactoryReset;
|
||||||
@@ -252,6 +245,9 @@ void NodeDB::init()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the global myRegion
|
||||||
|
initRegion();
|
||||||
|
|
||||||
strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
|
strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
|
||||||
strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
|
strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
|
||||||
|
|
||||||
@@ -423,10 +419,10 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
|||||||
|
|
||||||
switch (p.which_payload) {
|
switch (p.which_payload) {
|
||||||
case SubPacket_position_tag: {
|
case SubPacket_position_tag: {
|
||||||
// we carefully preserve the old time, because we always trust our local timestamps more
|
// we always trust our local timestamps more
|
||||||
uint32_t oldtime = info->position.time;
|
|
||||||
info->position = p.position;
|
info->position = p.position;
|
||||||
info->position.time = oldtime;
|
if (mp.rx_time)
|
||||||
|
info->position.time = mp.rx_time;
|
||||||
info->has_position = true;
|
info->has_position = true;
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ class NodeDB
|
|||||||
void saveToDisk();
|
void saveToDisk();
|
||||||
|
|
||||||
/** Reinit radio config if needed, because either:
|
/** Reinit radio config if needed, because either:
|
||||||
* a) sometimes a buggy android app might send us bogus settings or
|
* a) sometimes a buggy android app might send us bogus settings or
|
||||||
* b) the client set factory_reset
|
* 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
|
* @return true if the config was completely reset, in that case, we should send it back to the client
|
||||||
*/
|
*/
|
||||||
bool resetRadioConfig();
|
bool resetRadioConfig();
|
||||||
@@ -137,4 +137,25 @@ their nodes
|
|||||||
*
|
*
|
||||||
* https://github.com/meshtastic/Meshtastic-device/issues/269
|
* https://github.com/meshtastic/Meshtastic-device/issues/269
|
||||||
*/
|
*/
|
||||||
const char *getChannelName();
|
const char *getChannelName();
|
||||||
|
|
||||||
|
#define PREF_GET(name, defaultVal) \
|
||||||
|
inline uint32_t getPref_##name() { return radioConfig.preferences.name ? radioConfig.preferences.name : (defaultVal); }
|
||||||
|
|
||||||
|
PREF_GET(send_owner_interval, 4)
|
||||||
|
PREF_GET(position_broadcast_secs, 15 * 60)
|
||||||
|
|
||||||
|
// Each time we wake into the DARK state allow 1 minute to send and receive BLE packets to the phone
|
||||||
|
PREF_GET(wait_bluetooth_secs, 60)
|
||||||
|
|
||||||
|
PREF_GET(screen_on_secs, 60)
|
||||||
|
PREF_GET(mesh_sds_timeout_secs, 2 * 60 * 60)
|
||||||
|
PREF_GET(phone_sds_timeout_sec, 2 * 60 * 60)
|
||||||
|
PREF_GET(sds_secs, 365 * 24 * 60 * 60)
|
||||||
|
|
||||||
|
// We default to sleeping (with bluetooth off for 5 minutes at a time). This seems to be a good tradeoff between
|
||||||
|
// latency for the user sending messages and power savings because of not having to run (expensive) ESP32 bluetooth
|
||||||
|
PREF_GET(ls_secs, 5 * 60)
|
||||||
|
|
||||||
|
PREF_GET(phone_timeout_secs, 15 * 60)
|
||||||
|
PREF_GET(min_wake_secs, 10)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ void PhoneAPI::init()
|
|||||||
void PhoneAPI::checkConnectionTimeout()
|
void PhoneAPI::checkConnectionTimeout()
|
||||||
{
|
{
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
bool newConnected = (millis() - lastContactMsec < radioConfig.preferences.phone_timeout_secs * 1000L);
|
bool newConnected = (millis() - lastContactMsec < getPref_phone_timeout_secs() * 1000L);
|
||||||
if (!newConnected) {
|
if (!newConnected) {
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
onConnectionChanged(isConnected);
|
onConnectionChanged(isConnected);
|
||||||
@@ -109,7 +109,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_SEND_MY_INFO:
|
case STATE_SEND_MY_INFO:
|
||||||
myNodeInfo.has_gps = gps && gps->isConnected; // Update with latest GPS connect info
|
// If the user has specified they don't want our node to share its location, make sure to tell the phone
|
||||||
|
// app not to send locations on our behalf.
|
||||||
|
myNodeInfo.has_gps = (radioConfig.preferences.location_share == LocationSharing_LocDisabled)
|
||||||
|
? true
|
||||||
|
: (gps && gps->isConnected()); // Update with latest GPS connect info
|
||||||
fromRadioScratch.which_variant = FromRadio_my_info_tag;
|
fromRadioScratch.which_variant = FromRadio_my_info_tag;
|
||||||
fromRadioScratch.variant.my_info = myNodeInfo;
|
fromRadioScratch.variant.my_info = myNodeInfo;
|
||||||
state = STATE_SEND_RADIO;
|
state = STATE_SEND_RADIO;
|
||||||
@@ -117,7 +121,14 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
|||||||
|
|
||||||
case STATE_SEND_RADIO:
|
case STATE_SEND_RADIO:
|
||||||
fromRadioScratch.which_variant = FromRadio_radio_tag;
|
fromRadioScratch.which_variant = FromRadio_radio_tag;
|
||||||
|
|
||||||
fromRadioScratch.variant.radio = radioConfig;
|
fromRadioScratch.variant.radio = radioConfig;
|
||||||
|
|
||||||
|
// NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior.
|
||||||
|
// So even if we internally use 0 to represent 'use default' we still need to send the value we are
|
||||||
|
// using to the app (so that even old phone apps work with new device loads).
|
||||||
|
fromRadioScratch.variant.radio.preferences.ls_secs = getPref_ls_secs();
|
||||||
|
|
||||||
state = STATE_SEND_NODEINFO;
|
state = STATE_SEND_NODEINFO;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#define MAX_POWER 20
|
#define MAX_POWER 20
|
||||||
// if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17
|
// if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17
|
||||||
|
// In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING
|
||||||
|
// if you set power to something higher than 17 or 20 you might fry your board.
|
||||||
|
|
||||||
#define POWER_DEFAULT 17 // How much power to use if the user hasn't set a power level
|
#define POWER_DEFAULT 17 // How much power to use if the user hasn't set a power level
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ bool RF95Interface::init()
|
|||||||
power = MAX_POWER;
|
power = MAX_POWER;
|
||||||
|
|
||||||
limitPower();
|
limitPower();
|
||||||
|
|
||||||
iface = lora = new RadioLibRF95(&module);
|
iface = lora = new RadioLibRF95(&module);
|
||||||
|
|
||||||
#ifdef RF95_TCXO
|
#ifdef RF95_TCXO
|
||||||
@@ -158,7 +160,7 @@ void RF95Interface::startReceive()
|
|||||||
|
|
||||||
isReceiving = true;
|
isReceiving = true;
|
||||||
|
|
||||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
// Must be done AFTER, starting receive, because startReceive clears (possibly stale) interrupt pending register bits
|
||||||
enableInterrupt(isrRxLevel0);
|
enableInterrupt(isrRxLevel0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +173,7 @@ bool RF95Interface::isActivelyReceiving()
|
|||||||
bool RF95Interface::sleep()
|
bool RF95Interface::sleep()
|
||||||
{
|
{
|
||||||
// put chipset into sleep mode
|
// put chipset into sleep mode
|
||||||
disableInterrupt();
|
setStandby(); // First cancel any active receving/sending
|
||||||
lora->sleep();
|
lora->sleep();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -26,7 +26,20 @@ const RegionInfo regions[] = {
|
|||||||
RDEF(Unset, 903.08f, 2.16f, 13, 0) // Assume US freqs if unset, Must be last
|
RDEF(Unset, 903.08f, 2.16f, 13, 0) // Assume US freqs if unset, Must be last
|
||||||
};
|
};
|
||||||
|
|
||||||
static const RegionInfo *myRegion;
|
const RegionInfo *myRegion;
|
||||||
|
|
||||||
|
void initRegion()
|
||||||
|
{
|
||||||
|
if (!myRegion) {
|
||||||
|
const RegionInfo *r = regions;
|
||||||
|
for (; r->code != RegionCode_Unset && r->code != radioConfig.preferences.region; r++)
|
||||||
|
;
|
||||||
|
myRegion = r;
|
||||||
|
DEBUG_MSG("Wanted region %d, using %s\n", radioConfig.preferences.region, r->name);
|
||||||
|
|
||||||
|
myNodeInfo.num_channels = myRegion->numChannels; // Tell our android app how many channels we have
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## LoRaWAN for North America
|
* ## LoRaWAN for North America
|
||||||
@@ -95,16 +108,6 @@ RadioInterface::RadioInterface()
|
|||||||
{
|
{
|
||||||
assert(sizeof(PacketHeader) == 4 || sizeof(PacketHeader) == 16); // make sure the compiler did what we expected
|
assert(sizeof(PacketHeader) == 4 || sizeof(PacketHeader) == 16); // make sure the compiler did what we expected
|
||||||
|
|
||||||
if (!myRegion) {
|
|
||||||
const RegionInfo *r = regions;
|
|
||||||
for (; r->code != RegionCode_Unset && r->code != radioConfig.preferences.region; r++)
|
|
||||||
;
|
|
||||||
myRegion = r;
|
|
||||||
DEBUG_MSG("Wanted region %d, using %s\n", radioConfig.preferences.region, r->name);
|
|
||||||
|
|
||||||
myNodeInfo.num_channels = myRegion->numChannels; // Tell our android app how many channels we have
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can't print strings this early - serial not setup yet
|
// Can't print strings this early - serial not setup yet
|
||||||
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
||||||
}
|
}
|
||||||
@@ -121,9 +124,6 @@ bool RadioInterface::init()
|
|||||||
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
|
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
|
||||||
// time.
|
// time.
|
||||||
|
|
||||||
// we want this thread to run at very high priority, because it is effectively running as a user space ISR
|
|
||||||
start("radio", RADIO_STACK_SIZE, configMAX_PRIORITIES - 1); // Start our worker thread
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ typedef struct {
|
|||||||
*
|
*
|
||||||
* This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations)
|
* This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations)
|
||||||
*/
|
*/
|
||||||
class RadioInterface : protected concurrency::NotifiedWorkerThread
|
class RadioInterface
|
||||||
{
|
{
|
||||||
friend class MeshRadio; // for debugging we let that class touch pool
|
friend class MeshRadio; // for debugging we let that class touch pool
|
||||||
PointerQueue<MeshPacket> *rxDest = NULL;
|
PointerQueue<MeshPacket> *rxDest = NULL;
|
||||||
@@ -72,6 +72,8 @@ class RadioInterface : protected concurrency::NotifiedWorkerThread
|
|||||||
*/
|
*/
|
||||||
RadioInterface();
|
RadioInterface();
|
||||||
|
|
||||||
|
virtual ~RadioInterface() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set where to deliver received packets. This method should only be used by the Router class
|
* Set where to deliver received packets. This method should only be used by the Router class
|
||||||
*/
|
*/
|
||||||
@@ -117,8 +119,6 @@ class RadioInterface : protected concurrency::NotifiedWorkerThread
|
|||||||
*/
|
*/
|
||||||
size_t beginSending(MeshPacket *p);
|
size_t beginSending(MeshPacket *p);
|
||||||
|
|
||||||
virtual void loop() {} // Idle processing
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some regulatory regions limit xmit power.
|
* Some regulatory regions limit xmit power.
|
||||||
* This function should be called by subclasses after setting their desired power. It might lower it
|
* This function should be called by subclasses after setting their desired power. It might lower it
|
||||||
|
|||||||
@@ -19,17 +19,11 @@ void LockingModule::SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint
|
|||||||
|
|
||||||
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
||||||
SPIClass &spi, PhysicalLayer *_iface)
|
SPIClass &spi, PhysicalLayer *_iface)
|
||||||
: concurrency::PeriodicTask(0), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
|
: NotifiedWorkerThread("RadioIf"), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
|
||||||
{
|
{
|
||||||
instance = this;
|
instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RadioLibInterface::init()
|
|
||||||
{
|
|
||||||
setup(); // init our timer
|
|
||||||
return RadioInterface::init();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
// ESP32 doesn't use that flag
|
// ESP32 doesn't use that flag
|
||||||
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
|
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
|
||||||
@@ -41,9 +35,8 @@ void INTERRUPT_ATTR RadioLibInterface::isrLevel0Common(PendingISR cause)
|
|||||||
{
|
{
|
||||||
instance->disableInterrupt();
|
instance->disableInterrupt();
|
||||||
|
|
||||||
instance->pending = cause;
|
|
||||||
BaseType_t xHigherPriorityTaskWoken;
|
BaseType_t xHigherPriorityTaskWoken;
|
||||||
instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, eSetValueWithOverwrite);
|
instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, true);
|
||||||
|
|
||||||
/* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE.
|
/* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE.
|
||||||
The macro used to do this is dependent on the port and may be called
|
The macro used to do this is dependent on the port and may be called
|
||||||
@@ -191,10 +184,8 @@ transmitters that we are potentially stomping on. Requires further thought.
|
|||||||
|
|
||||||
FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later.
|
FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later.
|
||||||
*/
|
*/
|
||||||
void RadioLibInterface::loop()
|
void RadioLibInterface::onNotify(uint32_t notification)
|
||||||
{
|
{
|
||||||
pending = ISR_NONE;
|
|
||||||
|
|
||||||
switch (notification) {
|
switch (notification) {
|
||||||
case ISR_TX:
|
case ISR_TX:
|
||||||
handleTransmitInterrupt();
|
handleTransmitInterrupt();
|
||||||
@@ -209,6 +200,8 @@ void RadioLibInterface::loop()
|
|||||||
startTransmitTimer();
|
startTransmitTimer();
|
||||||
break;
|
break;
|
||||||
case TRANSMIT_DELAY_COMPLETED:
|
case TRANSMIT_DELAY_COMPLETED:
|
||||||
|
// DEBUG_MSG("delay done\n");
|
||||||
|
|
||||||
// If we are not currently in receive mode, then restart the timer and try again later (this can happen if the main thread
|
// If we are not currently in receive mode, then restart the timer and try again later (this can happen if the main thread
|
||||||
// has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode?
|
// has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode?
|
||||||
if (!txQueue.isEmpty()) {
|
if (!txQueue.isEmpty()) {
|
||||||
@@ -229,25 +222,14 @@ void RadioLibInterface::loop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RadioLibInterface::doTask()
|
|
||||||
{
|
|
||||||
disable(); // Don't call this callback again
|
|
||||||
|
|
||||||
// We use without overwrite, so that if there is already an interrupt pending to be handled, that gets handle properly (the
|
|
||||||
// ISR handler will restart our timer)
|
|
||||||
|
|
||||||
notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadioLibInterface::startTransmitTimer(bool withDelay)
|
void RadioLibInterface::startTransmitTimer(bool withDelay)
|
||||||
{
|
{
|
||||||
// If we have work to do and the timer wasn't already scheduled, schedule it now
|
// If we have work to do and the timer wasn't already scheduled, schedule it now
|
||||||
if (getPeriod() == 0 && !txQueue.isEmpty()) {
|
if (!txQueue.isEmpty()) {
|
||||||
uint32_t delay =
|
uint32_t delay =
|
||||||
!withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values
|
!withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values
|
||||||
// DEBUG_MSG("xmit timer %d\n", delay);
|
// DEBUG_MSG("xmit timer %d\n", delay);
|
||||||
// DEBUG_MSG("delaying %u\n", delay);
|
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
|
||||||
setPeriod(delay);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../concurrency/PeriodicTask.h"
|
#include "../concurrency/OSThread.h"
|
||||||
#include "RadioInterface.h"
|
#include "RadioInterface.h"
|
||||||
|
|
||||||
#ifdef CubeCell_BoardPlus
|
#ifdef CubeCell_BoardPlus
|
||||||
@@ -59,13 +59,11 @@ class LockingModule : public Module
|
|||||||
virtual void SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes);
|
virtual void SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes);
|
||||||
};
|
};
|
||||||
|
|
||||||
class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTask
|
class RadioLibInterface : public RadioInterface, protected concurrency::NotifiedWorkerThread
|
||||||
{
|
{
|
||||||
/// Used as our notification from the ISR
|
/// Used as our notification from the ISR
|
||||||
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED };
|
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED };
|
||||||
|
|
||||||
volatile PendingISR pending = ISR_NONE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raw ISR handler that just calls our polymorphic method
|
* Raw ISR handler that just calls our polymorphic method
|
||||||
*/
|
*/
|
||||||
@@ -137,6 +135,11 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
*/
|
*/
|
||||||
virtual void startReceive() = 0;
|
virtual void startReceive() = 0;
|
||||||
|
|
||||||
|
/** are we actively receiving a packet (only called during receiving state)
|
||||||
|
* This method is only public to facilitate debugging. Do not call.
|
||||||
|
*/
|
||||||
|
virtual bool isActivelyReceiving() = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing
|
/** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing
|
||||||
* the transmit
|
* the transmit
|
||||||
@@ -150,7 +153,7 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
|
|
||||||
static void timerCallback(void *p1, uint32_t p2);
|
static void timerCallback(void *p1, uint32_t p2);
|
||||||
|
|
||||||
virtual void doTask();
|
virtual void onNotify(uint32_t notification);
|
||||||
|
|
||||||
/** start an immediate transmit
|
/** start an immediate transmit
|
||||||
* This method is virtual so subclasses can hook as needed, subclasses should not call directly
|
* This method is virtual so subclasses can hook as needed, subclasses should not call directly
|
||||||
@@ -158,10 +161,6 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
virtual void startSend(MeshPacket *txp);
|
virtual void startSend(MeshPacket *txp);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Initialise the Driver transport hardware and software.
|
|
||||||
/// Make sure the Driver is properly configured before calling init().
|
|
||||||
/// \return true if initialisation succeeded.
|
|
||||||
virtual bool init();
|
|
||||||
|
|
||||||
/** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */
|
/** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */
|
||||||
virtual void configHardwareForSend() {}
|
virtual void configHardwareForSend() {}
|
||||||
@@ -176,9 +175,6 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
|
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
|
||||||
virtual bool canSendImmediately();
|
virtual bool canSendImmediately();
|
||||||
|
|
||||||
/** are we actively receiving a packet (only called during receiving state) */
|
|
||||||
virtual bool isActivelyReceiving() = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raw ISR handler that just calls our polymorphic method
|
* Raw ISR handler that just calls our polymorphic method
|
||||||
*/
|
*/
|
||||||
@@ -193,7 +189,5 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
|||||||
*/
|
*/
|
||||||
virtual void addReceiveMetadata(MeshPacket *mp) = 0;
|
virtual void addReceiveMetadata(MeshPacket *mp) = 0;
|
||||||
|
|
||||||
virtual void loop(); // Idle processing
|
|
||||||
|
|
||||||
virtual void setStandby() = 0;
|
virtual void setStandby() = 0;
|
||||||
};
|
};
|
||||||
@@ -160,9 +160,10 @@ PendingPacket *ReliableRouter::startRetransmission(MeshPacket *p)
|
|||||||
/**
|
/**
|
||||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||||
*/
|
*/
|
||||||
void ReliableRouter::doRetransmissions()
|
int32_t ReliableRouter::doRetransmissions()
|
||||||
{
|
{
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
|
int32_t d = INT32_MAX;
|
||||||
|
|
||||||
// FIXME, we should use a better datastructure rather than walking through this map.
|
// FIXME, we should use a better datastructure rather than walking through this map.
|
||||||
// for(auto el: pending) {
|
// for(auto el: pending) {
|
||||||
@@ -192,5 +193,13 @@ void ReliableRouter::doRetransmissions()
|
|||||||
p.setNextTx();
|
p.setNextTx();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Not yet time
|
||||||
|
int32_t t = p.nextTxMsec - now;
|
||||||
|
|
||||||
|
d = min(t, d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "FloodingRouter.h"
|
#include "FloodingRouter.h"
|
||||||
#include "../concurrency/PeriodicTask.h"
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,10 +79,13 @@ class ReliableRouter : public FloodingRouter
|
|||||||
virtual ErrorCode send(MeshPacket *p);
|
virtual ErrorCode send(MeshPacket *p);
|
||||||
|
|
||||||
/** Do our retransmission handling */
|
/** Do our retransmission handling */
|
||||||
virtual void loop()
|
virtual int32_t runOnce()
|
||||||
{
|
{
|
||||||
doRetransmissions();
|
auto d = FloodingRouter::runOnce();
|
||||||
FloodingRouter::loop();
|
|
||||||
|
int32_t r = doRetransmissions();
|
||||||
|
|
||||||
|
return min(d, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -124,6 +126,8 @@ class ReliableRouter : public FloodingRouter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
||||||
|
*
|
||||||
|
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
|
||||||
*/
|
*/
|
||||||
void doRetransmissions();
|
int32_t doRetransmissions();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
#include "CryptoEngine.h"
|
#include "CryptoEngine.h"
|
||||||
#include "GPS.h"
|
#include "RTC.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include <NodeDB.h>
|
#include <NodeDB.h>
|
||||||
@@ -34,25 +34,29 @@ Allocator<MeshPacket> &packetPool = staticPool;
|
|||||||
*
|
*
|
||||||
* Currently we only allow one interface, that may change in the future
|
* Currently we only allow one interface, that may change in the future
|
||||||
*/
|
*/
|
||||||
Router::Router() : fromRadioQueue(MAX_RX_FROMRADIO)
|
Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRADIO)
|
||||||
{
|
{
|
||||||
// This is called pre main(), don't touch anything here, the following code is not safe
|
// This is called pre main(), don't touch anything here, the following code is not safe
|
||||||
|
|
||||||
/* DEBUG_MSG("Size of NodeInfo %d\n", sizeof(NodeInfo));
|
/* DEBUG_MSG("Size of NodeInfo %d\n", sizeof(NodeInfo));
|
||||||
DEBUG_MSG("Size of SubPacket %d\n", sizeof(SubPacket));
|
DEBUG_MSG("Size of SubPacket %d\n", sizeof(SubPacket));
|
||||||
DEBUG_MSG("Size of MeshPacket %d\n", sizeof(MeshPacket)); */
|
DEBUG_MSG("Size of MeshPacket %d\n", sizeof(MeshPacket)); */
|
||||||
|
|
||||||
|
fromRadioQueue.setReader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* do idle processing
|
* do idle processing
|
||||||
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
||||||
*/
|
*/
|
||||||
void Router::loop()
|
int32_t Router::runOnce()
|
||||||
{
|
{
|
||||||
MeshPacket *mp;
|
MeshPacket *mp;
|
||||||
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
|
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
|
||||||
perhapsHandleReceived(mp);
|
perhapsHandleReceived(mp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return INT32_MAX; // Wait a long time - until we get woken for the message queue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a unique packet id
|
/// Generate a unique packet id
|
||||||
@@ -89,7 +93,7 @@ MeshPacket *Router::allocForSending()
|
|||||||
p->to = NODENUM_BROADCAST;
|
p->to = NODENUM_BROADCAST;
|
||||||
p->hop_limit = HOP_RELIABLE;
|
p->hop_limit = HOP_RELIABLE;
|
||||||
p->id = generatePacketId();
|
p->id = generatePacketId();
|
||||||
p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp
|
p->rx_time = getValidTime(RTCQualityFromNet); // Just in case we process the packet locally - make sure it has a valid timestamp
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@@ -198,9 +202,8 @@ NodeNum Router::getNodeNum()
|
|||||||
*/
|
*/
|
||||||
void Router::handleReceived(MeshPacket *p)
|
void Router::handleReceived(MeshPacket *p)
|
||||||
{
|
{
|
||||||
// FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function
|
|
||||||
// Also, we should set the time from the ISR and it should have msec level resolution
|
// Also, we should set the time from the ISR and it should have msec level resolution
|
||||||
p->rx_time = getValidTime(); // store the arrival timestamp for the phone
|
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
|
||||||
|
|
||||||
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
||||||
if (perhapsDecode(p)) {
|
if (perhapsDecode(p)) {
|
||||||
|
|||||||
@@ -5,12 +5,13 @@
|
|||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "PointerQueue.h"
|
#include "PointerQueue.h"
|
||||||
#include "RadioInterface.h"
|
#include "RadioInterface.h"
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
#include "mesh.pb.h"
|
#include "mesh.pb.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mesh aware router that supports multiple interfaces.
|
* A mesh aware router that supports multiple interfaces.
|
||||||
*/
|
*/
|
||||||
class Router
|
class Router : protected concurrency::OSThread
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
RadioInterface *iface;
|
RadioInterface *iface;
|
||||||
@@ -44,7 +45,7 @@ class Router
|
|||||||
* do idle processing
|
* do idle processing
|
||||||
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
||||||
*/
|
*/
|
||||||
virtual void loop();
|
virtual int32_t runOnce();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Works like send, but if we are sending to the local node, we directly put the message in the receive queue
|
* Works like send, but if we are sending to the local node, we directly put the message in the receive queue
|
||||||
@@ -113,7 +114,7 @@ class Router
|
|||||||
void handleReceived(MeshPacket *p);
|
void handleReceived(MeshPacket *p);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Router &router;
|
extern Router *router;
|
||||||
|
|
||||||
/// Generate a unique packet id
|
/// Generate a unique packet id
|
||||||
// FIXME, move this someplace better
|
// FIXME, move this someplace better
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
#include "SX1262Interface.h"
|
#include "SX1262Interface.h"
|
||||||
#include <configuration.h>
|
#include <configuration.h>
|
||||||
|
|
||||||
|
// Particular boards might define a different max power based on what their hardware can do
|
||||||
|
#ifndef SX1262_MAX_POWER
|
||||||
|
#define SX1262_MAX_POWER 22
|
||||||
|
#endif
|
||||||
|
|
||||||
SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
||||||
SPIClass &spi)
|
SPIClass &spi)
|
||||||
: RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module)
|
: RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module)
|
||||||
@@ -39,10 +44,10 @@ bool SX1262Interface::init()
|
|||||||
applyModemConfig();
|
applyModemConfig();
|
||||||
|
|
||||||
if (power == 0)
|
if (power == 0)
|
||||||
power = 22;
|
power = SX1262_MAX_POWER;
|
||||||
|
|
||||||
if (power > 22) // This chip has lower power limits than some
|
if (power > SX1262_MAX_POWER) // This chip has lower power limits than some
|
||||||
power = 22;
|
power = SX1262_MAX_POWER;
|
||||||
|
|
||||||
limitPower();
|
limitPower();
|
||||||
|
|
||||||
@@ -82,8 +87,8 @@ bool SX1262Interface::reconfigure()
|
|||||||
assert(err == ERR_NONE);
|
assert(err == ERR_NONE);
|
||||||
|
|
||||||
// Hmm - seems to lower SNR when the signal levels are high. Leaving off for now...
|
// Hmm - seems to lower SNR when the signal levels are high. Leaving off for now...
|
||||||
// err = lora.setRxGain(true);
|
err = lora.setRxGain(true);
|
||||||
// assert(err == ERR_NONE);
|
assert(err == ERR_NONE);
|
||||||
|
|
||||||
err = lora.setSyncWord(syncWord);
|
err = lora.setSyncWord(syncWord);
|
||||||
assert(err == ERR_NONE);
|
assert(err == ERR_NONE);
|
||||||
@@ -179,9 +184,19 @@ void SX1262Interface::startReceive()
|
|||||||
/** Could we send right now (i.e. either not actively receving or transmitting)? */
|
/** Could we send right now (i.e. either not actively receving or transmitting)? */
|
||||||
bool SX1262Interface::isActivelyReceiving()
|
bool SX1262Interface::isActivelyReceiving()
|
||||||
{
|
{
|
||||||
// return false; // FIXME
|
// The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
|
||||||
// FIXME this is not correct? - often always true - need to add an extra conditional
|
// received and handled the interrupt for reading the packet/handling errors.
|
||||||
return lora.getPacketLength() > 0;
|
// FIXME: it would be better to check for preamble, but we currently have our ISR not set to fire for packets that
|
||||||
|
// never even get a valid header, so we don't want preamble to get set and stay set due to noise on the network.
|
||||||
|
|
||||||
|
uint16_t irq = lora.getIrqStatus();
|
||||||
|
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
|
||||||
|
|
||||||
|
// this is not correct - often always true - need to add an extra conditional
|
||||||
|
// size_t bytesPending = lora.getPacketLength();
|
||||||
|
|
||||||
|
// if (hasPreamble) DEBUG_MSG("rx hasPreamble\n");
|
||||||
|
return hasPreamble;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SX1262Interface::sleep()
|
bool SX1262Interface::sleep()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
#include "freertosinc.h"
|
#include "freertosinc.h"
|
||||||
|
|
||||||
#ifdef HAS_FREE_RTOS
|
#ifdef HAS_FREE_RTOS
|
||||||
@@ -15,6 +16,7 @@ template <class T> class TypedQueue
|
|||||||
{
|
{
|
||||||
static_assert(std::is_pod<T>::value, "T must be pod");
|
static_assert(std::is_pod<T>::value, "T must be pod");
|
||||||
QueueHandle_t h;
|
QueueHandle_t h;
|
||||||
|
concurrency::OSThread *reader = NULL;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TypedQueue(int maxElements)
|
TypedQueue(int maxElements)
|
||||||
@@ -29,13 +31,35 @@ template <class T> class TypedQueue
|
|||||||
|
|
||||||
bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; }
|
bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; }
|
||||||
|
|
||||||
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { return xQueueSendToBack(h, &x, maxWait) == pdTRUE; }
|
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
||||||
|
{
|
||||||
|
if (reader) {
|
||||||
|
reader->setInterval(0);
|
||||||
|
concurrency::mainDelay.interrupt();
|
||||||
|
}
|
||||||
|
return xQueueSendToBack(h, &x, maxWait) == pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
bool enqueueFromISR(T x, BaseType_t *higherPriWoken) { return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE; }
|
bool enqueueFromISR(T x, BaseType_t *higherPriWoken)
|
||||||
|
{
|
||||||
|
if (reader) {
|
||||||
|
reader->setInterval(0);
|
||||||
|
concurrency::mainDelay.interruptFromISR(higherPriWoken);
|
||||||
|
}
|
||||||
|
return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY) { return xQueueReceive(h, p, maxWait) == pdTRUE; }
|
bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY) { return xQueueReceive(h, p, maxWait) == pdTRUE; }
|
||||||
|
|
||||||
bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a thread that is reading from this queue
|
||||||
|
* If a message is pushed to this queue that thread will be scheduled to run ASAP.
|
||||||
|
*
|
||||||
|
* Note: thread will not be automatically enabled, just have its interval set to 0
|
||||||
|
*/
|
||||||
|
void setReader(concurrency::OSThread *t) { reader = t; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#else
|
#else
|
||||||
@@ -49,6 +73,7 @@ template <class T> class TypedQueue
|
|||||||
template <class T> class TypedQueue
|
template <class T> class TypedQueue
|
||||||
{
|
{
|
||||||
std::queue<T> q;
|
std::queue<T> q;
|
||||||
|
concurrency::OSThread *reader = NULL;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TypedQueue(int maxElements) {}
|
TypedQueue(int maxElements) {}
|
||||||
@@ -59,6 +84,11 @@ template <class T> class TypedQueue
|
|||||||
|
|
||||||
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
||||||
{
|
{
|
||||||
|
if (reader) {
|
||||||
|
reader->setInterval(0);
|
||||||
|
concurrency::mainDelay.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
q.push(x);
|
q.push(x);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -77,5 +107,7 @@ template <class T> class TypedQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
// bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
// bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
||||||
|
|
||||||
|
void setReader(concurrency::OSThread *t) { reader = t; }
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ PB_BIND(MeshPacket, MeshPacket, 2)
|
|||||||
PB_BIND(ChannelSettings, ChannelSettings, AUTO)
|
PB_BIND(ChannelSettings, ChannelSettings, AUTO)
|
||||||
|
|
||||||
|
|
||||||
PB_BIND(RadioConfig, RadioConfig, AUTO)
|
PB_BIND(RadioConfig, RadioConfig, 2)
|
||||||
|
|
||||||
|
|
||||||
PB_BIND(RadioConfig_UserPreferences, RadioConfig_UserPreferences, 2)
|
PB_BIND(RadioConfig_UserPreferences, RadioConfig_UserPreferences, 2)
|
||||||
@@ -60,3 +60,5 @@ PB_BIND(ManufacturingData, ManufacturingData, AUTO)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,19 @@ typedef enum _RegionCode {
|
|||||||
RegionCode_TW = 8
|
RegionCode_TW = 8
|
||||||
} RegionCode;
|
} RegionCode;
|
||||||
|
|
||||||
|
typedef enum _GpsOperation {
|
||||||
|
GpsOperation_GpsOpUnset = 0,
|
||||||
|
GpsOperation_GpsOpMobile = 2,
|
||||||
|
GpsOperation_GpsOpTimeOnly = 3,
|
||||||
|
GpsOperation_GpsOpDisabled = 4
|
||||||
|
} GpsOperation;
|
||||||
|
|
||||||
|
typedef enum _LocationSharing {
|
||||||
|
LocationSharing_LocUnset = 0,
|
||||||
|
LocationSharing_LocEnabled = 1,
|
||||||
|
LocationSharing_LocDisabled = 2
|
||||||
|
} LocationSharing;
|
||||||
|
|
||||||
typedef enum _Data_Type {
|
typedef enum _Data_Type {
|
||||||
Data_Type_OPAQUE = 0,
|
Data_Type_OPAQUE = 0,
|
||||||
Data_Type_CLEAR_TEXT = 1,
|
Data_Type_CLEAR_TEXT = 1,
|
||||||
@@ -121,6 +134,12 @@ typedef struct _RadioConfig_UserPreferences {
|
|||||||
char wifi_password[64];
|
char wifi_password[64];
|
||||||
bool wifi_ap_mode;
|
bool wifi_ap_mode;
|
||||||
RegionCode region;
|
RegionCode region;
|
||||||
|
LocationSharing location_share;
|
||||||
|
GpsOperation gps_operation;
|
||||||
|
uint32_t gps_update_interval;
|
||||||
|
uint32_t gps_attempt_time;
|
||||||
|
bool is_router;
|
||||||
|
bool is_low_power;
|
||||||
bool factory_reset;
|
bool factory_reset;
|
||||||
pb_size_t ignore_incoming_count;
|
pb_size_t ignore_incoming_count;
|
||||||
uint32_t ignore_incoming[3];
|
uint32_t ignore_incoming[3];
|
||||||
@@ -248,6 +267,14 @@ typedef struct _ToRadio {
|
|||||||
#define _RegionCode_MAX RegionCode_TW
|
#define _RegionCode_MAX RegionCode_TW
|
||||||
#define _RegionCode_ARRAYSIZE ((RegionCode)(RegionCode_TW+1))
|
#define _RegionCode_ARRAYSIZE ((RegionCode)(RegionCode_TW+1))
|
||||||
|
|
||||||
|
#define _GpsOperation_MIN GpsOperation_GpsOpUnset
|
||||||
|
#define _GpsOperation_MAX GpsOperation_GpsOpDisabled
|
||||||
|
#define _GpsOperation_ARRAYSIZE ((GpsOperation)(GpsOperation_GpsOpDisabled+1))
|
||||||
|
|
||||||
|
#define _LocationSharing_MIN LocationSharing_LocUnset
|
||||||
|
#define _LocationSharing_MAX LocationSharing_LocDisabled
|
||||||
|
#define _LocationSharing_ARRAYSIZE ((LocationSharing)(LocationSharing_LocDisabled+1))
|
||||||
|
|
||||||
#define _Data_Type_MIN Data_Type_OPAQUE
|
#define _Data_Type_MIN Data_Type_OPAQUE
|
||||||
#define _Data_Type_MAX Data_Type_CLEAR_READACK
|
#define _Data_Type_MAX Data_Type_CLEAR_READACK
|
||||||
#define _Data_Type_ARRAYSIZE ((Data_Type)(Data_Type_CLEAR_READACK+1))
|
#define _Data_Type_ARRAYSIZE ((Data_Type)(Data_Type_CLEAR_READACK+1))
|
||||||
@@ -266,7 +293,7 @@ typedef struct _ToRadio {
|
|||||||
#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0}
|
#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0}
|
||||||
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0}
|
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0}
|
||||||
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
|
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
|
||||||
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, 0, 0, {0, 0, 0}}
|
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
|
||||||
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
|
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
|
||||||
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0, 0, 0}
|
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0, 0, 0}
|
||||||
@@ -282,7 +309,7 @@ typedef struct _ToRadio {
|
|||||||
#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0}
|
#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0}
|
||||||
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0}
|
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0}
|
||||||
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
|
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
|
||||||
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, 0, 0, {0, 0, 0}}
|
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
|
||||||
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
|
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
|
||||||
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0, 0, 0}
|
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0, 0, 0}
|
||||||
@@ -341,7 +368,13 @@ typedef struct _ToRadio {
|
|||||||
#define RadioConfig_UserPreferences_wifi_password_tag 13
|
#define RadioConfig_UserPreferences_wifi_password_tag 13
|
||||||
#define RadioConfig_UserPreferences_wifi_ap_mode_tag 14
|
#define RadioConfig_UserPreferences_wifi_ap_mode_tag 14
|
||||||
#define RadioConfig_UserPreferences_region_tag 15
|
#define RadioConfig_UserPreferences_region_tag 15
|
||||||
|
#define RadioConfig_UserPreferences_is_router_tag 37
|
||||||
|
#define RadioConfig_UserPreferences_is_low_power_tag 38
|
||||||
#define RadioConfig_UserPreferences_factory_reset_tag 100
|
#define RadioConfig_UserPreferences_factory_reset_tag 100
|
||||||
|
#define RadioConfig_UserPreferences_location_share_tag 32
|
||||||
|
#define RadioConfig_UserPreferences_gps_operation_tag 33
|
||||||
|
#define RadioConfig_UserPreferences_gps_update_interval_tag 34
|
||||||
|
#define RadioConfig_UserPreferences_gps_attempt_time_tag 36
|
||||||
#define RadioConfig_UserPreferences_ignore_incoming_tag 103
|
#define RadioConfig_UserPreferences_ignore_incoming_tag 103
|
||||||
#define RouteDiscovery_route_tag 2
|
#define RouteDiscovery_route_tag 2
|
||||||
#define User_id_tag 1
|
#define User_id_tag 1
|
||||||
@@ -498,6 +531,12 @@ X(a, STATIC, SINGULAR, STRING, wifi_ssid, 12) \
|
|||||||
X(a, STATIC, SINGULAR, STRING, wifi_password, 13) \
|
X(a, STATIC, SINGULAR, STRING, wifi_password, 13) \
|
||||||
X(a, STATIC, SINGULAR, BOOL, wifi_ap_mode, 14) \
|
X(a, STATIC, SINGULAR, BOOL, wifi_ap_mode, 14) \
|
||||||
X(a, STATIC, SINGULAR, UENUM, region, 15) \
|
X(a, STATIC, SINGULAR, UENUM, region, 15) \
|
||||||
|
X(a, STATIC, SINGULAR, UENUM, location_share, 32) \
|
||||||
|
X(a, STATIC, SINGULAR, UENUM, gps_operation, 33) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, gps_update_interval, 34) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, gps_attempt_time, 36) \
|
||||||
|
X(a, STATIC, SINGULAR, BOOL, is_router, 37) \
|
||||||
|
X(a, STATIC, SINGULAR, BOOL, is_low_power, 38) \
|
||||||
X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \
|
X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \
|
||||||
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103)
|
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103)
|
||||||
#define RadioConfig_UserPreferences_CALLBACK NULL
|
#define RadioConfig_UserPreferences_CALLBACK NULL
|
||||||
@@ -635,11 +674,11 @@ extern const pb_msgdesc_t ManufacturingData_msg;
|
|||||||
#define SubPacket_size 274
|
#define SubPacket_size 274
|
||||||
#define MeshPacket_size 313
|
#define MeshPacket_size 313
|
||||||
#define ChannelSettings_size 84
|
#define ChannelSettings_size 84
|
||||||
#define RadioConfig_size 282
|
#define RadioConfig_size 308
|
||||||
#define RadioConfig_UserPreferences_size 193
|
#define RadioConfig_UserPreferences_size 219
|
||||||
#define NodeInfo_size 132
|
#define NodeInfo_size 132
|
||||||
#define MyNodeInfo_size 110
|
#define MyNodeInfo_size 110
|
||||||
#define DeviceState_size 5434
|
#define DeviceState_size 5460
|
||||||
#define DebugString_size 258
|
#define DebugString_size 258
|
||||||
#define FromRadio_size 322
|
#define FromRadio_size 322
|
||||||
#define ToRadio_size 316
|
#define ToRadio_size 316
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "PhoneAPI.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
void initWebServer();
|
void initWebServer();
|
||||||
|
void createSSLCert();
|
||||||
|
|
||||||
void handleNotFound();
|
void handleNotFound();
|
||||||
|
|
||||||
@@ -15,8 +17,21 @@ void notifyWebUI();
|
|||||||
|
|
||||||
void handleHotspot();
|
void handleHotspot();
|
||||||
|
|
||||||
|
|
||||||
void handleStyleCSS();
|
void handleStyleCSS();
|
||||||
void handleRoot();
|
void handleRoot();
|
||||||
void handleScriptsScriptJS();
|
void handleScriptsScriptJS();
|
||||||
void handleJSONChatHistoryDummy();
|
void handleJSONChatHistoryDummy();
|
||||||
|
|
||||||
|
|
||||||
|
class HttpAPI : public PhoneAPI
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Nothing here yet
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Nothing here yet
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Nothing here yet
|
||||||
|
};
|
||||||
1346
src/meshwifi/meshhttpStatic.h
Normal file
1346
src/meshwifi/meshhttpStatic.h
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user