mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-22 10:42:49 +00:00
Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef5cdefca6 | ||
|
|
f6f9dfa463 | ||
|
|
2161ce21df | ||
|
|
534691f0c2 | ||
|
|
6bc8e1b10a | ||
|
|
c8b95f7691 | ||
|
|
daf8594b99 | ||
|
|
5b54fd6359 | ||
|
|
53765298e1 | ||
|
|
0d94458c4e | ||
|
|
5e55695862 | ||
|
|
c9e2e6c386 | ||
|
|
dbbb62f63e | ||
|
|
79ce7d929c | ||
|
|
33437b5246 | ||
|
|
f4bacb9d87 | ||
|
|
0ac218b06d | ||
|
|
2ce1b4bb2c | ||
|
|
f002bee4f4 | ||
|
|
9c69326ed9 | ||
|
|
7a617d5378 | ||
|
|
90edae1ce0 | ||
|
|
2134b4db9b | ||
|
|
7b1ffb5c09 | ||
|
|
535f07d927 | ||
|
|
bfad136137 | ||
|
|
36bee8fa53 | ||
|
|
99cb0b3855 | ||
|
|
41c95eaff7 | ||
|
|
ca8a25f585 | ||
|
|
51a8700391 | ||
|
|
f099a31a29 | ||
|
|
52dbc4e15d | ||
|
|
8cabb3ea3d | ||
|
|
7a4a1af332 | ||
|
|
90ecdf229e | ||
|
|
24ac907780 | ||
|
|
5037fb830e | ||
|
|
79f1346359 | ||
|
|
35dada683a | ||
|
|
30a431788d | ||
|
|
d0b8adab75 | ||
|
|
74f7b7b622 | ||
|
|
9ec8562ce7 | ||
|
|
0cbcb7a9bd | ||
|
|
2874b22d6c | ||
|
|
3c9be48445 | ||
|
|
b5201f928b | ||
|
|
2591859df5 | ||
|
|
c411db111b | ||
|
|
0d62a2be85 | ||
|
|
9c971d3686 | ||
|
|
3c1357b732 | ||
|
|
acd5e5d29d | ||
|
|
64109b25f2 | ||
|
|
e93bc76ac9 | ||
|
|
1107c6d23d | ||
|
|
9b2155402d | ||
|
|
35cf8a4859 | ||
|
|
76f21dfd6e | ||
|
|
b101dc2c88 | ||
|
|
9bbd658b9d | ||
|
|
4999da0824 | ||
|
|
a783341df1 | ||
|
|
04258755e7 | ||
|
|
ea711ece1f | ||
|
|
e93187b630 | ||
|
|
eca3cda8b9 | ||
|
|
2874adfd69 | ||
|
|
91227b25df | ||
|
|
4dfac03402 | ||
|
|
9bc895eb57 | ||
|
|
474026d7fe | ||
|
|
9995d2a7ab | ||
|
|
3028ac3f24 | ||
|
|
8605950eef | ||
|
|
0f856dea7b | ||
|
|
2a2bc5b1d6 | ||
|
|
f87cb00940 | ||
|
|
a4ebf83b69 | ||
|
|
5dbacbddba | ||
|
|
011864a2d3 | ||
|
|
8646d8aeba | ||
|
|
87782cdf8f | ||
|
|
10289ed11a | ||
|
|
5def6a66e6 | ||
|
|
2d3bb4bb75 | ||
|
|
ffe8bf2be6 | ||
|
|
ab73033e51 | ||
|
|
c66e24aa4d | ||
|
|
651b13b3e5 | ||
|
|
951f5d11d5 | ||
|
|
2ce95ed2ce | ||
|
|
d1b7b6c5c5 | ||
|
|
425c570757 | ||
|
|
46dab2d496 | ||
|
|
f5271f5c79 | ||
|
|
375804c9e4 | ||
|
|
fe5703c684 | ||
|
|
f1c90a232f | ||
|
|
a91fba763a | ||
|
|
28588f6730 | ||
|
|
d074218049 | ||
|
|
1f07d6735b | ||
|
|
bb0b2e72c8 | ||
|
|
30c717ac2f | ||
|
|
e01680cd4b | ||
|
|
4bb308c80c | ||
|
|
12a68026e7 | ||
|
|
06449e39c1 | ||
|
|
cc6a8c8430 | ||
|
|
9aedec2c5c | ||
|
|
2221ddfca2 | ||
|
|
7dc6264fa7 | ||
|
|
f091a9505a | ||
|
|
57d5a54099 | ||
|
|
045a2031a3 | ||
|
|
0b8d7768fd | ||
|
|
c41f572cf5 | ||
|
|
bf26994d4e | ||
|
|
30ff67d46a | ||
|
|
65e6d029e0 | ||
|
|
31e9d34d1e | ||
|
|
0a796c4937 | ||
|
|
877e312833 | ||
|
|
cace2f4290 | ||
|
|
7b348f30ac | ||
|
|
8893be57df | ||
|
|
6b696bfdc5 | ||
|
|
a3df099f60 | ||
|
|
0ce1bbb758 | ||
|
|
bb808c6c52 | ||
|
|
15ea13c286 | ||
|
|
b0e0ee6934 | ||
|
|
f4791137fd | ||
|
|
51b53f406e | ||
|
|
bd0f35ad83 | ||
|
|
af5e3a0e02 | ||
|
|
635e189007 | ||
|
|
6eba792537 |
6
.clang-format
Normal file
6
.clang-format
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Language: Cpp
|
||||||
|
IndentWidth: 4
|
||||||
|
ColumnLimit: 130
|
||||||
|
PointerAlignment: Right
|
||||||
|
BreakBeforeBraces: Linux
|
||||||
|
AllowShortFunctionsOnASingleLine: Inline
|
||||||
21
.github/workflows/main.yml
vendored
Normal file
21
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Continuous Integration
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
name: Main
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@master
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
- name: Install Platform IO
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -U platformio
|
||||||
|
- name: Build
|
||||||
|
run: platformio run
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "proto"]
|
||||||
|
path = proto
|
||||||
|
url = https://github.com/meshtastic/Meshtastic-protobufs.git
|
||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -41,10 +41,12 @@
|
|||||||
"utility": "cpp",
|
"utility": "cpp",
|
||||||
"typeinfo": "cpp",
|
"typeinfo": "cpp",
|
||||||
"string": "cpp",
|
"string": "cpp",
|
||||||
"*.xbm": "cpp"
|
"*.xbm": "cpp",
|
||||||
|
"list": "cpp"
|
||||||
},
|
},
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"Meshtastic",
|
"Meshtastic",
|
||||||
"descs"
|
"descs",
|
||||||
|
"protobufs"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
42
README.md
42
README.md
@@ -1,6 +1,8 @@
|
|||||||
# Meshtastic-esp32
|
# Meshtastic-esp32
|
||||||
This is the device side code for the [meshtastic.org](https://www.meshtastic.org) project.
|
This is the device side code for the [meshtastic.org](https://www.meshtastic.org) project.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Meshtastic is a project that lets you use
|
Meshtastic is a project that lets you use
|
||||||
inexpensive GPS mesh radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding -
|
inexpensive GPS mesh radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding -
|
||||||
essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other
|
essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other
|
||||||
@@ -11,27 +13,27 @@ will optionally work with your phone, but no phone is required.
|
|||||||
|
|
||||||
Typical time between recharging the radios should be about eight days.
|
Typical time between recharging the radios should be about eight days.
|
||||||
|
|
||||||
This project is currently early-alpha, but if you have questions please join our chat [](https://gitter.im/Meshtastic/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge).
|
This project is currently early-alpha, but if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
|
||||||
|
|
||||||
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the chat.
|
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the chat.
|
||||||
|
|
||||||
## Supported hardware
|
## Supported hardware
|
||||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and a 18650 battery (total cost less than $35). Make
|
We currently support three models of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html), [TTGO LORA32](https://www.banggood.com/LILYGO-TTGO-LORA32-868Mhz-SX1276-ESP32-Oled-Display-bluetooth-WIFI-Lora-Development-Module-Board-p-1248652.html?cur_warehouse=UK) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and a 18650 battery (total cost less than $35). Make
|
||||||
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen
|
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen
|
||||||
is optional, but highly recommended.
|
is optional, but highly recommended.
|
||||||
|
|
||||||
See (meshtastic.org) for 3D printable cases.
|
See (meshtastic.org) for 3D printable cases.
|
||||||
|
|
||||||
## Installing the firmware
|
## Installing the firmware
|
||||||
Prebuilt binaries for the supported radios is available in our [releases](https://github.com/geeksville/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
|
Prebuilt binaries for the supported radios is available in our [releases](https://github.com/meshtastic/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
|
||||||
|
|
||||||
The instructions currently require a few commmand lines, but it should be pretty straightforward. Please post comments on our group chat if you have problems or successes. Steps to install:
|
The instructions currently require a few commmand lines, but it should be pretty straightforward. Please post comments on our group chat if you have problems or successes. Steps to install:
|
||||||
|
|
||||||
1. Purchase a radio (see above) with the correct frequencies for your country (915MHz for US or JP, 470MHz for CN, 870MHz for EU).
|
1. Purchase a radio (see above) with the correct frequencies for your country (915MHz for US or JP, 470MHz for CN, 433MHz and 870MHz for EU).
|
||||||
2. Install "pip". Pip is the python package manager we use to get the esptool installer app. Instructions [here](https://www.makeuseof.com/tag/install-pip-for-python/).
|
2. Install "pip". Pip is the python package manager we use to get the esptool installer app. Instructions [here](https://www.makeuseof.com/tag/install-pip-for-python/).
|
||||||
3. Run "pip install --upgrade esptool" to get esptool installed on your machine
|
3. Run "pip install --upgrade esptool" to get esptool installed on your machine
|
||||||
4. Connect your radio to your USB port
|
4. Connect your radio to your USB port
|
||||||
5. Confirm that your device is talking to your PC by running "esptool.py chip_id". You should see something like:
|
5. Confirm that your device is talking to your PC by running "esptool.py chip_id". The Heltec build also works on the TTGO LORA32 radio. You should see something like:
|
||||||
```
|
```
|
||||||
mydir$ esptool.py chip_id
|
mydir$ esptool.py chip_id
|
||||||
esptool.py v2.6
|
esptool.py v2.6
|
||||||
@@ -76,23 +78,25 @@ Hard resetting via RTS pin...
|
|||||||
8. Please post a comment on our chat so we know if these instructions worked for you ;-). If you find bugs/have-questions post there also - we will be rapidly iterating over the next few weeks.
|
8. Please post a comment on our chat so we know if these instructions worked for you ;-). If you find bugs/have-questions post there also - we will be rapidly iterating over the next few weeks.
|
||||||
|
|
||||||
## Meshtastic Android app
|
## Meshtastic Android app
|
||||||
The source code for the (optional) Meshtastic Android app is [here](https://github.com/geeksville/Meshtastic-Android).
|
The source code for the (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android).
|
||||||
Soon our first alpha release of will be released here:
|
|
||||||
|
Alpha test builds are current available by opting into our alpha test group. See (www.meshtastic.org) for instructions.
|
||||||
|
|
||||||
|
After our rate of change slows a bit, we will make beta builds available here (without needing to join the alphatest group):
|
||||||
[](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub%26utm_medium%3Desp32-readme%26utm_campaign%3Dmeshtastic-esp32%2520readme%26anid%3Dadmob&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)
|
[](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub%26utm_medium%3Desp32-readme%26utm_campaign%3Dmeshtastic-esp32%2520readme%26anid%3Dadmob&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)
|
||||||
|
|
||||||
# Development
|
# Development
|
||||||
The following sections are probably only interesting if you want to join us in developing the software.
|
|
||||||
|
|
||||||
## Power measurements
|
We'd love to have you join us on this merry little project. Please see our [development documents](./docs/software/sw-design.md) and [join us in our discussion forum](https://meshtastic.discourse.group/).
|
||||||
Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing).
|
|
||||||
|
|
||||||
## Build instructions
|
# Credits
|
||||||
This project uses the simple PlatformIO build system. You can use the IDE, but for brevity
|
|
||||||
in these instructions I describe use of their command line tool.
|
|
||||||
|
|
||||||
1. Purchase a suitable radio (see above)
|
This project is run by volunteers. Past contributors include:
|
||||||
2. Install [PlatformIO](https://platformio.org/platformio-ide)
|
|
||||||
3. Download this git repo and cd into it
|
* @astro-arphid: Added support for 433MHz radios in europe.
|
||||||
4. Plug the radio into your USB port
|
* @claesg: Various documentation fixes and 3D print enclosures
|
||||||
4. Type "pio run -t upload" (This command will fetch dependencies, build the project and install it on the board via USB)
|
* @girtsf: So far our CI system, but soon lots of device improvements
|
||||||
5. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it
|
|
||||||
|
# IMPORTANT DISCLAIMERS AND FAQ
|
||||||
|
|
||||||
|
For a listing of currently missing features and a FAQ click [here](docs/faq.md).
|
||||||
@@ -2,32 +2,55 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
VERSION=0.0.4
|
source bin/version.sh
|
||||||
|
|
||||||
COUNTRIES="US EU CN JP"
|
COUNTRIES="US EU433 EU865 CN JP"
|
||||||
|
# COUNTRIES=US
|
||||||
|
|
||||||
SRCMAP=.pio/build/esp32/output.map
|
SRCMAP=.pio/build/esp32/output.map
|
||||||
SRCBIN=.pio/build/esp32/firmware.bin
|
SRCBIN=.pio/build/esp32/firmware.bin
|
||||||
|
OUTDIR=release/latest
|
||||||
|
|
||||||
|
# We keep all old builds (and their map files in the archive dir)
|
||||||
|
ARCHIVEDIR=release/archive
|
||||||
|
|
||||||
|
rm -f $OUTDIR/firmware*
|
||||||
|
|
||||||
for COUNTRY in $COUNTRIES; do
|
for COUNTRY in $COUNTRIES; do
|
||||||
|
|
||||||
COMMONOPTS="-DAPP_VERSION=$VERSION -DHW_VERSION_$COUNTRY -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial"
|
HWVERSTR="1.0-$COUNTRY"
|
||||||
|
COMMONOPTS="-DAPP_VERSION=$VERSION -DHW_VERSION_$COUNTRY -DHW_VERSION=$HWVERSTR -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial"
|
||||||
|
|
||||||
export PLATFORMIO_BUILD_FLAGS="-DT_BEAM_V10 $COMMONOPTS"
|
export PLATFORMIO_BUILD_FLAGS="-DT_BEAM_V10 $COMMONOPTS"
|
||||||
echo "Building with $PLATFORMIO_BUILD_FLAGS"
|
echo "Building with $PLATFORMIO_BUILD_FLAGS"
|
||||||
rm -f $SRCBIN $SRCMAP
|
rm -f $SRCBIN $SRCMAP
|
||||||
pio run # -v
|
pio run # -v
|
||||||
cp $SRCBIN release/firmware-TBEAM-$COUNTRY-$VERSION.bin
|
cp $SRCBIN $OUTDIR/firmware-TBEAM-$COUNTRY-$VERSION.bin
|
||||||
cp $SRCMAP release/firmware-TBEAM-$COUNTRY-$VERSION.map
|
#cp $SRCMAP $ARCHIVEDIR/firmware-TBEAM-$COUNTRY-$VERSION.map
|
||||||
|
|
||||||
export PLATFORMIO_BUILD_FLAGS="-DHELTEC_LORA32 $COMMONOPTS"
|
export PLATFORMIO_BUILD_FLAGS="-DHELTEC_LORA32 $COMMONOPTS"
|
||||||
rm -f $SRCBIN $SRCMAP
|
rm -f $SRCBIN $SRCMAP
|
||||||
pio run # -v
|
pio run # -v
|
||||||
cp $SRCBIN release/firmware-HELTEC-$COUNTRY-$VERSION.bin
|
cp $SRCBIN $OUTDIR/firmware-HELTEC-$COUNTRY-$VERSION.bin
|
||||||
cp $SRCMAP release/firmware-HELTEC-$COUNTRY-$VERSION.map
|
#cp $SRCMAP $ARCHIVEDIR/firmware-HELTEC-$COUNTRY-$VERSION.map
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
zip release/firmware-$VERSION.zip release/firmware-*-$VERSION.bin
|
# keep the bins in archive also
|
||||||
|
cp $OUTDIR/firmware* $ARCHIVEDIR
|
||||||
|
|
||||||
|
cat >$OUTDIR/curfirmwareversion.xml <<XML
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!-- This file is kept in source control because it reflects the last stable
|
||||||
|
release. It is used by the android app for forcing software updates. Do not edit.
|
||||||
|
Generated by bin/buildall.sh -->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="cur_firmware_version">$VERSION</string>
|
||||||
|
</resources>
|
||||||
|
XML
|
||||||
|
|
||||||
|
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
||||||
|
zip $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/firmware-*-$VERSION.bin
|
||||||
|
|
||||||
echo BUILT ALL
|
echo BUILT ALL
|
||||||
329
bin/exception_decoder.py
Executable file
329
bin/exception_decoder.py
Executable file
@@ -0,0 +1,329 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""ESP Exception Decoder
|
||||||
|
|
||||||
|
github: https://github.com/janLo/EspArduinoExceptionDecoder
|
||||||
|
license: GPL v3
|
||||||
|
author: Jan Losinski
|
||||||
|
|
||||||
|
Meshtastic notes:
|
||||||
|
* original version is at: https://github.com/janLo/EspArduinoExceptionDecoder
|
||||||
|
* version that's checked into meshtastic repo is based on: https://github.com/me21/EspArduinoExceptionDecoder
|
||||||
|
which adds in ESP32 Backtrace decoding.
|
||||||
|
* this also updates the defaults to use ESP32, instead of ESP8266 and defaults to the built firmware.bin
|
||||||
|
|
||||||
|
To use, copy the "Backtrace: 0x...." line to a file, e.g., backtrace.txt, then run:
|
||||||
|
$ bin/exception_decoder.py backtrace.txt
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
EXCEPTIONS = [
|
||||||
|
"Illegal instruction",
|
||||||
|
"SYSCALL instruction",
|
||||||
|
"InstructionFetchError: Processor internal physical address or data error during instruction fetch",
|
||||||
|
"LoadStoreError: Processor internal physical address or data error during load or store",
|
||||||
|
"Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register",
|
||||||
|
"Alloca: MOVSP instruction, if caller's registers are not in the register file",
|
||||||
|
"IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero",
|
||||||
|
"reserved",
|
||||||
|
"Privileged: Attempt to execute a privileged operation when CRING ? 0",
|
||||||
|
"LoadStoreAlignmentCause: Load or store to an unaligned address",
|
||||||
|
"reserved",
|
||||||
|
"reserved",
|
||||||
|
"InstrPIFDataError: PIF data error during instruction fetch",
|
||||||
|
"LoadStorePIFDataError: Synchronous PIF data error during LoadStore access",
|
||||||
|
"InstrPIFAddrError: PIF address error during instruction fetch",
|
||||||
|
"LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access",
|
||||||
|
"InstTLBMiss: Error during Instruction TLB refill",
|
||||||
|
"InstTLBMultiHit: Multiple instruction TLB entries matched",
|
||||||
|
"InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING",
|
||||||
|
"reserved",
|
||||||
|
"InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch",
|
||||||
|
"reserved",
|
||||||
|
"reserved",
|
||||||
|
"reserved",
|
||||||
|
"LoadStoreTLBMiss: Error during TLB refill for a load or store",
|
||||||
|
"LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store",
|
||||||
|
"LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING",
|
||||||
|
"reserved",
|
||||||
|
"LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads",
|
||||||
|
"StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores"
|
||||||
|
]
|
||||||
|
|
||||||
|
PLATFORMS = {
|
||||||
|
"ESP8266": "lx106",
|
||||||
|
"ESP32": "esp32"
|
||||||
|
}
|
||||||
|
|
||||||
|
BACKTRACE_REGEX = re.compile(r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b")
|
||||||
|
EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$")
|
||||||
|
COUNTER_REGEX = re.compile('^epc1=(?P<epc1>0x[0-9a-f]+) epc2=(?P<epc2>0x[0-9a-f]+) epc3=(?P<epc3>0x[0-9a-f]+) '
|
||||||
|
'excvaddr=(?P<excvaddr>0x[0-9a-f]+) depc=(?P<depc>0x[0-9a-f]+)$')
|
||||||
|
CTX_REGEX = re.compile("^ctx: (?P<ctx>.+)$")
|
||||||
|
POINTER_REGEX = re.compile('^sp: (?P<sp>[0-9a-f]+) end: (?P<end>[0-9a-f]+) offset: (?P<offset>[0-9a-f]+)$')
|
||||||
|
STACK_BEGIN = '>>>stack>>>'
|
||||||
|
STACK_END = '<<<stack<<<'
|
||||||
|
STACK_REGEX = re.compile(
|
||||||
|
'^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)(\W.*)?$')
|
||||||
|
|
||||||
|
StackLine = namedtuple("StackLine", ["offset", "content"])
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionDataParser(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.exception = None
|
||||||
|
|
||||||
|
self.epc1 = None
|
||||||
|
self.epc2 = None
|
||||||
|
self.epc3 = None
|
||||||
|
self.excvaddr = None
|
||||||
|
self.depc = None
|
||||||
|
|
||||||
|
self.ctx = None
|
||||||
|
|
||||||
|
self.sp = None
|
||||||
|
self.end = None
|
||||||
|
self.offset = None
|
||||||
|
|
||||||
|
self.stack = []
|
||||||
|
|
||||||
|
def _parse_backtrace(self, line):
|
||||||
|
if line.startswith('Backtrace:'):
|
||||||
|
self.stack = [StackLine(offset=0, content=(addr,)) for addr in BACKTRACE_REGEX.findall(line)]
|
||||||
|
return None
|
||||||
|
return self._parse_backtrace
|
||||||
|
|
||||||
|
def _parse_exception(self, line):
|
||||||
|
match = EXCEPTION_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.exception = int(match.group('exc'))
|
||||||
|
return self._parse_counters
|
||||||
|
return self._parse_exception
|
||||||
|
|
||||||
|
def _parse_counters(self, line):
|
||||||
|
match = COUNTER_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.epc1 = match.group("epc1")
|
||||||
|
self.epc2 = match.group("epc2")
|
||||||
|
self.epc3 = match.group("epc3")
|
||||||
|
self.excvaddr = match.group("excvaddr")
|
||||||
|
self.depc = match.group("depc")
|
||||||
|
return self._parse_ctx
|
||||||
|
return self._parse_counters
|
||||||
|
|
||||||
|
def _parse_ctx(self, line):
|
||||||
|
match = CTX_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.ctx = match.group("ctx")
|
||||||
|
return self._parse_pointers
|
||||||
|
return self._parse_ctx
|
||||||
|
|
||||||
|
def _parse_pointers(self, line):
|
||||||
|
match = POINTER_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.sp = match.group("sp")
|
||||||
|
self.end = match.group("end")
|
||||||
|
self.offset = match.group("offset")
|
||||||
|
return self._parse_stack_begin
|
||||||
|
return self._parse_pointers
|
||||||
|
|
||||||
|
def _parse_stack_begin(self, line):
|
||||||
|
if line == STACK_BEGIN:
|
||||||
|
return self._parse_stack_line
|
||||||
|
return self._parse_stack_begin
|
||||||
|
|
||||||
|
def _parse_stack_line(self, line):
|
||||||
|
if line != STACK_END:
|
||||||
|
match = STACK_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.stack.append(StackLine(offset=match.group("off"),
|
||||||
|
content=(match.group("c1"), match.group("c2"), match.group("c3"),
|
||||||
|
match.group("c4"))))
|
||||||
|
return self._parse_stack_line
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_file(self, file, platform, stack_only=False):
|
||||||
|
if platform == 'ESP32':
|
||||||
|
func = self._parse_backtrace
|
||||||
|
else:
|
||||||
|
func = self._parse_exception
|
||||||
|
if stack_only:
|
||||||
|
func = self._parse_stack_begin
|
||||||
|
|
||||||
|
for line in file:
|
||||||
|
func = func(line.strip())
|
||||||
|
if func is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if func is not None:
|
||||||
|
print("ERROR: Parser not complete!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class AddressResolver(object):
|
||||||
|
def __init__(self, tool_path, elf_path):
|
||||||
|
self._tool = tool_path
|
||||||
|
self._elf = elf_path
|
||||||
|
self._address_map = {}
|
||||||
|
|
||||||
|
def _lookup(self, addresses):
|
||||||
|
cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None]
|
||||||
|
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
output = subprocess.check_output(cmd)
|
||||||
|
else:
|
||||||
|
output = subprocess.check_output(cmd, encoding="utf-8")
|
||||||
|
|
||||||
|
line_regex = re.compile("^(?P<addr>[0-9a-fx]+): (?P<result>.+)$")
|
||||||
|
|
||||||
|
last = None
|
||||||
|
for line in output.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
match = line_regex.match(line)
|
||||||
|
|
||||||
|
if match is None:
|
||||||
|
if last is not None and line.startswith('(inlined by)'):
|
||||||
|
line = line [12:].strip()
|
||||||
|
self._address_map[last] += ("\n \-> inlined by: " + line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if match.group("result") == '?? ??:0':
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._address_map[match.group("addr")] = match.group("result")
|
||||||
|
last = match.group("addr")
|
||||||
|
|
||||||
|
def fill(self, parser):
|
||||||
|
addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset]
|
||||||
|
for line in parser.stack:
|
||||||
|
addresses.extend(line.content)
|
||||||
|
|
||||||
|
self._lookup(addresses)
|
||||||
|
|
||||||
|
def _sanitize_addr(self, addr):
|
||||||
|
if addr.startswith("0x"):
|
||||||
|
addr = addr[2:]
|
||||||
|
|
||||||
|
fill = "0" * (8 - len(addr))
|
||||||
|
return "0x" + fill + addr
|
||||||
|
|
||||||
|
def resolve_addr(self, addr):
|
||||||
|
out = self._sanitize_addr(addr)
|
||||||
|
|
||||||
|
if out in self._address_map:
|
||||||
|
out += ": " + self._address_map[out]
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
def resolve_stack_addr(self, addr, full=True):
|
||||||
|
addr = self._sanitize_addr(addr)
|
||||||
|
if addr in self._address_map:
|
||||||
|
return addr + ": " + self._address_map[addr]
|
||||||
|
|
||||||
|
if full:
|
||||||
|
return "[DATA (0x" + addr + ")]"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def print_addr(name, value, resolver):
|
||||||
|
print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value)))
|
||||||
|
|
||||||
|
|
||||||
|
def print_stack_full(lines, resolver):
|
||||||
|
print("stack:")
|
||||||
|
for line in lines:
|
||||||
|
print(line.offset + ":")
|
||||||
|
for content in line.content:
|
||||||
|
print(" " + resolver.resolve_stack_addr(content))
|
||||||
|
|
||||||
|
|
||||||
|
def print_stack(lines, resolver):
|
||||||
|
print("stack:")
|
||||||
|
for line in lines:
|
||||||
|
for content in line.content:
|
||||||
|
out = resolver.resolve_stack_addr(content, full=False)
|
||||||
|
if out is None:
|
||||||
|
continue
|
||||||
|
print(out)
|
||||||
|
|
||||||
|
|
||||||
|
def print_result(parser, resolver, platform, full=True, stack_only=False):
|
||||||
|
if platform == 'ESP8266' and not stack_only:
|
||||||
|
print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception]))
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print_addr("epc1", parser.epc1, resolver)
|
||||||
|
print_addr("epc2", parser.epc2, resolver)
|
||||||
|
print_addr("epc3", parser.epc3, resolver)
|
||||||
|
print_addr("excvaddr", parser.excvaddr, resolver)
|
||||||
|
print_addr("depc", parser.depc, resolver)
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("ctx: " + parser.ctx)
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print_addr("sp", parser.sp, resolver)
|
||||||
|
print_addr("end", parser.end, resolver)
|
||||||
|
print_addr("offset", parser.offset, resolver)
|
||||||
|
|
||||||
|
print("")
|
||||||
|
if full:
|
||||||
|
print_stack_full(parser.stack, resolver)
|
||||||
|
else:
|
||||||
|
print_stack(parser.stack, resolver)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(description="decode ESP Stacktraces.")
|
||||||
|
|
||||||
|
parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(),
|
||||||
|
default="ESP32")
|
||||||
|
parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain",
|
||||||
|
default="~/.platformio/packages/toolchain-xtensa32/")
|
||||||
|
parser.add_argument("-e", "--elf", help="path to elf file",
|
||||||
|
default=".pio/build/esp32/firmware.elf")
|
||||||
|
parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true")
|
||||||
|
parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true")
|
||||||
|
parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-")
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
if args.file == "-":
|
||||||
|
file = sys.stdin
|
||||||
|
else:
|
||||||
|
if not os.path.exists(args.file):
|
||||||
|
print("ERROR: file " + args.file + " not found")
|
||||||
|
sys.exit(1)
|
||||||
|
file = open(args.file, "r")
|
||||||
|
|
||||||
|
addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)),
|
||||||
|
"bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line")
|
||||||
|
if os.name == 'nt':
|
||||||
|
addr2line += '.exe'
|
||||||
|
if not os.path.exists(addr2line):
|
||||||
|
print("ERROR: addr2line not found (" + addr2line + ")")
|
||||||
|
|
||||||
|
elf_file = os.path.abspath(os.path.expanduser(args.elf))
|
||||||
|
if not os.path.exists(elf_file):
|
||||||
|
print("ERROR: elf file not found (" + elf_file + ")")
|
||||||
|
|
||||||
|
parser = ExceptionDataParser()
|
||||||
|
resolver = AddressResolver(addr2line, elf_file)
|
||||||
|
|
||||||
|
parser.parse_file(file, args.platform, args.stack_only)
|
||||||
|
resolver.fill(parser)
|
||||||
|
|
||||||
|
print_result(parser, resolver, args.platform, args.full, args.stack_only)
|
||||||
6
bin/program-release-heltec.sh
Executable file
6
bin/program-release-heltec.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source bin/version.sh
|
||||||
|
|
||||||
|
esptool.py --baud 921600 write_flash 0x10000 release/latest/firmware-HELTEC-US-$VERSION.bin
|
||||||
6
bin/program-release-tbeam.sh
Executable file
6
bin/program-release-tbeam.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source bin/version.sh
|
||||||
|
|
||||||
|
esptool.py --baud 921600 write_flash 0x10000 release/latest/firmware-TBEAM-US-$VERSION.bin
|
||||||
1
bin/read-system-info.sh
Executable file
1
bin/read-system-info.sh
Executable file
@@ -0,0 +1 @@
|
|||||||
|
esptool.py --baud 921600 read_flash 0x1000 0xf000 system-info.img
|
||||||
@@ -1 +1,6 @@
|
|||||||
/home/kevinh/packages/nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:src -I=../MeshUtil/app/src/main/proto mesh.proto
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1"
|
||||||
|
# the nanopb tool seems to require that the .options file be in the current directory!
|
||||||
|
cd proto
|
||||||
|
../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src -I=../proto mesh.proto
|
||||||
|
|||||||
3
bin/version.sh
Normal file
3
bin/version.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export VERSION=0.1.8
|
||||||
9
docs/.well-known/assetlinks.json
Normal file
9
docs/.well-known/assetlinks.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[{
|
||||||
|
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.geeksville.mesh",
|
||||||
|
"sha256_cert_fingerprints":
|
||||||
|
["D0:05:E7:8B:D2:1B:FA:94:56:1D:6B:90:EB:53:07:1A:74:4F:D9:C2:6F:13:87:6A:D9:17:4F:C2:59:48:02:9D"]
|
||||||
|
}
|
||||||
|
}]
|
||||||
@@ -18,7 +18,7 @@ will optionally work with your phone, but no phone is required.
|
|||||||
[](https://www.youtube.com/watch?v=WlNbMbVZlHI "Meshtastic early demo")
|
[](https://www.youtube.com/watch?v=WlNbMbVZlHI "Meshtastic early demo")
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
Not all of these features are fully implemented yet - see below. But they should be in by the time we decide to call this project beta (three months?)
|
Not all of these features are fully implemented yet - see **important** disclaimers below. But they should be in by the time we decide to call this project beta (three months?)
|
||||||
|
|
||||||
* Very long battery life (should be about eight days with the beta software)
|
* Very long battery life (should be about eight days with the beta software)
|
||||||
* Built in GPS and [LoRa](https://en.wikipedia.org/wiki/LoRa) radio, but we manage the radio automatically for you
|
* Built in GPS and [LoRa](https://en.wikipedia.org/wiki/LoRa) radio, but we manage the radio automatically for you
|
||||||
@@ -28,51 +28,46 @@ Not all of these features are fully implemented yet - see below. But they shoul
|
|||||||
* Open and extensible codebase supporting multiple hardware vendors - no lock in to one vendor
|
* Open and extensible codebase supporting multiple hardware vendors - no lock in to one vendor
|
||||||
* Communication API for bluetooth devices (such as our Android app) to use the mesh. So if you have some application that needs long range low power networking, this might work for you.
|
* Communication API for bluetooth devices (such as our Android app) to use the mesh. So if you have some application that needs long range low power networking, this might work for you.
|
||||||
* Eventually (within a couple of months) we should have a modified version of Signal that works with this project.
|
* Eventually (within a couple of months) we should have a modified version of Signal that works with this project.
|
||||||
* Very easy sharing of private secured channels. Just share a special link or QR code with other users and they can join your encrypted mesh
|
* Very easy sharing of private secured channels. Just share a special link or QR code with friends and they can join your encrypted mesh
|
||||||
|
|
||||||
This project is currently in early alpha - if you have questions please join our chat [](https://gitter.im/Meshtastic/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge).
|
This project is currently in early alpha - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
|
||||||
|
|
||||||
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the [chat](https://gitter.im/Meshtastic/community).
|
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the [forum](https://meshtastic.discourse.group/).
|
||||||
|
|
||||||
# Update 1
|
# Updates
|
||||||
|
|
||||||
* 02/20/2020 - Our first alpha release of the radio software is ready for early users. If you'd like to try it, we'd love your feedback. Click [here](https://github.com/geeksville/Meshtastic-esp32/blob/master/README.md) for instructions.
|
Note: Updates are happening almost daily, only major updates are listed below. For more details see our chat, github releases or the Android alpha tester emails.
|
||||||
|
|
||||||
|
* 03/03/2020 - 0.0.9 of the Android app and device code is released. Still an alpha but fairly functional.
|
||||||
|
* 02/25/2020 - 0.0.4 of the Android app is released. This is a very early alpha, see below to join the alpha-testers group.
|
||||||
|
* 02/23/2020 - 0.0.4 release. Still very bleeding edge but much closer to the final power management, a charged T-BEAM should run for many days with this load. If you'd like to try it, we'd love your feedback. Click [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md) for instructions.
|
||||||
|
* 02/20/2020 - Our first alpha release (0.0.3) of the radio software is ready brave early people.
|
||||||
|
|
||||||
## Meshtastic Android app
|
## Meshtastic Android app
|
||||||
Soon our (optional) companion Android application will be released here:
|
Once out of alpha the companion Android application will be released here:
|
||||||
|
|
||||||
[](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dhomepage%26anid%3Dadmob)
|
[](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dhomepage%26anid%3Dadmob)
|
||||||
|
|
||||||
If you would like to join our super bleeding-edge alpha test group for this app, we'd love to have you. Three steps:
|
But if you want the bleeding edge app now, we'd love to have your help testing. Three steps to opt-in to the alpha- test:
|
||||||
|
|
||||||
1. Join [this Google group](https://groups.google.com/forum/#!forum/meshtastic-alpha-testers) with the account you use in Google Play.
|
1. Join [this Google group](https://groups.google.com/forum/#!forum/meshtastic-alpha-testers) with the account you use in Google Play.
|
||||||
2. Go to this [URL](https://play.google.com/apps/testing/com.geeksville.mesh) to opt-in to the alpha test.
|
2. Go to this [URL](https://play.google.com/apps/testing/com.geeksville.mesh) to opt-in to the alpha test.
|
||||||
3. If you encounter any problems or have questions, post in our gitter chat and we'll help.
|
3. If you encounter any problems or have questions, post in our [forum](https://meshtastic.discourse.group/) and we'll help.
|
||||||
|
|
||||||
If you'd like to help with development, the source code is [on github](https://github.com/geeksville/Meshtastic-Android).
|
If you'd like to help with development, the source code is [on github](https://github.com/meshtastic/Meshtastic-Android).
|
||||||
|
|
||||||
## Supported hardware
|
## Supported hardware
|
||||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and a 18650 battery (total cost less than $35). Make
|
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most people should buy the T-Beam and a 18650 battery (total cost less than $35). Make
|
||||||
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen
|
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen is optional, but highly recommended.
|
||||||
is optional, but highly recommended.
|
|
||||||
|
|
||||||
Instructions for installing prebuilt firmware can be found [here](https://github.com/geeksville/Meshtastic-esp32/blob/master/README.md).
|
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
|
||||||
|
|
||||||
For a nice TTGO 3D printable case see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
|
For a nice printable cases:
|
||||||
For a nice Heltec 3D printable case see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
|
|
||||||
|
|
||||||
# Disclaimers
|
1. TTGO T-Beam V0 see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
|
||||||
|
2. TTGO T_Beam V1 see this [design](https://www.thingiverse.com/thing:3830711) by [rwanrooy](https://www.thingiverse.com/rwanrooy) or this [remix](https://www.thingiverse.com/thing:3949330) by [8ung](https://www.thingiverse.com/8ung)
|
||||||
|
3. Heltec Lora32 see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
|
||||||
|
|
||||||
This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds.
|
# IMPORTANT DISCLAIMERS AND FAQ
|
||||||
Most of these problems should be solved by the beta release:
|
|
||||||
|
|
||||||
* We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-)
|
For a listing of currently missing features and a FAQ click [here](faq.md).
|
||||||
* Encryption is turned off for now
|
|
||||||
* A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about two days. Join us on chat if you want the spreadsheet of power measurements/calculations.
|
|
||||||
* The current Android GUI is pretty ugly still
|
|
||||||
* The Android API needs to be documented better
|
|
||||||
* The Bluetooth API needs to be documented better
|
|
||||||
* The mesh protocol is turned off for now, currently we only send packets one hop distant
|
|
||||||
* No one has written an iOS app yet ;-)
|
|
||||||
|
|
||||||
For more details see the [device software TODO](https://github.com/geeksville/Meshtastic-esp32/blob/master/TODO.md) or the [Android app TODO](https://github.com/geeksville/Meshtastic-Android/blob/master/TODO.md).
|
|
||||||
|
|||||||
36
docs/faq.md
Normal file
36
docs/faq.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Disclaimers
|
||||||
|
|
||||||
|
This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds.
|
||||||
|
Most of these problems should be solved by the beta release (within three months):
|
||||||
|
|
||||||
|
* We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-)
|
||||||
|
* Encryption is turned off for now
|
||||||
|
* A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations.
|
||||||
|
* The current Android GUI is slightly ugly still
|
||||||
|
* The Android API needs to be documented better
|
||||||
|
* The mesh protocol is turned off for now, currently we only send packets one hop distant. The mesh feature will be turned on again [soonish](https://github.com/meshtastic/Meshtastic-esp32/issues/3).
|
||||||
|
* No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-)
|
||||||
|
|
||||||
|
For more details see the [device software TODO](https://github.com/meshtastic/Meshtastic-esp32/blob/master/docs/software/TODO.md) or the [Android app TODO](https://github.com/meshtastic/Meshtastic-Android/blob/master/TODO.md).
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
If you have a question missing from this faq, please [ask in our discussion forum](https://meshtastic.discourse.group/). And if you are feeling extra generous send in a pull-request for this faq.md with whatever we answered ;-).
|
||||||
|
|
||||||
|
## Q: Which of the various supported radios should I buy?
|
||||||
|
|
||||||
|
Basically you just need the radio + (optional but recommended) battery. The TBEAM is usually better because it has gps and huge battery socket. The Heltec is basically the same hardware but without the GPS (the phone provides position data to the radio in that case, so the behavior is similar - but it does burn some battery in the phone). Also the battery for the Heltec can be smaller.
|
||||||
|
|
||||||
|
In addition to Aliexpress, (banggood.com) usually has stock and faster shipping, or Amazon. If buying a TBEAM, make sure to buy a version that includes the OLED screen - this project doesn't absolutely require the screen, but we use it if is installed.
|
||||||
|
|
||||||
|
@claesg has added links to various 3D printable cases, you can see them at (www.meshtastic.org).
|
||||||
|
|
||||||
|
## Q: Do you have plans to commercialize this project
|
||||||
|
|
||||||
|
Nope. though if some other person/group wanted to use this software and a more customized device we think that would be awesome (as long as they obey the GPL license).
|
||||||
|
|
||||||
|
## Q: Does this project use patented algorithms?
|
||||||
|
|
||||||
|
(Kindly borrowed from the geeks at [ffmpeg](http://ffmpeg.org/legal.html))
|
||||||
|
|
||||||
|
We do not know, we are not lawyers so we are not qualified to answer this. Also we have never read patents to implement any part of this, so even if we were qualified we could not answer it as we do not know what is patented. Furthermore the sheer number of software patents makes it impossible to read them all so no one (lawyer or not) could answer such a question with a definite no. We are merely geeks experimenting on a fun and free project.
|
||||||
BIN
docs/hardware/AN1200_22_Semtech_LoRa_Basics_v2_STD.pdf
Normal file
BIN
docs/hardware/AN1200_22_Semtech_LoRa_Basics_v2_STD.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/AXP192 Datasheet_v1.1_en_draft_2211.pdf
Normal file
BIN
docs/hardware/AXP192 Datasheet_v1.1_en_draft_2211.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/DS_SX1276-7-8-9_W_APP_V6.pdf
Normal file
BIN
docs/hardware/DS_SX1276-7-8-9_W_APP_V6.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/NEO-6_DataSheet_(GPS.G6-HW-09005).pdf
Normal file
BIN
docs/hardware/NEO-6_DataSheet_(GPS.G6-HW-09005).pdf
Normal file
Binary file not shown.
BIN
docs/hardware/RFM95_96_97_98W.pdf
Normal file
BIN
docs/hardware/RFM95_96_97_98W.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/T_BeamV1.0.pdf
Normal file
BIN
docs/hardware/T_BeamV1.0.pdf
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.
BIN
docs/hardware/axp192-complete-datasheet-chinese.pdf
Normal file
BIN
docs/hardware/axp192-complete-datasheet-chinese.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/esp32_datasheet_en.pdf
Normal file
BIN
docs/hardware/esp32_datasheet_en.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/lora-fcc-requirements.pdf
Normal file
BIN
docs/hardware/lora-fcc-requirements.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/pe4259ds-rf-switch.pdf
Normal file
BIN
docs/hardware/pe4259ds-rf-switch.pdf
Normal file
Binary file not shown.
20
docs/mesh-proto.md
Normal file
20
docs/mesh-proto.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
TODO:
|
||||||
|
* reread the radiohead mesh implementation
|
||||||
|
* read about general mesh flooding solutions
|
||||||
|
* reread the disaster radio protocol docs
|
||||||
|
|
||||||
|
good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept
|
||||||
|
|
||||||
|
interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf
|
||||||
|
It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728
|
||||||
|
https://en.wikipedia.org/wiki/Dynamic_Source_Routing
|
||||||
|
|
||||||
|
broadcast solution:
|
||||||
|
Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent)
|
||||||
|
Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set
|
||||||
|
|
||||||
|
compare to db sync algorithm?
|
||||||
|
|
||||||
|
what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies.
|
||||||
|
|
||||||
|
If Y were to die, at least the neighbor nodes of Y would have their last known position of Y.
|
||||||
@@ -5,7 +5,7 @@ We don't collect any personal identifying information.
|
|||||||
If you have opted-in to analytics (thank you - that helps us know what things we need to improve), we'll receive anonymized information about user behavior. i.e. which screens you used in the app etc... We never
|
If you have opted-in to analytics (thank you - that helps us know what things we need to improve), we'll receive anonymized information about user behavior. i.e. which screens you used in the app etc... We never
|
||||||
capture usernames, the contents of your texts or your location.
|
capture usernames, the contents of your texts or your location.
|
||||||
|
|
||||||
This is an open-source project run by hobbyists and we try to be completely transparent. If you have questions on this policy, please file [a github issue](https://github.com/geeksville/meshtastic-esp32/issues) and we'll reply/clarify/correct.
|
This is an open-source project run by hobbyists and we try to be completely transparent. If you have questions on this policy, please file [a github issue](https://github.com/meshtastic/meshtastic-esp32/issues) and we'll reply/clarify/correct.
|
||||||
|
|
||||||
Keep being awesome!
|
Keep being awesome!
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ See [this site](https://www.rfwireless-world.com/Tutorials/LoRa-channels-list.ht
|
|||||||
|
|
||||||
The maximum power allowed is +14dBM.
|
The maximum power allowed is +14dBM.
|
||||||
|
|
||||||
|
### 433 MHz
|
||||||
|
|
||||||
|
There are eight channels defined with a 0.2 MHz gap between them.
|
||||||
|
Channel zero starts at 433.175 MHz
|
||||||
|
|
||||||
|
### 870 MHz
|
||||||
|
|
||||||
There are eight channels defined with a 0.3 MHz gap between them.
|
There are eight channels defined with a 0.3 MHz gap between them.
|
||||||
Channel zero starts at 865.20 MHz
|
Channel zero starts at 865.20 MHz
|
||||||
|
|
||||||
|
|||||||
@@ -1,99 +1,96 @@
|
|||||||
# High priority
|
# High priority
|
||||||
|
|
||||||
Items to complete before the first alpha release.
|
Items to complete soon (next couple of alpha releases).
|
||||||
|
|
||||||
* have state machine properly enter deep sleep based on loss of mesh and phone comms
|
|
||||||
* default to enter deep sleep if no LORA received for two hours (indicates user has probably left the meshS)
|
|
||||||
* if the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs
|
|
||||||
for it (because it will redownload the nodedb when it comes back)
|
|
||||||
|
|
||||||
* lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time
|
* lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time
|
||||||
being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch.
|
being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch.
|
||||||
|
|
||||||
F95::canSleep say no if we are busy receiving a message
|
* use gps sleep mode instead of killing its power (to allow fast position when we wake)
|
||||||
|
* enable fast lock and low power inside the gps chip
|
||||||
|
|
||||||
* retest BLE software update for both board types
|
|
||||||
* send note about Adafruit Clue
|
|
||||||
* send note to the guy who designed the cases
|
|
||||||
* remeasure wake time power draws now that we run CPU down at 80MHz
|
* remeasure wake time power draws now that we run CPU down at 80MHz
|
||||||
|
* add a SF12 transmit option for _super_ long range
|
||||||
|
|
||||||
|
# AXP192 tasks
|
||||||
|
* "AXP192 interrupt is not firing, remove this temporary polling of battery state"
|
||||||
|
* make debug info screen show real data (including battery level & charging) - close corresponding github issue
|
||||||
|
|
||||||
# Medium priority
|
# Medium priority
|
||||||
|
|
||||||
Items to complete before the first beta release.
|
Items to complete before the first beta release.
|
||||||
|
|
||||||
* turn light sleep on aggressively (while lora is on but BLE off)
|
* Make a FAQ
|
||||||
* sync wake windows to gps time
|
* good source of battery/signal/gps icons https://materialdesignicons.com/
|
||||||
|
* investigate changing routing to https://github.com/sudomesh/LoRaLayer2 ?
|
||||||
|
* check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/
|
||||||
|
* use fuse bits to store the board type and region. So one load can be used on all boards
|
||||||
* research and implement better mesh algorithm
|
* research and implement better mesh algorithm
|
||||||
* the BLE stack is leaking about 200 bytes each time we go to light sleep
|
* the BLE stack is leaking about 200 bytes each time we go to light sleep
|
||||||
* use gps sleep mode instead of killing its power (to allow fast position when we wake)
|
|
||||||
* leave lora receiver always on
|
|
||||||
* rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms.
|
* rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms.
|
||||||
* assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel"
|
* assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel"
|
||||||
* Note: we do not do address filtering at the chip level, because we might need to route for the mesh
|
* Note: we do not do address filtering at the chip level, because we might need to route for the mesh
|
||||||
* Use the Periodic class for both position and user periodic broadcasts
|
* add basic crypto - https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation - use ECB at first (though it is shit) because it doesn't require us to send 16 bytes of IV with each packet. Then OFB per example
|
||||||
* make debug info screen show real data (including battery level & charging)
|
* add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting
|
||||||
* don't forward redundant pings or ping responses to the phone, it just wastes phone battery
|
* share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.
|
||||||
* don't treat north as up, instead adjust shown bearings for our guess at the users heading (i.e. subtract one from the other)
|
|
||||||
* answer to pings (because some other user is looking at our nodeinfo) with our latest location
|
|
||||||
* show radio and gps signal strength as an image
|
|
||||||
* only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app.
|
|
||||||
* use https://platformio.org/lib/show/1260/OneButton if necessary
|
|
||||||
* make an about to sleep screen
|
|
||||||
* don't send location packets if we haven't moved
|
|
||||||
* scrub default radio config settings for bandwidth/range/speed
|
|
||||||
* add basic crypto - http://rweather.github.io/arduinolibs/crypto.html with speck https://www.airspayce.com/mikem/arduino/RadioHead/rf95_encrypted_client_8pde-example.html
|
|
||||||
* override peekAtMessage so we can see any messages that pass through our node (even if not broadcast)? would that be useful?
|
|
||||||
* sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread
|
|
||||||
* How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95?
|
|
||||||
* use std::map<NodeInfo*, std::string> in node db
|
|
||||||
* make a HAM build: yep - that's a great idea. I'll add it to the TODO. should be pretty painless - just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts
|
|
||||||
* add frequency hopping
|
|
||||||
* publish update articles on the web
|
* publish update articles on the web
|
||||||
|
|
||||||
# Pre-beta priority
|
# Pre-beta priority
|
||||||
|
|
||||||
During the beta timeframe the following improvements 'would be nice' (and yeah - I guess some of these items count as features, but it is a hobby project ;-) )
|
During the beta timeframe the following improvements 'would be nice' (and yeah - I guess some of these items count as features, but it is a hobby project ;-) )
|
||||||
|
|
||||||
|
* figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?"
|
||||||
|
* If the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs
|
||||||
|
for it (because it will redownload the nodedb when it comes back)
|
||||||
* Figure out why the RF95 ISR is never seeing RH_RF95_VALID_HEADER, so it is not protecting our rx packets from getting stomped on by sends
|
* Figure out why the RF95 ISR is never seeing RH_RF95_VALID_HEADER, so it is not protecting our rx packets from getting stomped on by sends
|
||||||
* use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability
|
|
||||||
* make an install script to let novices install software on their boards
|
|
||||||
* fix the frequency error reading in the RF95 RX code (can't do floating point math in an ISR ;-)
|
* fix the frequency error reading in the RF95 RX code (can't do floating point math in an ISR ;-)
|
||||||
* See CustomRF95::send and fix the problem of dropping partially received packets if we want to start sending
|
* See CustomRF95::send and fix the problem of dropping partially received packets if we want to start sending
|
||||||
* swap out speck for hw-accelerated full AES https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/esp32/hwcrypto/aes.h
|
|
||||||
* use variable length arduino Strings in protobufs (instead of current fixed buffers)
|
|
||||||
* don't even power on bluetooth until we have some data to send to the android phone. Most of the time we should be sleeping in a lowpower "listening for lora" only mode. Once we have some packets for the phone, then power on bluetooth
|
|
||||||
until the phone pulls those packets. Ever so often power on bluetooth just so we can see if the phone wants to send some packets. Possibly might need ULP processor to help with this wake process.
|
|
||||||
* do hibernation mode to get power draw down to 2.5uA https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/
|
|
||||||
* make sure main cpu is not woken for packets with bad crc or not addressed to this node - do that in the radio hw
|
* make sure main cpu is not woken for packets with bad crc or not addressed to this node - do that in the radio hw
|
||||||
* enable fast init inside the gps chip
|
|
||||||
* triple check fcc compliance
|
* triple check fcc compliance
|
||||||
* pick channel center frequency based on name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? Or is that too opaque?
|
* pick channel center frequency based on channel name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? allows us to hide the concept of channel # from hte user.
|
||||||
* scan to find channels with low background noise?
|
* scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels)
|
||||||
* share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.
|
* make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
|
||||||
|
* if radio params change fundamentally, discard the nodedb
|
||||||
|
* reneable the bluetooth battery level service on the T-BEAM, because we can read battery level there
|
||||||
|
|
||||||
|
# Spinoff project ideas
|
||||||
|
|
||||||
|
* an open source version of https://www.burnair.ch/skynet/
|
||||||
|
* a paragliding app like http://airwhere.co.uk/
|
||||||
|
* a version with a solar cell for power, just mounted high to permanently provide routing for nodes in a valley. Someone just pointed me at disaster.radio
|
||||||
|
* How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95?
|
||||||
|
* provide generalized (but slow) internet message forwarding servie if one of our nodes has internet connectivity
|
||||||
|
|
||||||
# Low priority
|
# Low priority
|
||||||
|
|
||||||
Items after the first final candidate release.
|
Items after the first final candidate release.
|
||||||
|
|
||||||
* Use CAD mode of the RF95 to automatically find low noise channels
|
* use variable length arduino Strings in protobufs (instead of current fixed buffers)
|
||||||
|
* use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability
|
||||||
|
* make an install script to let novices install software on their boards
|
||||||
|
* use std::map<NodeInfo*, std::string> in node db
|
||||||
|
* make a HAM build: yep - that's a great idea. I'll add it to the TODO. should be pretty painless - just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts
|
||||||
|
* don't forward redundant pings or ping responses to the phone, it just wastes phone battery
|
||||||
|
* use https://platformio.org/lib/show/1260/OneButton if necessary
|
||||||
|
* don't send location packets if we haven't moved
|
||||||
|
* scrub default radio config settings for bandwidth/range/speed
|
||||||
|
* answer to pings (because some other user is looking at our nodeinfo) with our latest location (not a stale location)
|
||||||
|
* show radio and gps signal strength as an image
|
||||||
|
* only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app.
|
||||||
|
* make mesh aware network timing state machine (sync wake windows to gps time)
|
||||||
|
* split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
||||||
* read the PMU battery fault indicators and blink/led/warn user on screen
|
* read the PMU battery fault indicators and blink/led/warn user on screen
|
||||||
* make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
|
|
||||||
* the AXP debug output says it is trying to charge at 700mA, but the max I've seen is 180mA, so AXP registers probably need to be set to tell them the circuit can only provide 300mAish max. So that the low charge rate kicks in faster and we don't wear out batteries.
|
* the AXP debug output says it is trying to charge at 700mA, but the max I've seen is 180mA, so AXP registers probably need to be set to tell them the circuit can only provide 300mAish max. So that the low charge rate kicks in faster and we don't wear out batteries.
|
||||||
* increase the max charging rate a bit for 18650s, currently it limits to 180mA (at 4V). Work backwards from the 500mA USB limit (at 5V) and let the AXP charge at that rate.
|
* increase the max charging rate a bit for 18650s, currently it limits to 180mA (at 4V). Work backwards from the 500mA USB limit (at 5V) and let the AXP charge at that rate.
|
||||||
* if radio params change fundamentally, discard the nodedb
|
|
||||||
* discard very old nodedb records (> 1wk)
|
* discard very old nodedb records (> 1wk)
|
||||||
* using the genpartitions based table doesn't work on TTGO so for now I stay with my old memory map
|
* using the genpartitions based table doesn't work on TTGO so for now I stay with my old memory map
|
||||||
* We let anyone BLE scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured)
|
* We let anyone BLE scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured)
|
||||||
* use two different buildenv flags for ttgo vs lora32. https://docs.platformio.org/en/latest/ide/vscode.html#key-bindings
|
* use two different buildenv flags for ttgo vs lora32. https://docs.platformio.org/en/latest/ide/vscode.html#key-bindings
|
||||||
* sim gps data for testing nodes that don't have hardware
|
* sim gps data for testing nodes that don't have hardware
|
||||||
* have android provide position data for nodes that don't have gps
|
|
||||||
* do debug serial logging to android over bluetooth
|
* do debug serial logging to android over bluetooth
|
||||||
* break out my bluetooth OTA software as a seperate library so others can use it
|
* break out my bluetooth OTA software as a seperate library so others can use it
|
||||||
* Heltec LoRa32 has 8MB flash, use a bigger partition table if needed - TTGO is 4MB but has PSRAM
|
* Heltec LoRa32 has 8MB flash, use a bigger partition table if needed - TTGO is 4MB but has PSRAM
|
||||||
* add a watchdog timer
|
* add a watchdog timer
|
||||||
* fix GPS.zeroOffset calculation it is wrong
|
|
||||||
* handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
* handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
||||||
* reneable the bluetooth battery level service on the T-BEAM, because we can read battery level there
|
|
||||||
* report esp32 device code bugs back to the mothership via android
|
* report esp32 device code bugs back to the mothership via android
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
@@ -176,3 +173,31 @@ Items after the first final candidate release.
|
|||||||
* don't enter NB state if we've recently talked to the phone (to prevent breaking syncing or bluetooth sw update)
|
* don't enter NB state if we've recently talked to the phone (to prevent breaking syncing or bluetooth sw update)
|
||||||
* have sw update prevent BLE sleep
|
* have sw update prevent BLE sleep
|
||||||
* manually delete characteristics/descs
|
* manually delete characteristics/descs
|
||||||
|
* leave lora receiver always on
|
||||||
|
* protobufs are sometimes corrupted after sleep!
|
||||||
|
* stay awake while charging
|
||||||
|
* check gps battery voltage
|
||||||
|
* if a position report includes ground truth time and we don't have time yet, set our clock from that. It is better than nothing.
|
||||||
|
* retest BLE software update for both board types
|
||||||
|
* report on wikifactory
|
||||||
|
* send note to the guy who designed the cases
|
||||||
|
* turn light sleep on aggressively (while lora is on but BLE off)
|
||||||
|
* Use the Periodic class for both position and user periodic broadcasts
|
||||||
|
* don't treat north as up, instead adjust shown bearings for our guess at the users heading (i.e. subtract one from the other)
|
||||||
|
* sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread
|
||||||
|
* don't even power on bluetooth until we have some data to send to the android phone. Most of the time we should be sleeping in a lowpower "listening for lora" only mode. Once we have some packets for the phone, then power on bluetooth
|
||||||
|
until the phone pulls those packets. Ever so often power on bluetooth just so we can see if the phone wants to send some packets. Possibly might need ULP processor to help with this wake process.
|
||||||
|
* do hibernation mode to get power draw down to 2.5uA https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/
|
||||||
|
* fix GPS.zeroOffset calculation it is wrong
|
||||||
|
* (needs testing) fixed the following during a plane flight:
|
||||||
|
Have state machine properly enter deep sleep based on loss of mesh and phone comms.
|
||||||
|
Default to enter deep sleep if no LORA received for two hours (indicates user has probably left the mesh).
|
||||||
|
* (fixed I think) text messages are not showing on local screen if screen was on
|
||||||
|
* add links to todos
|
||||||
|
* link to the kanban page
|
||||||
|
* add a getting started page
|
||||||
|
* finish mesh alg reeval
|
||||||
|
* ublox gps parsing seems a little buggy (we shouldn't be sending out read solution commands, the device is already broadcasting them)
|
||||||
|
* turn on gps https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino
|
||||||
|
* switch gps to 38400 baud https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino
|
||||||
|
* Use Neo-M8M API to put it in sleep mode
|
||||||
110
docs/software/bluetooth-api.md
Normal file
110
docs/software/bluetooth-api.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Bluetooth API
|
||||||
|
|
||||||
|
The Bluetooth API is design to have only a few characteristics and most polymorphism comes from the flexible set of Google Protocol Buffers which are sent over the wire. We use protocol buffers extensively both for the bluetooth API and for packets inside the mesh or when providing packets to other applications on the phone.
|
||||||
|
|
||||||
|
## A note on MTU sizes
|
||||||
|
|
||||||
|
This device will work with any MTU size, but it is highly recommended that you call your phone's "setMTU function to increase MTU to 512 bytes" as soon as you connect to a service. This will dramatically improve performance when reading/writing packets.
|
||||||
|
|
||||||
|
## MeshBluetoothService
|
||||||
|
|
||||||
|
This is the main bluetooth service for the device and provides the API your app should use to get information about the mesh, send packets or provision the radio.
|
||||||
|
|
||||||
|
For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt). Typical flow when
|
||||||
|
a phone connects to the device should be the following:
|
||||||
|
|
||||||
|
* SetMTU size to 512
|
||||||
|
* Read a RadioConfig from "radio" - used to get the channel and radio settings
|
||||||
|
* Read (and write if incorrect) a User from "user" - to get the username for this node
|
||||||
|
* Read a MyNodeInfo from "mynode" to get information about this local device
|
||||||
|
* Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine
|
||||||
|
* Read from "nodeinfo" until it returns empty to build the phone's copy of the current NodeDB for the mesh
|
||||||
|
* Read from "fromradio" until it returns empty to get any messages that arrived for this node while the phone was away
|
||||||
|
* Subscribe to notify on "fromnum" to get notified whenever the device has a new received packet
|
||||||
|
* Read that new packet from "fromradio"
|
||||||
|
* Whenever the phone has a packet to send write to "toradio"
|
||||||
|
|
||||||
|
For definitions (and documentation) on FromRadio, ToRadio, MyNodeInfo, NodeInfo and User protocol buffers see [mesh.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/mesh.proto)
|
||||||
|
|
||||||
|
UUID for the service: 6ba1b218-15a8-461f-9fa8-5dcae273eafd
|
||||||
|
|
||||||
|
Each characteristic is listed as follows:
|
||||||
|
|
||||||
|
UUID
|
||||||
|
Properties
|
||||||
|
Description (including human readable name)
|
||||||
|
|
||||||
|
8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||||
|
read
|
||||||
|
fromradio - contains a newly received FromRadio packet destined towards the phone (up to MAXPACKET bytes per packet).
|
||||||
|
After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this
|
||||||
|
mailbox.
|
||||||
|
|
||||||
|
f75c76d2-129e-4dad-a1dd-7866124401e7
|
||||||
|
write
|
||||||
|
toradio - write ToRadio protobufs to this characteristic to send them (up to MAXPACKET len)
|
||||||
|
|
||||||
|
ed9da18c-a800-4f66-a670-aa7547e34453
|
||||||
|
read,notify,write
|
||||||
|
fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages
|
||||||
|
until it catches up with this number.
|
||||||
|
|
||||||
|
The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio.
|
||||||
|
When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio.
|
||||||
|
|
||||||
|
Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted.
|
||||||
|
|
||||||
|
ea9f3f82-8dc4-4733-9452-1f6da28892a2
|
||||||
|
read
|
||||||
|
mynode - read this to access a MyNodeInfo protobuf
|
||||||
|
|
||||||
|
d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8
|
||||||
|
read, write
|
||||||
|
nodeinfo - read this to get a series of NodeInfos (ending with a null empty record), write to this to restart the read statemachine that returns all the node infos
|
||||||
|
|
||||||
|
b56786c8-839a-44a1-b98e-a1724c4a0262
|
||||||
|
read,write
|
||||||
|
radio - read/write this to access a RadioConfig protobuf
|
||||||
|
|
||||||
|
6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6
|
||||||
|
read,write
|
||||||
|
owner - read/write this to access a User protobuf
|
||||||
|
|
||||||
|
Re: queue management
|
||||||
|
Not all messages are kept in the fromradio queue (filtered based on SubPacket):
|
||||||
|
* only the most recent Position and User messages for a particular node are kept
|
||||||
|
* all Data SubPackets are kept
|
||||||
|
* No WantNodeNum / DenyNodeNum messages are kept
|
||||||
|
A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging)
|
||||||
|
|
||||||
|
|
||||||
|
## Other bluetooth services
|
||||||
|
|
||||||
|
This document focuses on the core mesh service, but it is worth noting that the following other Bluetooth services are also
|
||||||
|
provided by the device.
|
||||||
|
|
||||||
|
### BluetoothSoftwareUpdate
|
||||||
|
|
||||||
|
The software update service. For a sample function that performs a software update using this API see [startUpdate](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt).
|
||||||
|
|
||||||
|
SoftwareUpdateService UUID cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30
|
||||||
|
|
||||||
|
Characteristics
|
||||||
|
|
||||||
|
| UUID | properties | description|
|
||||||
|
|--------------------------------------|------------------|------------|
|
||||||
|
| e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e | write,read | total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted) |
|
||||||
|
| e272ebac-d463-4b98-bc84-5cc1a39ee517 | write | data, variable sized, recommended 512 bytes, write one for each block of file |
|
||||||
|
| 4826129c-c22a-43a3-b066-ce8f0d5bacc6 | write | crc32, write last - writing this will complete the OTA operation, now you can read result |
|
||||||
|
| 5e134862-7411-4424-ac4a-210937432c77 | read,notify | result code, readable but will notify when the OTA operation completes |
|
||||||
|
| GATT_UUID_SW_VERSION_STR/0x2a28 | read | We also implement these standard GATT entries because SW update probably needs them: |
|
||||||
|
| GATT_UUID_MANU_NAME/0x2a29 | read | |
|
||||||
|
| GATT_UUID_HW_VERSION_STR/0x2a27 | read | |
|
||||||
|
|
||||||
|
### DeviceInformationService
|
||||||
|
|
||||||
|
Implements the standard BLE contract for this service (has software version, hardware model, serial number, etc...)
|
||||||
|
|
||||||
|
### BatteryLevelService
|
||||||
|
|
||||||
|
Implements the standard BLE contract service, provides battery level in a way that most client devices should automatically understand (i.e. it should show in the bluetooth devices screen automatically)
|
||||||
25
docs/software/build-instructions.md
Normal file
25
docs/software/build-instructions.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Build instructions
|
||||||
|
|
||||||
|
This project uses the simple PlatformIO build system. You can use the IDE, but for brevity
|
||||||
|
in these instructions I describe use of their command line tool.
|
||||||
|
|
||||||
|
1. Purchase a suitable radio (see above)
|
||||||
|
2. Install [PlatformIO](https://platformio.org/platformio-ide)
|
||||||
|
3. Download this git repo and cd into it
|
||||||
|
4. Edit configuration.h and comment out *one* of the following two lines (depending on which board you are using):
|
||||||
|
```
|
||||||
|
// #define T_BEAM_V10
|
||||||
|
#define HELTEC_LORA32
|
||||||
|
```
|
||||||
|
5. Plug the radio into your USB port
|
||||||
|
6. Type "pio run -t upload" (This command will fetch dependencies, build the project and install it on the board via USB)
|
||||||
|
7. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it
|
||||||
|
|
||||||
|
|
||||||
|
## Decoding stack traces
|
||||||
|
|
||||||
|
If you get a crash, you can decode the addresses from the `Backtrace:` line:
|
||||||
|
1. Save the `Backtrace: 0x....` line to a file, e.g., `backtrace.txt`.
|
||||||
|
2. Run `bin/exception_decoder.py backtrace.txt` (this uses symbols from the
|
||||||
|
last `firmware.elf`, so you must be running the same binary that's still in
|
||||||
|
your `.pio/build` directory).
|
||||||
72
docs/software/mesh-alg.md
Normal file
72
docs/software/mesh-alg.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Mesh broadcast algorithm
|
||||||
|
|
||||||
|
FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If
|
||||||
|
all else fails could always use the stock Radiohead solution - though super inefficient.
|
||||||
|
|
||||||
|
great source of papers and class notes: http://www.cs.jhu.edu/~cs647/
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
* DONE reread the radiohead mesh implementation - hop to hop acknoledgement seems VERY expensive but otherwise it seems like DSR
|
||||||
|
* DONE read about mesh routing solutions (DSR and AODV)
|
||||||
|
* DONE read about general mesh flooding solutions (naive, MPR, geo assisted)
|
||||||
|
* DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish)
|
||||||
|
* possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack
|
||||||
|
* update duty cycle spreadsheet for our typical usecase
|
||||||
|
* generalize naive flooding on top of radiohead or disaster.radio? (and fix radiohead to use my new driver)
|
||||||
|
|
||||||
|
good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept
|
||||||
|
|
||||||
|
interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf
|
||||||
|
It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728
|
||||||
|
https://en.wikipedia.org/wiki/Dynamic_Source_Routing
|
||||||
|
|
||||||
|
broadcast solution:
|
||||||
|
Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent)
|
||||||
|
Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set
|
||||||
|
|
||||||
|
compare to db sync algorithm?
|
||||||
|
|
||||||
|
what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies.
|
||||||
|
|
||||||
|
If Y were to die, at least the neighbor nodes of Y would have their last known position of Y.
|
||||||
|
|
||||||
|
## approach 1
|
||||||
|
|
||||||
|
* send all broadcasts with a TTL
|
||||||
|
* periodically(?) do a survey to find the max TTL that is needed to fully cover the current network.
|
||||||
|
* to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request)
|
||||||
|
* survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets)
|
||||||
|
* count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts
|
||||||
|
* repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up.
|
||||||
|
* it is important for any node to do listen before talk to prevent stomping on other rebroadcasters...
|
||||||
|
* For these little networks I bet a max TTL would never be higher than 3?
|
||||||
|
|
||||||
|
## approach 2
|
||||||
|
|
||||||
|
* send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis)
|
||||||
|
* we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?"
|
||||||
|
* if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to)
|
||||||
|
* given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful
|
||||||
|
* once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast
|
||||||
|
|
||||||
|
## approach 3
|
||||||
|
|
||||||
|
* when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?)
|
||||||
|
* all nodes constantly update their local db based on replies they witnessed.
|
||||||
|
* after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf.
|
||||||
|
* could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set).
|
||||||
|
|
||||||
|
## approach 4
|
||||||
|
|
||||||
|
look into the literature for this idea specifically.
|
||||||
|
|
||||||
|
* don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together
|
||||||
|
to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the
|
||||||
|
other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually).
|
||||||
|
* handle group messages the same way, there would be a table of messages and time of creation.
|
||||||
|
* when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet).
|
||||||
|
* Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date.
|
||||||
|
* essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to
|
||||||
|
build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db.
|
||||||
|
* construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here?
|
||||||
|
* possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database.
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
This is a mini design doc for various core behaviors...
|
# Power Management State Machine
|
||||||
|
|
||||||
# Rules for sleep
|
i.e. sleep behavior
|
||||||
|
|
||||||
|
## Power measurements
|
||||||
|
|
||||||
|
Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing).
|
||||||
|
|
||||||
## States
|
## States
|
||||||
|
|
||||||
From lower to higher power consumption.
|
From lower to higher power consumption.
|
||||||
|
|
||||||
* Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press
|
* Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press. We enter this mode only after no radio comms for a few hours, used to put the device into what is effectively "off" mode.
|
||||||
onEntry: setBluetoothOn(false), call doDeepSleep
|
onEntry: setBluetoothOn(false), call doDeepSleep
|
||||||
onExit: (standard bootup code, starts in DARK)
|
onExit: (standard bootup code, starts in DARK)
|
||||||
|
|
||||||
@@ -53,8 +57,9 @@ off during light sleep, but there is a TODO item to fix this.
|
|||||||
* While in ON: If it has been more than screen_on_secs since a press, lower to DARK
|
* While in ON: If it has been more than screen_on_secs since a press, lower to DARK
|
||||||
* While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode
|
* While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode
|
||||||
* While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state
|
* While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state
|
||||||
* While in into LS: If either phone_sds_timeout_secs (default 1 hr) or mesh_sds_timeout_secs (default 1 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 hr) (or a button press).
|
* While in LS: If either phone_sds_timeout_secs (default 2 hr) or mesh_sds_timeout_secs (default 2 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 yr) (or a button press). (Note: phone_sds_timeout_secs is currently disabled for now, because most users
|
||||||
* Any time we enter LS mode: We stay in that mode for ls_secs (default 1 hr) (or until an interrupt, button press)
|
are using without a phone)
|
||||||
|
* Any time we enter LS mode: We stay in that until an interrupt, button press or other state transition. Every ls_secs (default 1 hr) and let the arduino loop() run one iteration (FIXME, not sure if we need this at all), and then immediately reenter lightsleep mode on the CPU.
|
||||||
|
|
||||||
TODO: Eventually these scheduled intervals should be synchronized to the GPS clock, so that we can consider leaving the lora receiver off to save even more power.
|
TODO: Eventually these scheduled intervals should be synchronized to the GPS clock, so that we can consider leaving the lora receiver off to save even more power.
|
||||||
TODO: In NB mode we should put cpu into light sleep any time we really aren't that busy (without declaring LS state) - i.e. we should leave GPS on etc...
|
TODO: In NB mode we should put cpu into light sleep any time we really aren't that busy (without declaring LS state) - i.e. we should leave GPS on etc...
|
||||||
@@ -78,49 +83,3 @@ General ideas to hit the power draws our spreadsheet predicts. Do the easy ones
|
|||||||
* see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO.
|
* see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO.
|
||||||
* never enter deep sleep while connected to USB power (but still go to other low power modes)
|
* never enter deep sleep while connected to USB power (but still go to other low power modes)
|
||||||
* when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt.
|
* when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt.
|
||||||
|
|
||||||
# Mesh broadcast algoritm
|
|
||||||
|
|
||||||
FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If
|
|
||||||
all else fails could always use the stock radiohead solution - though super inefficent.
|
|
||||||
|
|
||||||
## approach 1
|
|
||||||
|
|
||||||
* send all broadcasts with a TTL
|
|
||||||
* periodically(?) do a survey to find the max TTL that is needed to fully cover the current network.
|
|
||||||
* to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request)
|
|
||||||
* survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets)
|
|
||||||
* count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts
|
|
||||||
* repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up.
|
|
||||||
* it is important for any node to do listen before talk to prevent stomping on other rebroadcasters...
|
|
||||||
* For these little networks I bet a max TTL would never be higher than 3?
|
|
||||||
|
|
||||||
## approach 2
|
|
||||||
|
|
||||||
* send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis)
|
|
||||||
* we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?"
|
|
||||||
* if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to)
|
|
||||||
* given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful
|
|
||||||
* once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast
|
|
||||||
|
|
||||||
## approach 3
|
|
||||||
|
|
||||||
* when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?)
|
|
||||||
* all nodes constantly update their local db based on replies they witnessed.
|
|
||||||
* after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf.
|
|
||||||
* could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set).
|
|
||||||
|
|
||||||
## approach 4
|
|
||||||
|
|
||||||
look into the literature for this idea specifically.
|
|
||||||
|
|
||||||
* don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together
|
|
||||||
to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the
|
|
||||||
other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually).
|
|
||||||
* handle group messages the same way, there would be a table of messages and time of creation.
|
|
||||||
* when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet).
|
|
||||||
* Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date.
|
|
||||||
* essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to
|
|
||||||
build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db.
|
|
||||||
* construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here?
|
|
||||||
* possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database.
|
|
||||||
9
docs/software/sw-design.md
Normal file
9
docs/software/sw-design.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
This is a mini design doc for developing the meshtastic software.
|
||||||
|
|
||||||
|
* [Build instructions](build-instructions.md)
|
||||||
|
* [TODO](TODO.md) - read this if you are looking for things to do (or curious about currently missing features)
|
||||||
|
* Our [project board](https://github.com/orgs/meshtastic/projects/1) - shows what things we are currently working on and remaining work items for the current release.
|
||||||
|
* [Power Management](power.md)
|
||||||
|
* [Mesh algorithm](mesh-alg.md)
|
||||||
|
* [Bluetooth API](bluetooth-api.md) and porting guide for new clients (iOS, python, etc...)
|
||||||
|
* TODO: how to port the device code to a new device.
|
||||||
BIN
images/system-info.bin
Normal file
BIN
images/system-info.bin
Normal file
Binary file not shown.
@@ -2,13 +2,11 @@
|
|||||||
"name": "BluetoothOTA",
|
"name": "BluetoothOTA",
|
||||||
"keywords": "esp32, bluetooth",
|
"keywords": "esp32, bluetooth",
|
||||||
"description": "A BTLE based software OTA update tool",
|
"description": "A BTLE based software OTA update tool",
|
||||||
"repository":
|
"repository": {
|
||||||
{
|
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/geeksville/fixme.git"
|
"url": "https://github.com/geeksville/fixme.git"
|
||||||
},
|
},
|
||||||
"authors":
|
"authors": [
|
||||||
[
|
|
||||||
{
|
{
|
||||||
"name": "Kevin Hester",
|
"name": "Kevin Hester",
|
||||||
"email": "kevinh@geeksville.com",
|
"email": "kevinh@geeksville.com",
|
||||||
@@ -20,7 +18,15 @@
|
|||||||
"frameworks": "arduino",
|
"frameworks": "arduino",
|
||||||
"platforms": "*",
|
"platforms": "*",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{ "name": "Update" },
|
{
|
||||||
{ "name": "ESP32 BLE Arduino" }
|
"name": "Update"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ESP32 BLE Arduino"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "arduino-fsm",
|
||||||
|
"version": "https://github.com/meshtastic/arduino-fsm.git"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,13 @@ public:
|
|||||||
if (!canBegin)
|
if (!canBegin)
|
||||||
// Indicate failure by forcing the size to 0
|
// Indicate failure by forcing the size to 0
|
||||||
c->setValue(0UL);
|
c->setValue(0UL);
|
||||||
|
else {
|
||||||
|
// This totally breaks abstraction to up up into the app layer for this, but quick hack to make sure we only
|
||||||
|
// talk to one service during the sw update.
|
||||||
|
//DEBUG_MSG("FIXME, crufty shutdown of mesh bluetooth for sw update.");
|
||||||
|
//void stopMeshBluetoothService();
|
||||||
|
//stopMeshBluetoothService();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,6 +108,8 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void bluetoothRebootCheck()
|
void bluetoothRebootCheck()
|
||||||
{
|
{
|
||||||
if (rebootAtMsec && millis() > rebootAtMsec)
|
if (rebootAtMsec && millis() > rebootAtMsec)
|
||||||
@@ -108,20 +117,13 @@ void bluetoothRebootCheck()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
SoftwareUpdateService UUID cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30
|
See bluetooth-api.md
|
||||||
|
|
||||||
Characteristics
|
|
||||||
|
|
||||||
UUID properties description
|
|
||||||
e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e write|read total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted)
|
|
||||||
e272ebac-d463-4b98-bc84-5cc1a39ee517 write data, variable sized, recommended 512 bytes, write one for each block of file
|
|
||||||
4826129c-c22a-43a3-b066-ce8f0d5bacc6 write crc32, write last - writing this will complete the OTA operation, now you can read result
|
|
||||||
5e134862-7411-4424-ac4a-210937432c77 read|notify result code, readable but will notify when the OTA operation completes
|
|
||||||
*/
|
*/
|
||||||
BLEService *createUpdateService(BLEServer *server)
|
BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion)
|
||||||
{
|
{
|
||||||
// Create the BLE Service
|
// Create the BLE Service
|
||||||
BLEService *service = server->createService("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30");
|
BLEService *service = server->createService(BLEUUID("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30"), 25, 0);
|
||||||
|
|
||||||
assert(!resultC);
|
assert(!resultC);
|
||||||
resultC = new BLECharacteristic("5e134862-7411-4424-ac4a-210937432c77", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
resultC = new BLECharacteristic("5e134862-7411-4424-ac4a-210937432c77", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||||
@@ -133,10 +135,23 @@ BLEService *createUpdateService(BLEServer *server)
|
|||||||
|
|
||||||
resultC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
resultC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
||||||
|
|
||||||
|
BLECharacteristic *swC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||||
|
swC->setValue(swVersion);
|
||||||
|
service->addCharacteristic(addBLECharacteristic(swC));
|
||||||
|
|
||||||
|
BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ);
|
||||||
|
mfC->setValue(hwVendor);
|
||||||
|
service->addCharacteristic(addBLECharacteristic(mfC));
|
||||||
|
|
||||||
|
BLECharacteristic *hwvC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||||
|
hwvC->setValue(hwVersion);
|
||||||
|
service->addCharacteristic(addBLECharacteristic(hwvC));
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroyUpdateService() {
|
void destroyUpdateService()
|
||||||
|
{
|
||||||
assert(resultC);
|
assert(resultC);
|
||||||
|
|
||||||
resultC = NULL;
|
resultC = NULL;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
BLEService *createUpdateService(BLEServer* server);
|
BLEService *createUpdateService(BLEServer* server, std::string hwVendor, std::string swVersion, std::string hwVersion);
|
||||||
|
|
||||||
void destroyUpdateService();
|
void destroyUpdateService();
|
||||||
void bluetoothRebootCheck();
|
void bluetoothRebootCheck();
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Update.h>
|
#include <Update.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "screen.h"
|
|
||||||
|
|
||||||
SimpleAllocator btPool;
|
SimpleAllocator btPool;
|
||||||
|
|
||||||
@@ -173,7 +172,7 @@ uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue)
|
|||||||
|
|
||||||
class MySecurity : public BLESecurityCallbacks
|
class MySecurity : public BLESecurityCallbacks
|
||||||
{
|
{
|
||||||
|
protected:
|
||||||
bool onConfirmPIN(uint32_t pin)
|
bool onConfirmPIN(uint32_t pin)
|
||||||
{
|
{
|
||||||
Serial.printf("onConfirmPIN %u\n", pin);
|
Serial.printf("onConfirmPIN %u\n", pin);
|
||||||
@@ -189,7 +188,7 @@ class MySecurity : public BLESecurityCallbacks
|
|||||||
void onPassKeyNotify(uint32_t pass_key)
|
void onPassKeyNotify(uint32_t pass_key)
|
||||||
{
|
{
|
||||||
Serial.printf("onPassKeyNotify %u\n", pass_key);
|
Serial.printf("onPassKeyNotify %u\n", pass_key);
|
||||||
screen_start_bluetooth(pass_key);
|
startCb(pass_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool onSecurityRequest()
|
bool onSecurityRequest()
|
||||||
@@ -211,9 +210,13 @@ class MySecurity : public BLESecurityCallbacks
|
|||||||
Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason);
|
Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove our custom screen
|
// Remove our custom PIN request screen.
|
||||||
screen_set_frames();
|
stopCb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
StartBluetoothPinScreenCallback startCb;
|
||||||
|
StopBluetoothPinScreenCallback stopCb;
|
||||||
};
|
};
|
||||||
|
|
||||||
BLEServer *pServer;
|
BLEServer *pServer;
|
||||||
@@ -255,7 +258,10 @@ void deinitBLE()
|
|||||||
btPool.reset();
|
btPool.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion)
|
BLEServer *initBLE(
|
||||||
|
StartBluetoothPinScreenCallback startBtPinScreen,
|
||||||
|
StopBluetoothPinScreenCallback stopBtPinScreen,
|
||||||
|
std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion)
|
||||||
{
|
{
|
||||||
BLEDevice::init(deviceName);
|
BLEDevice::init(deviceName);
|
||||||
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
|
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
|
||||||
@@ -264,6 +270,8 @@ BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swV
|
|||||||
* Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation
|
* Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation
|
||||||
*/
|
*/
|
||||||
static MySecurity mySecurity;
|
static MySecurity mySecurity;
|
||||||
|
mySecurity.startCb = startBtPinScreen;
|
||||||
|
mySecurity.stopCb = stopBtPinScreen;
|
||||||
BLEDevice::setSecurityCallbacks(&mySecurity);
|
BLEDevice::setSecurityCallbacks(&mySecurity);
|
||||||
|
|
||||||
// Create the BLE Server
|
// Create the BLE Server
|
||||||
@@ -276,7 +284,7 @@ BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swV
|
|||||||
// We now let users create the battery service only if they really want (not all devices have a battery)
|
// We now let users create the battery service only if they really want (not all devices have a battery)
|
||||||
// BLEService *pBattery = createBatteryService(pServer);
|
// BLEService *pBattery = createBatteryService(pServer);
|
||||||
|
|
||||||
pUpdate = createUpdateService(pServer); // We need to advertise this so our android ble scan operation can see it
|
pUpdate = createUpdateService(pServer, hwVendor, swVersion, hwVersion); // We need to advertise this so our android ble scan operation can see it
|
||||||
|
|
||||||
// It seems only one service can be advertised - so for now don't advertise our updater
|
// It seems only one service can be advertised - so for now don't advertise our updater
|
||||||
// pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID());
|
// pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID());
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <BLEDevice.h>
|
#include <BLEDevice.h>
|
||||||
#include <BLEServer.h>
|
#include <BLEServer.h>
|
||||||
@@ -17,8 +19,14 @@ void dumpCharacteristic(BLECharacteristic *c);
|
|||||||
/** converting endianness pull out a 32 bit value */
|
/** converting endianness pull out a 32 bit value */
|
||||||
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue);
|
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue);
|
||||||
|
|
||||||
|
// TODO(girts): create a class for the bluetooth utils helpers?
|
||||||
|
using StartBluetoothPinScreenCallback = std::function<void(uint32_t pass_key)>;
|
||||||
|
using StopBluetoothPinScreenCallback = std::function<void(void)>;
|
||||||
|
|
||||||
void loopBLE();
|
void loopBLE();
|
||||||
BLEServer *initBLE(std::string devName, std::string hwVendor, std::string swVersion, std::string hwVersion = "");
|
BLEServer *initBLE(
|
||||||
|
StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoothPinScreenCallback stopBtPinScreen,
|
||||||
|
std::string devName, std::string hwVendor, std::string swVersion, std::string hwVersion = "");
|
||||||
void deinitBLE();
|
void deinitBLE();
|
||||||
|
|
||||||
/// Add a characteristic that we will delete when we restart
|
/// Add a characteristic that we will delete when we restart
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../MeshUtil/app/src/main/proto/mesh.options
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../MeshUtil/app/src/main/proto/mesh.proto
|
|
||||||
@@ -21,8 +21,8 @@ framework = arduino
|
|||||||
board_build.partitions = partition-table.csv
|
board_build.partitions = partition-table.csv
|
||||||
|
|
||||||
; note: we add src to our include search path so that lmic_project_config can override
|
; note: we add src to our include search path so that lmic_project_config can override
|
||||||
; FIXME fix dependencies on arduino-fsm
|
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
||||||
build_flags = -Wall -Wextra -Wno-missing-field-initializers -I.pio/libdeps/esp32/arduino-fsm -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial
|
build_flags = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/esp32/output.map -DAXP_DEBUG_PORT=Serial
|
||||||
|
|
||||||
; not needed included in ttgo-t-beam board file
|
; not needed included in ttgo-t-beam board file
|
||||||
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
|
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
|
||||||
@@ -55,16 +55,18 @@ debug_tool = jlink
|
|||||||
|
|
||||||
debug_init_break = tbreak setup
|
debug_init_break = tbreak setup
|
||||||
|
|
||||||
|
; Note: some libraries are specified by #ID where there are conflicting library
|
||||||
|
; names.
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/geeksville/RadioHead.git
|
https://github.com/meshtastic/RadioHead.git
|
||||||
TinyGPSPlus
|
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||||
https://github.com/geeksville/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
|
||||||
AXP202X_Library
|
AXP202X_Library
|
||||||
SPI
|
SPI
|
||||||
OneButton
|
; 1260 ; OneButton - not used yet
|
||||||
CRC32 ; explicitly needed because dependency is missing in the ble ota update lib
|
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
||||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||||
https://github.com/geeksville/arduino-fsm.git
|
https://github.com/meshtastic/arduino-fsm.git
|
||||||
|
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
|
||||||
|
|
||||||
;[env:tbeam]
|
;[env:tbeam]
|
||||||
;board = ttgo-t-beam
|
;board = ttgo-t-beam
|
||||||
|
|||||||
1
proto
Submodule
1
proto
Submodule
Submodule proto added at 398fdf3625
1
release/latest/.gitignore
vendored
Normal file
1
release/latest/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
curfirmwareversion.xml
|
||||||
@@ -21,6 +21,7 @@ CustomRF95::CustomRF95(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &
|
|||||||
bool CustomRF95::canSleep()
|
bool CustomRF95::canSleep()
|
||||||
{
|
{
|
||||||
// We allow initializing mode, because sometimes while testing we don't ever call init() to turn on the hardware
|
// We allow initializing mode, because sometimes while testing we don't ever call init() to turn on the hardware
|
||||||
|
DEBUG_MSG("canSleep, mode=%d, isRx=%d, txEmpty=%d, txGood=%d\n", _mode, _isReceiving, txQueue.isEmpty(), _txGood);
|
||||||
return (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !_isReceiving && txQueue.isEmpty();
|
return (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !_isReceiving && txQueue.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,8 +126,7 @@ void CustomRF95::handleInterrupt()
|
|||||||
// parsing was successful, queue for our recipient
|
// parsing was successful, queue for our recipient
|
||||||
mp->has_payload = true;
|
mp->has_payload = true;
|
||||||
|
|
||||||
int res = rxDest.enqueueFromISR(mp, &higherPriWoken); // NOWAIT - fixme, if queue is full, delete older messages
|
assert(rxDest.enqueueFromISR(mp, &higherPriWoken)); // NOWAIT - fixme, if queue is full, delete older messages
|
||||||
assert(res == pdTRUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearRxBuf(); // This message accepted and cleared
|
clearRxBuf(); // This message accepted and cleared
|
||||||
|
|||||||
166
src/GPS.cpp
166
src/GPS.cpp
@@ -1,30 +1,81 @@
|
|||||||
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
|
#include "configuration.h"
|
||||||
#include "time.h"
|
#include "time.h"
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include "configuration.h"
|
|
||||||
|
|
||||||
// stuff that really should be in in the instance instead...
|
|
||||||
HardwareSerial _serial_gps(GPS_SERIAL_NUM);
|
HardwareSerial _serial_gps(GPS_SERIAL_NUM);
|
||||||
uint32_t timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
|
|
||||||
uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
|
|
||||||
|
|
||||||
RTC_DATA_ATTR bool timeSetFromGPS; // We only reset our time once per _boot_ after that point just run from the internal clock (even across sleeps)
|
RTC_DATA_ATTR bool timeSetFromGPS; // We only reset our time once per _boot_ after that point just run from the internal clock
|
||||||
|
// (even across sleeps)
|
||||||
|
|
||||||
GPS gps;
|
GPS gps;
|
||||||
bool hasValidLocation; // default to false, until we complete our first read
|
|
||||||
bool wantNewLocation = true;
|
|
||||||
|
|
||||||
GPS::GPS() : PeriodicTask()
|
// 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
|
||||||
|
|
||||||
|
static bool hasValidLocation; // default to false, until we complete our first read
|
||||||
|
static bool wantNewLocation = true;
|
||||||
|
|
||||||
|
GPS::GPS() : PeriodicTask() {}
|
||||||
|
|
||||||
void GPS::setup()
|
void GPS::setup()
|
||||||
{
|
{
|
||||||
readFromRTC();
|
readFromRTC(); // read the main CPU RTC at first
|
||||||
|
|
||||||
#ifdef GPS_RX_PIN
|
#ifdef GPS_RX_PIN
|
||||||
_serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
_serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||||
|
// _serial_gps.setRxBufferSize(1024); // the default is 256
|
||||||
|
// ublox.enableDebugging(Serial);
|
||||||
|
|
||||||
|
// note: the lib's implementation has the wrong docs for what the return val is
|
||||||
|
// it is not a bool, it returns zero for success
|
||||||
|
isConnected = ublox.begin(_serial_gps);
|
||||||
|
|
||||||
|
// try a second time, the ublox lib serial parsing is buggy?
|
||||||
|
if(!isConnected) isConnected = ublox.begin(_serial_gps);
|
||||||
|
|
||||||
|
if (isConnected) {
|
||||||
|
DEBUG_MSG("Connected to GPS successfully, TXpin=%d\n", GPS_TX_PIN);
|
||||||
|
|
||||||
|
bool factoryReset = false;
|
||||||
|
bool ok;
|
||||||
|
if (factoryReset) {
|
||||||
|
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have
|
||||||
|
// GPS_TX connected)
|
||||||
|
ublox.factoryReset();
|
||||||
|
delay(2000);
|
||||||
|
isConnected = ublox.begin(_serial_gps);
|
||||||
|
DEBUG_MSG("Factory reset success=%d\n", isConnected);
|
||||||
|
if (isConnected) {
|
||||||
|
ublox.assumeAutoPVT(true, true); // Just parse NEMA for now
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API
|
||||||
|
assert(ok);
|
||||||
|
ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low
|
||||||
|
assert(ok);
|
||||||
|
// ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M
|
||||||
|
// assert(ok);
|
||||||
|
// ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds
|
||||||
|
// assert(ok);
|
||||||
|
ok = ublox.powerSaveMode(); // use power save mode
|
||||||
|
assert(ok);
|
||||||
|
}
|
||||||
|
ok = ublox.saveConfiguration(2000);
|
||||||
|
assert(ok);
|
||||||
|
} else {
|
||||||
|
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
||||||
|
// assume NEMA at 9600 baud.
|
||||||
|
DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n");
|
||||||
|
|
||||||
|
// tell lib, we are expecting the module to send PVT messages by itself to our Rx pin
|
||||||
|
// you can set second parameter to "false" if you want to control the parsing and eviction of the data (need to call
|
||||||
|
// checkUblox cyclically)
|
||||||
|
ublox.assumeAutoPVT(true, true);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,8 +83,7 @@ void GPS::readFromRTC()
|
|||||||
{
|
{
|
||||||
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
||||||
|
|
||||||
if (!gettimeofday(&tv, NULL))
|
if (!gettimeofday(&tv, NULL)) {
|
||||||
{
|
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
|
|
||||||
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
|
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
|
||||||
@@ -45,8 +95,7 @@ void GPS::readFromRTC()
|
|||||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||||
void GPS::perhapsSetRTC(const struct timeval *tv)
|
void GPS::perhapsSetRTC(const struct timeval *tv)
|
||||||
{
|
{
|
||||||
if (!timeSetFromGPS)
|
if (!timeSetFromGPS) {
|
||||||
{
|
|
||||||
timeSetFromGPS = true;
|
timeSetFromGPS = true;
|
||||||
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
||||||
settimeofday(tv, NULL);
|
settimeofday(tv, NULL);
|
||||||
@@ -75,70 +124,83 @@ uint32_t GPS::getValidTime()
|
|||||||
/// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock)
|
/// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock)
|
||||||
bool GPS::canSleep()
|
bool GPS::canSleep()
|
||||||
{
|
{
|
||||||
return !wantNewLocation;
|
return true; // we leave GPS on during sleep now, so sleep is okay !wantNewLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||||
void GPS::prepareSleep()
|
void GPS::prepareSleep()
|
||||||
{
|
{
|
||||||
// discard all rx serial bytes so we don't try to parse them when we come back
|
if (isConnected)
|
||||||
while (_serial_gps.available())
|
ublox.powerOff();
|
||||||
{
|
|
||||||
_serial_gps.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
// make the parser bail on whatever it was parsing
|
|
||||||
encode('\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPS::doTask()
|
void GPS::doTask()
|
||||||
{
|
{
|
||||||
#ifdef GPS_RX_PIN
|
#ifdef GPS_RX_PIN
|
||||||
|
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
|
||||||
|
|
||||||
|
if (isConnected) {
|
||||||
// Consume all characters that have arrived
|
// Consume all characters that have arrived
|
||||||
|
|
||||||
while (_serial_gps.available())
|
// getPVT automatically calls checkUblox
|
||||||
{
|
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
||||||
encode(_serial_gps.read());
|
|
||||||
// DEBUG_MSG("Got GPS response\n");
|
// If we don't have a fix (a quick check), don't try waiting for a solution)
|
||||||
|
// Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions
|
||||||
|
// turn off for now
|
||||||
|
// fixtype = ublox.getFixType();
|
||||||
|
DEBUG_MSG("fix type %d\n", fixtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!timeSetFromGPS && time.isValid() && date.isValid())
|
// DEBUG_MSG("sec %d\n", ublox.getSecond());
|
||||||
{
|
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
|
||||||
|
|
||||||
|
// any fix that has time
|
||||||
|
if (!timeSetFromGPS && ublox.getT()) {
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
|
||||||
DEBUG_MSG("Got time from GPS\n");
|
|
||||||
|
|
||||||
/* Convert to unix time
|
/* 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).
|
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;
|
struct tm t;
|
||||||
t.tm_sec = time.second();
|
t.tm_sec = ublox.getSecond();
|
||||||
t.tm_min = time.minute();
|
t.tm_min = ublox.getMinute();
|
||||||
t.tm_hour = time.hour();
|
t.tm_hour = ublox.getHour();
|
||||||
t.tm_mday = date.day();
|
t.tm_mday = ublox.getDay();
|
||||||
t.tm_mon = date.month() - 1;
|
t.tm_mon = ublox.getMonth() - 1;
|
||||||
t.tm_year = date.year() - 1900;
|
t.tm_year = ublox.getYear() - 1900;
|
||||||
t.tm_isdst = false;
|
t.tm_isdst = false;
|
||||||
time_t res = mktime(&t);
|
time_t res = mktime(&t);
|
||||||
tv.tv_sec = res;
|
tv.tv_sec = res;
|
||||||
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
|
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);
|
||||||
|
|
||||||
perhapsSetRTC(&tv);
|
perhapsSetRTC(&tv);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
if (location.isValid() && location.isUpdated())
|
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only
|
||||||
{ // we only notify if position has changed
|
{
|
||||||
// DEBUG_MSG("new gps pos\n");
|
// we only notify if position has changed
|
||||||
hasValidLocation = true;
|
latitude = ublox.getLatitude() * 1e-7;
|
||||||
|
longitude = ublox.getLongitude() * 1e-7;
|
||||||
|
altitude = ublox.getAltitude() / 1000; // in mm convert to meters
|
||||||
|
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude, longitude, altitude);
|
||||||
|
|
||||||
|
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
|
||||||
|
if (hasValidLocation) {
|
||||||
wantNewLocation = false;
|
wantNewLocation = false;
|
||||||
notifyObservers();
|
notifyObservers();
|
||||||
|
// ublox.powerOff();
|
||||||
}
|
}
|
||||||
else // we didn't get a location update, go back to sleep and hope the characters show up
|
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
||||||
wantNewLocation = true;
|
wantNewLocation = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 100ms until we have something over the serial
|
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over
|
||||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 100);
|
// the serial
|
||||||
|
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPS::startLock()
|
void GPS::startLock()
|
||||||
@@ -147,11 +209,3 @@ void GPS::startLock()
|
|||||||
wantNewLocation = true;
|
wantNewLocation = true;
|
||||||
setPeriod(1);
|
setPeriod(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
String GPS::getTimeStr()
|
|
||||||
{
|
|
||||||
static char t[12]; // used to sprintf for Serial output
|
|
||||||
|
|
||||||
snprintf(t, sizeof(t), "%02d:%02d:%02d", time.hour(), time.minute(), time.second());
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|||||||
13
src/GPS.h
13
src/GPS.h
@@ -1,18 +1,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <TinyGPS++.h>
|
|
||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "sys/time.h"
|
#include "sys/time.h"
|
||||||
|
#include "SparkFun_Ublox_Arduino_Library.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 : public PeriodicTask, public Observable, public TinyGPSPlus
|
class GPS : public PeriodicTask, public Observable
|
||||||
{
|
{
|
||||||
|
SFE_UBLOX_GPS ublox;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
double latitude, longitude;
|
||||||
|
uint32_t altitude;
|
||||||
|
bool isConnected; // Do we have a GPS we are talking to
|
||||||
|
|
||||||
GPS();
|
GPS();
|
||||||
|
|
||||||
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
|
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
|
||||||
@@ -21,8 +27,6 @@ public:
|
|||||||
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
|
/// Return time since 1970 in secs. If we don't have a GPS lock return zero
|
||||||
uint32_t getValidTime();
|
uint32_t getValidTime();
|
||||||
|
|
||||||
String getTimeStr();
|
|
||||||
|
|
||||||
void setup();
|
void setup();
|
||||||
|
|
||||||
virtual void loop();
|
virtual void loop();
|
||||||
@@ -46,3 +50,4 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
extern GPS gps;
|
extern GPS gps;
|
||||||
|
|
||||||
|
|||||||
@@ -66,16 +66,14 @@ public:
|
|||||||
/// Return a buffer for use by others
|
/// Return a buffer for use by others
|
||||||
void release(T *p)
|
void release(T *p)
|
||||||
{
|
{
|
||||||
int res = dead.enqueue(p, 0);
|
assert(dead.enqueue(p, 0));
|
||||||
assert(res == pdTRUE);
|
|
||||||
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-)
|
/// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-)
|
||||||
void releaseFromISR(T *p, BaseType_t *higherPriWoken)
|
void releaseFromISR(T *p, BaseType_t *higherPriWoken)
|
||||||
{
|
{
|
||||||
int res = dead.enqueueFromISR(p, higherPriWoken);
|
assert(dead.enqueueFromISR(p, higherPriWoken));
|
||||||
assert(res == pdTRUE);
|
|
||||||
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "CallbackCharacteristic.h"
|
#include "CallbackCharacteristic.h"
|
||||||
|
|
||||||
|
#include "GPS.h"
|
||||||
|
|
||||||
// This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in proccess at once
|
// This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in proccess at once
|
||||||
static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)];
|
static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)];
|
||||||
|
|
||||||
@@ -33,8 +35,8 @@ public:
|
|||||||
void onRead(BLECharacteristic *c)
|
void onRead(BLECharacteristic *c)
|
||||||
{
|
{
|
||||||
BLEKeepAliveCallbacks::onRead(c);
|
BLEKeepAliveCallbacks::onRead(c);
|
||||||
DEBUG_MSG("Got proto read\n");
|
|
||||||
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), fields, my_struct);
|
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), fields, my_struct);
|
||||||
|
DEBUG_MSG("pbread from %s returns %d bytes\n", c->getUUID().toString().c_str(), numbytes);
|
||||||
c->setValue(trBytes, numbytes);
|
c->setValue(trBytes, numbytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,8 +53,8 @@ protected:
|
|||||||
bool writeToDest(BLECharacteristic *c, void *dest)
|
bool writeToDest(BLECharacteristic *c, void *dest)
|
||||||
{
|
{
|
||||||
// dumpCharacteristic(pCharacteristic);
|
// dumpCharacteristic(pCharacteristic);
|
||||||
DEBUG_MSG("Got on proto write\n");
|
|
||||||
std::string src = c->getValue();
|
std::string src = c->getValue();
|
||||||
|
DEBUG_MSG("pbwrite to %s of %d bytes\n", c->getUUID().toString().c_str(), src.length());
|
||||||
return pb_decode_from_bytes((const uint8_t *)src.c_str(), src.length(), fields, dest);
|
return pb_decode_from_bytes((const uint8_t *)src.c_str(), src.length(), fields, dest);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -88,11 +90,12 @@ public:
|
|||||||
void onWrite(BLECharacteristic *c)
|
void onWrite(BLECharacteristic *c)
|
||||||
{
|
{
|
||||||
BLEKeepAliveCallbacks::onWrite(c);
|
BLEKeepAliveCallbacks::onWrite(c);
|
||||||
DEBUG_MSG("Got on nodeinfo write\n");
|
DEBUG_MSG("Reset nodeinfo read pointer\n");
|
||||||
nodeDB.resetReadPointer();
|
nodeDB.resetReadPointer();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// wrap our protobuf version with something that forces the service to reload the config
|
// wrap our protobuf version with something that forces the service to reload the config
|
||||||
class RadioCharacteristic : public ProtobufCharacteristic
|
class RadioCharacteristic : public ProtobufCharacteristic
|
||||||
{
|
{
|
||||||
@@ -102,6 +105,16 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onRead(BLECharacteristic *c)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Reading radio config\n");
|
||||||
|
|
||||||
|
// update gps connection state
|
||||||
|
devicestate.has_radio = gps.isConnected;
|
||||||
|
|
||||||
|
BLEKeepAliveCallbacks::onRead(c);
|
||||||
|
}
|
||||||
|
|
||||||
void onWrite(BLECharacteristic *c)
|
void onWrite(BLECharacteristic *c)
|
||||||
{
|
{
|
||||||
ProtobufCharacteristic::onWrite(c);
|
ProtobufCharacteristic::onWrite(c);
|
||||||
@@ -188,19 +201,18 @@ public:
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DEBUG_MSG("delivering toPhone packet to phone\n");
|
static FromRadio fRadio;
|
||||||
|
|
||||||
static FromRadio fradio;
|
// Encapsulate as a FromRadio packet
|
||||||
|
memset(&fRadio, 0, sizeof(fRadio));
|
||||||
|
fRadio.which_variant = FromRadio_packet_tag;
|
||||||
|
fRadio.variant.packet = *mp;
|
||||||
|
|
||||||
// Encapsulate as a ToRadio packet
|
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), FromRadio_fields, &fRadio);
|
||||||
memset(&fradio, 0, sizeof(fradio));
|
DEBUG_MSG("delivering toPhone packet to phone %d bytes\n", numbytes);
|
||||||
fradio.which_variant = FromRadio_packet_tag;
|
c->setValue(trBytes, numbytes);
|
||||||
fradio.variant.packet = *mp;
|
|
||||||
|
|
||||||
service.releaseToPool(mp); // we just copied the bytes, so don't need this buffer anymore
|
service.releaseToPool(mp); // we just copied the bytes, so don't need this buffer anymore
|
||||||
|
|
||||||
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), FromRadio_fields, &fradio);
|
|
||||||
c->setValue(trBytes, numbytes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -239,60 +251,7 @@ void bluetoothNotifyFromNum(uint32_t newValue)
|
|||||||
BLEService *meshService;
|
BLEService *meshService;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
MeshBluetoothService UUID 6ba1b218-15a8-461f-9fa8-5dcae273eafd
|
See bluetooth-api.md for documentation.
|
||||||
|
|
||||||
FIXME - notify vs indication for fromradio output. Using notify for now, not sure if that is best
|
|
||||||
FIXME - in the esp32 mesh managment code, occasionally mirror the current net db to flash, so that if we reboot we still have a good guess of users who are out there.
|
|
||||||
FIXME - make sure this protocol is guaranteed robust and won't drop packets
|
|
||||||
|
|
||||||
"According to the BLE specification the notification length can be max ATT_MTU - 3. The 3 bytes subtracted is the 3-byte header(OP-code (operation, 1 byte) and the attribute handle (2 bytes)).
|
|
||||||
In BLE 4.1 the ATT_MTU is 23 bytes (20 bytes for payload), but in BLE 4.2 the ATT_MTU can be negotiated up to 247 bytes."
|
|
||||||
|
|
||||||
MAXPACKET is 256? look into what the lora lib uses. FIXME
|
|
||||||
|
|
||||||
Characteristics:
|
|
||||||
UUID
|
|
||||||
properties
|
|
||||||
description
|
|
||||||
|
|
||||||
8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
|
||||||
read
|
|
||||||
fromradio - contains a newly received packet destined towards the phone (up to MAXPACKET bytes? per packet).
|
|
||||||
After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this
|
|
||||||
mailbox.
|
|
||||||
|
|
||||||
f75c76d2-129e-4dad-a1dd-7866124401e7
|
|
||||||
write
|
|
||||||
toradio - write ToRadio protobufs to this charstic to send them (up to MAXPACKET len)
|
|
||||||
|
|
||||||
ed9da18c-a800-4f66-a670-aa7547e34453
|
|
||||||
read|notify|write
|
|
||||||
fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages
|
|
||||||
until it catches up with this number.
|
|
||||||
The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32
|
|
||||||
callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio.
|
|
||||||
When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio.
|
|
||||||
Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted.
|
|
||||||
|
|
||||||
meshMyNodeCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ)
|
|
||||||
mynode - read this to access a MyNodeInfo protobuf
|
|
||||||
|
|
||||||
meshNodeInfoCharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ),
|
|
||||||
nodeinfo - read this to get a series of node infos (ending with a null empty record), write to this to restart the read statemachine that returns all the node infos
|
|
||||||
|
|
||||||
meshRadioCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ),
|
|
||||||
radio - read/write this to access a RadioConfig protobuf
|
|
||||||
|
|
||||||
meshOwnerCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
|
|
||||||
owner - read/write this to access a User protobuf
|
|
||||||
|
|
||||||
Re: queue management
|
|
||||||
Not all messages are kept in the fromradio queue (filtered based on SubPacket):
|
|
||||||
* only the most recent Position and User messages for a particular node are kept
|
|
||||||
* all Data SubPackets are kept
|
|
||||||
* No WantNodeNum / DenyNodeNum messages are kept
|
|
||||||
A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging)
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
BLEService *createMeshBluetoothService(BLEServer *server)
|
BLEService *createMeshBluetoothService(BLEServer *server)
|
||||||
{
|
{
|
||||||
@@ -330,6 +289,14 @@ BLEService *createMeshBluetoothService(BLEServer *server)
|
|||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stopMeshBluetoothService()
|
||||||
|
{
|
||||||
|
assert(meshService);
|
||||||
|
meshService->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void destroyMeshBluetoothService()
|
void destroyMeshBluetoothService()
|
||||||
{
|
{
|
||||||
assert(meshService);
|
assert(meshService);
|
||||||
|
|||||||
@@ -11,3 +11,5 @@ void destroyMeshBluetoothService();
|
|||||||
* Tell any bluetooth clients that the number of rx packets has changed
|
* Tell any bluetooth clients that the number of rx packets has changed
|
||||||
*/
|
*/
|
||||||
void bluetoothNotifyFromNum(uint32_t newValue);
|
void bluetoothNotifyFromNum(uint32_t newValue);
|
||||||
|
|
||||||
|
void stopMeshBluetoothService();
|
||||||
@@ -9,8 +9,6 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
|
|
||||||
#define DEFAULT_CHANNEL_NUM 3 // we randomly pick one
|
|
||||||
|
|
||||||
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on
|
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on
|
||||||
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
||||||
|
|
||||||
@@ -39,7 +37,6 @@ MeshRadio::MeshRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_r
|
|||||||
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
||||||
|
|
||||||
channelSettings.tx_power = 23;
|
channelSettings.tx_power = 23;
|
||||||
channelSettings.channel_num = DEFAULT_CHANNEL_NUM;
|
|
||||||
memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk));
|
memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk));
|
||||||
strcpy(channelSettings.name, "Default");
|
strcpy(channelSettings.name, "Default");
|
||||||
// Can't print strings this early - serial not setup yet
|
// Can't print strings this early - serial not setup yet
|
||||||
@@ -81,6 +78,22 @@ bool MeshRadio::init()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** hash a string into an integer
|
||||||
|
*
|
||||||
|
* djb2 by Dan Bernstein.
|
||||||
|
* http://www.cse.yorku.ca/~oz/hash.html
|
||||||
|
*/
|
||||||
|
unsigned long hash(char *str)
|
||||||
|
{
|
||||||
|
unsigned long hash = 5381;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
while ((c = *str++) != 0)
|
||||||
|
hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
void MeshRadio::reloadConfig()
|
void MeshRadio::reloadConfig()
|
||||||
{
|
{
|
||||||
rf95.setModeIdle(); // Need to be idle before doing init
|
rf95.setModeIdle(); // Need to be idle before doing init
|
||||||
@@ -91,10 +104,9 @@ void MeshRadio::reloadConfig()
|
|||||||
// setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
|
// setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
|
||||||
// rf95.setPreambleLength(8); // Default is 8
|
// rf95.setPreambleLength(8); // Default is 8
|
||||||
|
|
||||||
assert(channelSettings.channel_num < NUM_CHANNELS); // If the phone tries to tell us to use an illegal channel then panic
|
|
||||||
|
|
||||||
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
||||||
float center_freq = CH0 + CH_SPACING * channelSettings.channel_num;
|
int channel_num = hash(channelSettings.name) % NUM_CHANNELS;
|
||||||
|
float center_freq = CH0 + CH_SPACING * channel_num;
|
||||||
if (!rf95.setFrequency(center_freq))
|
if (!rf95.setFrequency(center_freq))
|
||||||
{
|
{
|
||||||
DEBUG_MSG("setFrequency failed\n");
|
DEBUG_MSG("setFrequency failed\n");
|
||||||
@@ -109,7 +121,7 @@ void MeshRadio::reloadConfig()
|
|||||||
// FIXME - can we do this? It seems to be in the Heltec board.
|
// FIXME - can we do this? It seems to be in the Heltec board.
|
||||||
rf95.setTxPower(channelSettings.tx_power, false);
|
rf95.setTxPower(channelSettings.tx_power, false);
|
||||||
|
|
||||||
DEBUG_MSG("Set radio: name=%s. config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config, channelSettings.channel_num, channelSettings.tx_power);
|
DEBUG_MSG("Set radio: name=%s. config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config, channel_num, channelSettings.tx_power);
|
||||||
|
|
||||||
// Done with init tell radio to start receiving
|
// Done with init tell radio to start receiving
|
||||||
rf95.setModeRx();
|
rf95.setModeRx();
|
||||||
|
|||||||
@@ -13,17 +13,22 @@
|
|||||||
#define CH_SPACING_US 2.16f // MHz
|
#define CH_SPACING_US 2.16f // MHz
|
||||||
#define NUM_CHANNELS_US 13
|
#define NUM_CHANNELS_US 13
|
||||||
|
|
||||||
// EU channel settings
|
// EU433 channel settings
|
||||||
#define CH0_EU 865.2f // MHz
|
#define CH0_EU433 433.175f // MHz
|
||||||
#define CH_SPACING_EU 0.3f // MHz
|
#define CH_SPACING_EU433 0.2f // MHz
|
||||||
#define NUM_CHANNELS_EU 10
|
#define NUM_CHANNELS_EU433 8
|
||||||
|
|
||||||
|
// EU865 channel settings
|
||||||
|
#define CH0_EU865 865.2f // MHz
|
||||||
|
#define CH_SPACING_EU865 0.3f // MHz
|
||||||
|
#define NUM_CHANNELS_EU865 10
|
||||||
|
|
||||||
// CN channel settings
|
// CN channel settings
|
||||||
#define CH0_CN 470.0f // MHz
|
#define CH0_CN 470.0f // MHz
|
||||||
#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510
|
#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510
|
||||||
#define NUM_CHANNELS_CN 20
|
#define NUM_CHANNELS_CN 20
|
||||||
|
|
||||||
// CN channel settings
|
// JP channel settings
|
||||||
#define CH0_JP 920.0f // MHz
|
#define CH0_JP 920.0f // MHz
|
||||||
#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925
|
#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925
|
||||||
#define NUM_CHANNELS_JP 10
|
#define NUM_CHANNELS_JP 10
|
||||||
@@ -33,10 +38,14 @@
|
|||||||
#define CH0 CH0_US
|
#define CH0 CH0_US
|
||||||
#define CH_SPACING CH_SPACING_US
|
#define CH_SPACING CH_SPACING_US
|
||||||
#define NUM_CHANNELS NUM_CHANNELS_US
|
#define NUM_CHANNELS NUM_CHANNELS_US
|
||||||
#elif defined(HW_VERSION_EU)
|
#elif defined(HW_VERSION_EU433)
|
||||||
#define CH0 CH0_EU
|
#define CH0 CH0_EU433
|
||||||
#define CH_SPACING CH_SPACING_EU
|
#define CH_SPACING CH_SPACING_EU433
|
||||||
#define NUM_CHANNELS NUM_CHANNELS_EU
|
#define NUM_CHANNELS NUM_CHANNELS_EU433
|
||||||
|
#elif defined(HW_VERSION_EU865)
|
||||||
|
#define CH0 CH0_EU865
|
||||||
|
#define CH_SPACING CH_SPACING_EU865
|
||||||
|
#define NUM_CHANNELS NUM_CHANNELS_EU865
|
||||||
#elif defined(HW_VERSION_CN)
|
#elif defined(HW_VERSION_CN)
|
||||||
#define CH0 CH0_CN
|
#define CH0 CH0_CN
|
||||||
#define CH_SPACING CH_SPACING_CN
|
#define CH_SPACING CH_SPACING_CN
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "MeshBluetoothService.h"
|
#include "MeshBluetoothService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "screen.h"
|
|
||||||
#include "Periodic.h"
|
#include "Periodic.h"
|
||||||
|
#include "PowerFSM.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
||||||
@@ -117,16 +118,41 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
|
|||||||
sendOurOwner(mp->from);
|
sendOurOwner(mp->from);
|
||||||
|
|
||||||
String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n";
|
String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n";
|
||||||
screen_print(lcd.c_str());
|
screen.print(lcd.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mp;
|
return mp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MeshService::handleIncomingPosition(MeshPacket *mp)
|
||||||
|
{
|
||||||
|
if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("handled incoming position time=%u\n", mp->payload.variant.position.time);
|
||||||
|
|
||||||
|
if (mp->payload.variant.position.time)
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
uint32_t secs = mp->payload.variant.position.time;
|
||||||
|
|
||||||
|
tv.tv_sec = secs;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
|
gps.perhapsSetRTC(&tv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MeshService::handleFromRadio(MeshPacket *mp)
|
void MeshService::handleFromRadio(MeshPacket *mp)
|
||||||
{
|
{
|
||||||
|
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
|
||||||
|
|
||||||
mp->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone
|
mp->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone
|
||||||
|
|
||||||
|
// 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 (!myNodeInfo.has_gps)
|
||||||
|
handleIncomingPosition(mp);
|
||||||
|
|
||||||
if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag)
|
if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag)
|
||||||
{
|
{
|
||||||
mp = handleFromRadioUser(mp);
|
mp = handleFromRadioUser(mp);
|
||||||
@@ -147,9 +173,9 @@ void MeshService::handleFromRadio(MeshPacket *mp)
|
|||||||
if (d)
|
if (d)
|
||||||
releaseToPool(d);
|
releaseToPool(d);
|
||||||
}
|
}
|
||||||
assert(toPhoneQueue.enqueue(mp, 0) == pdTRUE); // FIXME, instead of failing for full queue, delete the oldest mssages
|
assert(toPhoneQueue.enqueue(mp, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages
|
||||||
|
|
||||||
if(mp->payload.want_response)
|
if (mp->payload.want_response)
|
||||||
sendNetworkPing(mp->from);
|
sendNetworkPing(mp->from);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -168,7 +194,6 @@ void MeshService::handleFromRadio()
|
|||||||
bluetoothNotifyFromNum(fromNum);
|
bluetoothNotifyFromNum(fromNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint32_t sendOwnerCb()
|
uint32_t sendOwnerCb()
|
||||||
{
|
{
|
||||||
service.sendOurOwner();
|
service.sendOurOwner();
|
||||||
@@ -209,17 +234,7 @@ void MeshService::handleToRadio(std::string s)
|
|||||||
case ToRadio_packet_tag:
|
case ToRadio_packet_tag:
|
||||||
{
|
{
|
||||||
// If our phone is sending a position, see if we can use it to set our RTC
|
// If our phone is sending a position, see if we can use it to set our RTC
|
||||||
if (r.variant.packet.has_payload && r.variant.packet.payload.which_variant == SubPacket_position_tag && r.variant.packet.payload.variant.position.time)
|
handleIncomingPosition(&r.variant.packet); // If it is a position packet, perhaps set our clock
|
||||||
{
|
|
||||||
struct timeval tv;
|
|
||||||
uint32_t secs = r.variant.packet.payload.variant.position.time;
|
|
||||||
|
|
||||||
// FIXME, this is a shit not right version of the standard def of unix time!!!
|
|
||||||
tv.tv_sec = secs;
|
|
||||||
tv.tv_usec = 0;
|
|
||||||
|
|
||||||
gps.perhapsSetRTC(&tv);
|
|
||||||
}
|
|
||||||
|
|
||||||
r.variant.packet.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone (so we update our nodedb for the local node)
|
r.variant.packet.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone (so we update our nodedb for the local node)
|
||||||
|
|
||||||
@@ -240,6 +255,9 @@ void MeshService::handleToRadio(std::string s)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
DEBUG_MSG("Error: ignoring malformed toradio\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshService::sendToMesh(MeshPacket *p)
|
void MeshService::sendToMesh(MeshPacket *p)
|
||||||
@@ -247,8 +265,14 @@ 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 nodes shouldn't trust it anyways)
|
// 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 devices can get time.
|
||||||
if (p->has_payload && p->payload.which_variant == SubPacket_position_tag)
|
if (p->has_payload && p->payload.which_variant == SubPacket_position_tag)
|
||||||
|
{
|
||||||
|
if (!myNodeInfo.has_gps)
|
||||||
p->payload.variant.position.time = 0;
|
p->payload.variant.position.time = 0;
|
||||||
|
else
|
||||||
|
DEBUG_MSG("Providing time to mesh %u\n", p->payload.variant.position.time);
|
||||||
|
}
|
||||||
|
|
||||||
// If the phone sent a packet just to us, don't send it out into the network
|
// If the phone sent a packet just to us, don't send it out into the network
|
||||||
if (p->to == nodeDB.getNodeNum())
|
if (p->to == nodeDB.getNodeNum())
|
||||||
@@ -295,8 +319,7 @@ void MeshService::sendOurPosition(NodeNum dest)
|
|||||||
p->to = dest;
|
p->to = dest;
|
||||||
p->payload.which_variant = SubPacket_position_tag;
|
p->payload.which_variant = SubPacket_position_tag;
|
||||||
p->payload.variant.position = node->position;
|
p->payload.variant.position = node->position;
|
||||||
// FIXME - for now we are leaving this in the sent packets (for debugging)
|
p->payload.variant.position.time = gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||||
//p->payload.variant.position.time = 0; // No need to send time, other node won't trust it anyways
|
|
||||||
sendToMesh(p);
|
sendToMesh(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,12 +328,16 @@ void MeshService::onGPSChanged()
|
|||||||
// 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 = allocForSending();
|
MeshPacket *p = allocForSending();
|
||||||
p->payload.which_variant = SubPacket_position_tag;
|
p->payload.which_variant = SubPacket_position_tag;
|
||||||
|
|
||||||
Position &pos = p->payload.variant.position;
|
Position &pos = p->payload.variant.position;
|
||||||
if (gps.altitude.isValid())
|
// !zero or !zero lat/long means valid
|
||||||
pos.altitude = gps.altitude.meters();
|
if(gps.latitude != 0 || gps.longitude != 0) {
|
||||||
pos.latitude = gps.location.lat();
|
if (gps.altitude != 0)
|
||||||
pos.longitude = gps.location.lng();
|
pos.altitude = gps.altitude;
|
||||||
|
pos.latitude = gps.latitude;
|
||||||
|
pos.longitude = gps.longitude;
|
||||||
pos.time = gps.getValidTime();
|
pos.time = gps.getValidTime();
|
||||||
|
}
|
||||||
|
|
||||||
// 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;
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ private:
|
|||||||
|
|
||||||
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
|
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
|
||||||
MeshPacket *handleFromRadioUser(MeshPacket *mp);
|
MeshPacket *handleFromRadioUser(MeshPacket *mp);
|
||||||
|
|
||||||
|
/// look at inbound packets and if they contain a position with time, possibly set our clock
|
||||||
|
void handleIncomingPosition(MeshPacket *mp);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MeshService service;
|
extern MeshService service;
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ MyNodeInfo &myNodeInfo = devicestate.my_node;
|
|||||||
RadioConfig &radioConfig = devicestate.radio;
|
RadioConfig &radioConfig = devicestate.radio;
|
||||||
ChannelSettings &channelSettings = radioConfig.channel_settings;
|
ChannelSettings &channelSettings = radioConfig.channel_settings;
|
||||||
|
|
||||||
|
/*
|
||||||
|
DeviceState versions used to be defined in the .proto file but really only this function cares. So changed to a
|
||||||
|
#define here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DEVICESTATE_CUR_VER 6
|
||||||
|
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
||||||
|
|
||||||
#define FS SPIFFS
|
#define FS SPIFFS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +52,7 @@ void NodeDB::init()
|
|||||||
devicestate.has_my_node = true;
|
devicestate.has_my_node = true;
|
||||||
devicestate.has_radio = true;
|
devicestate.has_radio = true;
|
||||||
devicestate.has_owner = true;
|
devicestate.has_owner = true;
|
||||||
devicestate.has_radio = true;
|
devicestate.has_radio = false;
|
||||||
devicestate.radio.has_channel_settings = true;
|
devicestate.radio.has_channel_settings = true;
|
||||||
devicestate.radio.has_preferences = true;
|
devicestate.radio.has_preferences = true;
|
||||||
devicestate.node_db_count = 0;
|
devicestate.node_db_count = 0;
|
||||||
@@ -54,9 +62,9 @@ void NodeDB::init()
|
|||||||
radioConfig.preferences.position_broadcast_secs = 15 * 60;
|
radioConfig.preferences.position_broadcast_secs = 15 * 60;
|
||||||
radioConfig.preferences.wait_bluetooth_secs = 120;
|
radioConfig.preferences.wait_bluetooth_secs = 120;
|
||||||
radioConfig.preferences.screen_on_secs = 30;
|
radioConfig.preferences.screen_on_secs = 30;
|
||||||
radioConfig.preferences.mesh_sds_timeout_secs = 60 * 60;
|
radioConfig.preferences.mesh_sds_timeout_secs = 2 * 60 * 60;
|
||||||
radioConfig.preferences.phone_sds_timeout_sec = 60 * 60;
|
radioConfig.preferences.phone_sds_timeout_sec = 2 * 60 * 60;
|
||||||
radioConfig.preferences.sds_secs = 60 * 60;
|
radioConfig.preferences.sds_secs = 365 * 24 * 60 * 60; // one year
|
||||||
radioConfig.preferences.ls_secs = 60 * 60;
|
radioConfig.preferences.ls_secs = 60 * 60;
|
||||||
radioConfig.preferences.phone_timeout_secs = 15 * 60;
|
radioConfig.preferences.phone_timeout_secs = 15 * 60;
|
||||||
|
|
||||||
@@ -64,6 +72,9 @@ void NodeDB::init()
|
|||||||
// some hardware defaults to have a built in GPS
|
// some hardware defaults to have a built in GPS
|
||||||
myNodeInfo.has_gps = true;
|
myNodeInfo.has_gps = true;
|
||||||
#endif
|
#endif
|
||||||
|
strncpy(myNodeInfo.region, xstr(HW_VERSION), sizeof(myNodeInfo.region));
|
||||||
|
strncpy(myNodeInfo.firmware_version, xstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
|
||||||
|
strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
|
||||||
|
|
||||||
// Init our blank owner info to reasonable defaults
|
// Init our blank owner info to reasonable defaults
|
||||||
esp_efuse_mac_get_default(ourMacAddr);
|
esp_efuse_mac_get_default(ourMacAddr);
|
||||||
@@ -144,10 +155,11 @@ void NodeDB::loadFromDisk()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (scratch.version < DeviceState_Version_Minimum)
|
if (scratch.version < DEVICESTATE_MIN_VER)
|
||||||
DEBUG_MSG("Warn: devicestate is old, discarding\n");
|
DEBUG_MSG("Warn: devicestate is old, discarding\n");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
DEBUG_MSG("Loaded saved preferences version %d\n", scratch.version);
|
||||||
devicestate = scratch;
|
devicestate = scratch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +185,7 @@ void NodeDB::saveToDisk()
|
|||||||
|
|
||||||
//DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
//DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
||||||
|
|
||||||
devicestate.version = DeviceState_Version_Current;
|
devicestate.version = DEVICESTATE_CUR_VER;
|
||||||
if (!pb_encode(&stream, DeviceState_fields, &devicestate))
|
if (!pb_encode(&stream, DeviceState_fields, &devicestate))
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
|
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
|
||||||
@@ -275,7 +287,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
|||||||
devicestate.rx_text_message = mp;
|
devicestate.rx_text_message = mp;
|
||||||
devicestate.has_rx_text_message = true;
|
devicestate.has_rx_text_message = true;
|
||||||
updateTextMessage = true;
|
updateTextMessage = true;
|
||||||
powerFSM.trigger(EVENT_NODEDB_UPDATED);
|
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
#include "Periodic.h"
|
#include "Periodic.h"
|
||||||
|
|
||||||
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod)
|
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// call this from loop
|
/// call this from loop
|
||||||
void PeriodicTask::loop()
|
void PeriodicTask::loop()
|
||||||
{
|
{
|
||||||
uint32_t now = millis();
|
|
||||||
if (period && (now - lastMsec) >= period)
|
|
||||||
{
|
{
|
||||||
lastMsec = now;
|
meshtastic::LockGuard lg(&lock);
|
||||||
doTask();
|
uint32_t now = millis();
|
||||||
|
if (!period || (now - lastMsec) < period) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
lastMsec = now;
|
||||||
|
}
|
||||||
|
// Release the lock in case the task wants to change the period.
|
||||||
|
doTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Periodic::doTask()
|
void Periodic::doTask()
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <cstdint>
|
||||||
#include "configuration.h"
|
|
||||||
|
#include "lock.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base class for tasks that want their doTask() method invoked periodically
|
* A base class for tasks that want their doTask() method invoked periodically
|
||||||
@@ -15,9 +16,10 @@ class PeriodicTask
|
|||||||
uint32_t lastMsec = 0;
|
uint32_t lastMsec = 0;
|
||||||
uint32_t period = 1; // call soon after creation
|
uint32_t period = 1; // call soon after creation
|
||||||
|
|
||||||
public:
|
// Protects the above variables.
|
||||||
uint32_t periodMsec;
|
meshtastic::Lock lock;
|
||||||
|
|
||||||
|
public:
|
||||||
virtual ~PeriodicTask() {}
|
virtual ~PeriodicTask() {}
|
||||||
|
|
||||||
PeriodicTask(uint32_t initialPeriod = 1);
|
PeriodicTask(uint32_t initialPeriod = 1);
|
||||||
@@ -26,8 +28,12 @@ public:
|
|||||||
virtual void loop();
|
virtual void loop();
|
||||||
|
|
||||||
/// Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
|
/// Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
|
||||||
void setPeriod(uint32_t p) { period = p; }
|
void setPeriod(uint32_t p)
|
||||||
|
{
|
||||||
|
meshtastic::LockGuard lg(&lock);
|
||||||
|
period = p;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void doTask() = 0;
|
virtual void doTask() = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public:
|
|||||||
{
|
{
|
||||||
T *p;
|
T *p;
|
||||||
|
|
||||||
return this->dequeue(&p, maxWait) == pdTRUE ? p : NULL;
|
return this->dequeue(&p, maxWait) ? p : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a ptr or null if the queue was empty
|
// returns a ptr or null if the queue was empty
|
||||||
@@ -26,6 +26,6 @@ public:
|
|||||||
{
|
{
|
||||||
T *p;
|
T *p;
|
||||||
|
|
||||||
return this->dequeueFromISR(&p, higherPriWoken) == pdTRUE ? p : NULL;
|
return this->dequeueFromISR(&p, higherPriWoken) ? p : nullptr;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
|
||||||
#include "sleep.h"
|
#include "PowerFSM.h"
|
||||||
|
#include "GPS.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include "main.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "PowerFSM.h"
|
#include "sleep.h"
|
||||||
#include "GPS.h"
|
|
||||||
|
|
||||||
static void sdsEnter()
|
static void sdsEnter()
|
||||||
{
|
{
|
||||||
@@ -24,20 +25,30 @@ static void sdsEnter()
|
|||||||
|
|
||||||
static void lsEnter()
|
static void lsEnter()
|
||||||
{
|
{
|
||||||
|
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
||||||
|
screen.setOn(false);
|
||||||
|
|
||||||
while (!service.radio.rf95.canSleep())
|
while (!service.radio.rf95.canSleep())
|
||||||
delay(10); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives)
|
delay(10); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives)
|
||||||
|
|
||||||
gps.prepareSleep(); // abandon in-process parsing
|
gps.prepareSleep(); // abandon in-process parsing
|
||||||
setGPSPower(false); // kill GPS power
|
|
||||||
|
// if (!isUSBPowered) // FIXME - temp hack until we can put gps in sleep mode, if we have AC when we go to sleep then
|
||||||
|
// leave GPS on
|
||||||
|
// setGPSPower(false); // kill GPS power
|
||||||
|
|
||||||
|
DEBUG_MSG("lsEnter end\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lsIdle()
|
static void lsIdle()
|
||||||
{
|
{
|
||||||
|
DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
||||||
|
|
||||||
uint32_t secsSlept = 0;
|
uint32_t secsSlept = 0;
|
||||||
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
||||||
|
bool reached_ls_secs = false;
|
||||||
|
|
||||||
while (secsSlept < radioConfig.preferences.ls_secs)
|
while (!reached_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 = 5;
|
uint32_t sleepTime = 5;
|
||||||
|
|
||||||
@@ -52,21 +63,35 @@ static void lsIdle()
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
secsSlept += sleepTime;
|
secsSlept += sleepTime;
|
||||||
|
reached_ls_secs = secsSlept >= radioConfig.preferences.ls_secs;
|
||||||
}
|
}
|
||||||
setLed(false);
|
setLed(false);
|
||||||
|
|
||||||
// Regardless of why we woke (for now) just transition to NB (and that state will handle stuff like IRQs etc)
|
if (reached_ls_secs) {
|
||||||
|
// stay in LS mode but let loop check whatever it wants
|
||||||
|
DEBUG_MSG("reached ls_secs, servicing loop()\n");
|
||||||
|
} else {
|
||||||
|
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
||||||
|
|
||||||
|
if (!digitalRead(BUTTON_PIN)) // If we woke because of press, instead generate a PRESS event.
|
||||||
|
{
|
||||||
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
|
} else {
|
||||||
|
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
||||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lsExit()
|
static void lsExit()
|
||||||
{
|
{
|
||||||
setGPSPower(true); // restore GPS power
|
// setGPSPower(true); // restore GPS power
|
||||||
gps.startLock();
|
gps.startLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbEnter()
|
static void nbEnter()
|
||||||
{
|
{
|
||||||
|
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
|
||||||
@@ -81,47 +106,56 @@ static void onEnter()
|
|||||||
{
|
{
|
||||||
screen.setOn(true);
|
screen.setOn(true);
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
|
|
||||||
|
static uint32_t lastPingMs;
|
||||||
|
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
if (now - lastPingMs > 60 * 1000) { // if more than a minute since our last press, ask other nodes to update their state
|
||||||
|
service.sendNetworkPing();
|
||||||
|
lastPingMs = now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onExit()
|
static void wakeForPing() {}
|
||||||
{
|
|
||||||
screen.setOn(false);
|
|
||||||
}
|
|
||||||
static void wakeForPing()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static void screenPress()
|
static void screenPress()
|
||||||
{
|
{
|
||||||
screen.onPress();
|
screen.onPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void bootEnter() {
|
||||||
|
}
|
||||||
|
|
||||||
State stateSDS(sdsEnter, NULL, NULL, "SDS");
|
State stateSDS(sdsEnter, NULL, NULL, "SDS");
|
||||||
State stateLS(lsEnter, lsIdle, lsExit, "LS");
|
State stateLS(lsEnter, lsIdle, lsExit, "LS");
|
||||||
State stateNB(nbEnter, NULL, NULL, "NB");
|
State stateNB(nbEnter, NULL, NULL, "NB");
|
||||||
State stateDARK(darkEnter, NULL, NULL, "DARK");
|
State stateDARK(darkEnter, NULL, NULL, "DARK");
|
||||||
State stateON(onEnter, NULL, onExit, "ON");
|
State stateBOOT(bootEnter , NULL, NULL, "BOOT");
|
||||||
Fsm powerFSM(&stateDARK);
|
State stateON(onEnter, NULL, NULL, "ON");
|
||||||
|
Fsm powerFSM(&stateBOOT);
|
||||||
|
|
||||||
void PowerFSM_setup()
|
void PowerFSM_setup()
|
||||||
{
|
{
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BOOT, NULL, "Boot");
|
powerFSM.add_timed_transition(&stateBOOT, &stateON, 3 * 1000, NULL,
|
||||||
|
"boot timeout");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing, "Wake timer");
|
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing, "Wake timer");
|
||||||
|
|
||||||
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it handles things
|
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it
|
||||||
// powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
// 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");
|
||||||
|
|
||||||
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it handles things
|
// Handle press events
|
||||||
// powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press");
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press");
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
|
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Bluetooth pairing");
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, NULL, "Bluetooth pairing");
|
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||||
@@ -130,6 +164,7 @@ void PowerFSM_setup()
|
|||||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text");
|
||||||
|
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
|
||||||
|
|
||||||
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
||||||
|
|
||||||
@@ -141,7 +176,14 @@ void PowerFSM_setup()
|
|||||||
|
|
||||||
powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout");
|
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, "Bluetooth timeout");
|
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL,
|
||||||
|
"Bluetooth timeout");
|
||||||
|
|
||||||
|
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL,
|
||||||
|
"mesh timeout");
|
||||||
|
// removing for now, because some users don't even have phones
|
||||||
|
// powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.phone_sds_timeout_sec * 1000, NULL, "phone
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Fsm.h"
|
#include <Fsm.h>
|
||||||
|
|
||||||
// See sw-design.md for documentation
|
// See sw-design.md for documentation
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#define EVENT_RECEIVED_PACKET 3
|
#define EVENT_RECEIVED_PACKET 3
|
||||||
#define EVENT_PACKET_FOR_PHONE 4
|
#define EVENT_PACKET_FOR_PHONE 4
|
||||||
#define EVENT_RECEIVED_TEXT_MSG 5
|
#define EVENT_RECEIVED_TEXT_MSG 5
|
||||||
#define EVENT_BOOT 6
|
// #define EVENT_BOOT 6 // now done with a timed transition
|
||||||
#define EVENT_BLUETOOTH_PAIR 7
|
#define EVENT_BLUETOOTH_PAIR 7
|
||||||
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
||||||
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
|
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <cassert>
|
||||||
#include <assert.h>
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/queue.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper for freertos queues. Note: each element object must be quite small, so T should be only
|
* A wrapper for freertos queues. Note: each element object should be small
|
||||||
* pointer types or ints
|
* and POD (Plain Old Data type) as elements are memcpied by value.
|
||||||
*/
|
*/
|
||||||
template <class T>
|
template <class T>
|
||||||
class TypedQueue
|
class TypedQueue
|
||||||
{
|
{
|
||||||
|
static_assert(std::is_pod<T>::value, "T must be pod");
|
||||||
QueueHandle_t h;
|
QueueHandle_t h;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TypedQueue(int maxElements)
|
TypedQueue(int maxElements)
|
||||||
{
|
{
|
||||||
h = xQueueCreate(maxElements, sizeof(T));
|
h = xQueueCreate(maxElements, sizeof(T));
|
||||||
@@ -34,24 +38,22 @@ public:
|
|||||||
return uxQueueMessagesWaiting(h) == 0;
|
return uxQueueMessagesWaiting(h) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdTRUE for success else failure
|
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
||||||
BaseType_t enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
|
||||||
{
|
{
|
||||||
return xQueueSendToBack(h, &x, maxWait);
|
return xQueueSendToBack(h, &x, maxWait) == pdTRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseType_t enqueueFromISR(T x, BaseType_t *higherPriWoken)
|
bool enqueueFromISR(T x, BaseType_t *higherPriWoken)
|
||||||
{
|
{
|
||||||
return xQueueSendToBackFromISR(h, &x, higherPriWoken);
|
return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdTRUE for success else failure
|
bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY)
|
||||||
BaseType_t dequeue(T *p, TickType_t maxWait = portMAX_DELAY)
|
|
||||||
{
|
{
|
||||||
return xQueueReceive(h, p, maxWait);
|
return xQueueReceive(h, p, maxWait) == pdTRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseType_t dequeueFromISR(T *p, BaseType_t *higherPriWoken)
|
bool dequeueFromISR(T *p, BaseType_t *higherPriWoken)
|
||||||
{
|
{
|
||||||
return xQueueReceiveFromISR(h, p, higherPriWoken);
|
return xQueueReceiveFromISR(h, p, higherPriWoken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,21 +29,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// Version
|
// Version
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
#define APP_NAME "Meshtastic"
|
|
||||||
|
|
||||||
// If app version is not specified we assume we are not being invoked by the build script
|
// If app version is not specified we assume we are not being invoked by the build script
|
||||||
#ifndef APP_VERSION
|
#ifndef APP_VERSION
|
||||||
#define APP_VERSION "0.0.0" // this def normally comes from build-all.sh
|
#define APP_VERSION 0.0.0 // this def normally comes from build-all.sh
|
||||||
|
#define HW_VERSION 1.0 - US // normally comes from build-all.sh and contains the region code
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Configuration
|
// Configuration
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Select which board is being used. If the outside build environment has sent a choice, just use that
|
// Select which board is being used. If the outside build environment has sent a choice, just use that
|
||||||
#if !defined(T_BEAM_V10) && !defined(HELTEC_LORA32)
|
#if !defined(T_BEAM_V10) && !defined(HELTEC_LORA32)
|
||||||
//#define T_BEAM_V10 // AKA Rev1 (second board released)
|
// #define T_BEAM_V10 // AKA Rev1 (second board released)
|
||||||
#define HELTEC_LORA32
|
#define HELTEC_LORA32
|
||||||
|
|
||||||
#define HW_VERSION_US // We encode the hardware freq range in the hw version string, so sw update can eventually install the correct build
|
#define HW_VERSION_US // We encode the hardware freq range in the hw version string, so sw update can eventually install the correct build
|
||||||
@@ -60,13 +58,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
#define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found
|
#define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found
|
||||||
|
|
||||||
|
#define xstr(s) str(s)
|
||||||
|
#define str(s) #s
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// DEBUG
|
// DEBUG
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
#ifdef DEBUG_PORT
|
#ifdef DEBUG_PORT
|
||||||
#define DEBUG_MSG(...) DEBUG_PORT.printf( __VA_ARGS__ )
|
#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#define DEBUG_MSG(...)
|
#define DEBUG_MSG(...)
|
||||||
#endif
|
#endif
|
||||||
@@ -84,14 +84,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define GPS_SERIAL_NUM 1
|
#define GPS_SERIAL_NUM 1
|
||||||
#define GPS_BAUDRATE 9600
|
#define GPS_BAUDRATE 9600
|
||||||
|
|
||||||
#if defined(T_BEAM_V10)
|
|
||||||
#define GPS_RX_PIN 34
|
#define GPS_RX_PIN 34
|
||||||
#ifdef USE_JTAG
|
#ifdef USE_JTAG
|
||||||
#define GPS_TX_PIN -1
|
#define GPS_TX_PIN -1
|
||||||
#else
|
#else
|
||||||
#define GPS_TX_PIN 12
|
#define GPS_TX_PIN 12
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// LoRa SPI
|
// LoRa SPI
|
||||||
@@ -103,10 +101,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define NSS_GPIO 18
|
#define NSS_GPIO 18
|
||||||
|
|
||||||
#if defined(T_BEAM_V10)
|
#if defined(T_BEAM_V10)
|
||||||
#define HW_VENDOR "TTGO"
|
// This string must exactly match the case used in release file names or the android updater won't work
|
||||||
|
#define HW_VENDOR "TBEAM"
|
||||||
|
|
||||||
#define BICOLOR_DISPLAY // we have yellow at the top 16 lines
|
#define BICOLOR_DISPLAY // we have yellow at the top 16 lines
|
||||||
|
|
||||||
|
// #define BUTTON_NEED_PULLUP // if set we need to turn on the internal CPU pullup during sleep
|
||||||
|
|
||||||
#define I2C_SDA 21
|
#define I2C_SDA 21
|
||||||
#define I2C_SCL 22
|
#define I2C_SCL 22
|
||||||
|
|
||||||
@@ -123,7 +124,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define PMU_IRQ 35
|
#define PMU_IRQ 35
|
||||||
|
|
||||||
#elif defined(HELTEC_LORA32)
|
#elif defined(HELTEC_LORA32)
|
||||||
#define HW_VENDOR "Heltec"
|
// This string must exactly match the case used in release file names or the android updater won't work
|
||||||
|
#define HW_VENDOR "HELTEC"
|
||||||
|
|
||||||
#ifndef USE_JTAG // gpio15 is TDO for JTAG, so no I2C on this board while doing jtag
|
#ifndef USE_JTAG // gpio15 is TDO for JTAG, so no I2C on this board while doing jtag
|
||||||
#define I2C_SDA 4
|
#define I2C_SDA 4
|
||||||
@@ -144,7 +146,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define DIO2_GPIO 34
|
#define DIO2_GPIO 34
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// AXP192 (Rev1-specific options)
|
// AXP192 (Rev1-specific options)
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -152,4 +153,3 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// #define AXP192_SLAVE_ADDRESS 0x34 // Now defined in axp20x.h
|
// #define AXP192_SLAVE_ADDRESS 0x34 // Now defined in axp20x.h
|
||||||
#define GPS_POWER_CTRL_CH 3
|
#define GPS_POWER_CTRL_CH 3
|
||||||
#define LORA_POWER_CTRL_CH 2
|
#define LORA_POWER_CTRL_CH 2
|
||||||
|
|
||||||
|
|||||||
20
src/debug.cpp
Normal file
20
src/debug.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
void printThreadInfo(const char *extra)
|
||||||
|
{
|
||||||
|
uint32_t taskHandle = reinterpret_cast<uint32_t>(xTaskGetCurrentTaskHandle());
|
||||||
|
DEBUG_MSG("printThreadInfo(%s) task: %" PRIx32 " core id: %u min free stack: %u\n", extra, taskHandle, xPortGetCoreID(),
|
||||||
|
uxTaskGetStackHighWaterMark(nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
10
src/debug.h
Normal file
10
src/debug.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Dumps out which core we are running on, and min level of remaining stack
|
||||||
|
/// seen.
|
||||||
|
void printThreadInfo(const char *extra);
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
35
src/lock.cpp
Normal file
35
src/lock.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include "lock.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
Lock::Lock()
|
||||||
|
{
|
||||||
|
handle = xSemaphoreCreateBinary();
|
||||||
|
assert(handle);
|
||||||
|
assert(xSemaphoreGive(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lock::lock()
|
||||||
|
{
|
||||||
|
assert(xSemaphoreTake(handle, portMAX_DELAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lock::unlock()
|
||||||
|
{
|
||||||
|
assert(xSemaphoreGive(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
LockGuard::LockGuard(Lock *lock) : lock(lock)
|
||||||
|
{
|
||||||
|
lock->lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
LockGuard::~LockGuard()
|
||||||
|
{
|
||||||
|
lock->unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
46
src/lock.h
Normal file
46
src/lock.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
// Simple wrapper around FreeRTOS API for implementing a mutex lock.
|
||||||
|
class Lock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Lock();
|
||||||
|
|
||||||
|
Lock(const Lock &) = delete;
|
||||||
|
Lock &operator=(const Lock &) = delete;
|
||||||
|
|
||||||
|
/// Locks the lock.
|
||||||
|
//
|
||||||
|
// Must not be called from an ISR.
|
||||||
|
void lock();
|
||||||
|
|
||||||
|
// Unlocks the lock.
|
||||||
|
//
|
||||||
|
// Must not be called from an ISR.
|
||||||
|
void unlock();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
// RAII lock guard.
|
||||||
|
class LockGuard
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LockGuard(Lock *lock);
|
||||||
|
~LockGuard();
|
||||||
|
|
||||||
|
LockGuard(const LockGuard &) = delete;
|
||||||
|
LockGuard &operator=(const LockGuard &) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Lock *lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "rom/rtc.h"
|
#include "rom/rtc.h"
|
||||||
#include <driver/rtc_io.h>
|
#include <driver/rtc_io.h>
|
||||||
#include <TinyGPS++.h>
|
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include "BluetoothUtil.h"
|
#include "BluetoothUtil.h"
|
||||||
#include "MeshBluetoothService.h"
|
#include "MeshBluetoothService.h"
|
||||||
@@ -45,6 +44,14 @@ AXP20X_Class axp;
|
|||||||
bool pmu_irq = false;
|
bool pmu_irq = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Global Screen singleton
|
||||||
|
#ifdef I2C_SDA
|
||||||
|
meshtastic::Screen screen(SSD1306_ADDRESS, I2C_SDA, I2C_SCL);
|
||||||
|
#else
|
||||||
|
// Fake values for pins to keep build happy, we won't ever initialize it.
|
||||||
|
meshtastic::Screen screen(SSD1306_ADDRESS, 0, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
// these flags are all in bss so they default false
|
// these flags are all in bss so they default false
|
||||||
bool isCharging;
|
bool isCharging;
|
||||||
bool isUSBPowered;
|
bool isUSBPowered;
|
||||||
@@ -54,9 +61,6 @@ bool axp192_found;
|
|||||||
|
|
||||||
bool bluetoothOn;
|
bool bluetoothOn;
|
||||||
|
|
||||||
#define xstr(s) str(s)
|
|
||||||
#define str(s) #s
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Application
|
// Application
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -175,8 +179,8 @@ void axp192Init()
|
|||||||
axp.clearIRQ();
|
axp.clearIRQ();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
isCharging = axp.isChargeing();
|
isCharging = axp.isChargeing() ? 1 : 0;
|
||||||
isUSBPowered = axp.isVBUSPlug();
|
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -201,8 +205,6 @@ const char *getDeviceName()
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
// Debug
|
// Debug
|
||||||
@@ -227,8 +229,6 @@ void setup()
|
|||||||
scanI2Cdevice();
|
scanI2Cdevice();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
axp192Init();
|
|
||||||
|
|
||||||
// Buttons & LED
|
// Buttons & LED
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||||
@@ -240,27 +240,30 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Hello
|
// Hello
|
||||||
DEBUG_MSG("%s %s\n", xstr(APP_NAME), str(APP_VERSION));
|
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_VERSION));
|
||||||
|
|
||||||
// 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
|
ssd1306_found = false; // forget we even have the hardware
|
||||||
|
|
||||||
|
// Initialize the screen first so we can show the logo while we start up everything else.
|
||||||
if (ssd1306_found)
|
if (ssd1306_found)
|
||||||
screen.setup();
|
screen.setup();
|
||||||
|
|
||||||
|
axp192Init();
|
||||||
|
|
||||||
|
screen.print("Started...\n");
|
||||||
|
|
||||||
// Init GPS
|
// Init GPS
|
||||||
gps.setup();
|
gps.setup();
|
||||||
|
|
||||||
screen_print("Started...\n");
|
|
||||||
|
|
||||||
service.init();
|
service.init();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
PowerFSM_setup();
|
|
||||||
powerFSM.trigger(EVENT_BOOT); // transition to ON, FIXME, only do this for cold boots, not waking from SDS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void initBluetooth()
|
void initBluetooth()
|
||||||
@@ -270,7 +273,14 @@ void initBluetooth()
|
|||||||
// FIXME - we are leaking like crazy
|
// FIXME - we are leaking like crazy
|
||||||
// AllocatorScope scope(btPool);
|
// AllocatorScope scope(btPool);
|
||||||
|
|
||||||
BLEServer *serve = initBLE(getDeviceName(), HW_VENDOR, str(APP_VERSION)); // FIXME, use a real name based on the macaddr
|
// Note: these callbacks might be coming in from a different thread.
|
||||||
|
BLEServer *serve = initBLE(
|
||||||
|
[](uint8_t pin) {
|
||||||
|
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
|
||||||
|
screen.startBluetoothPinScreen(pin);
|
||||||
|
},
|
||||||
|
[]() { screen.stopBluetoothPinScreen(); },
|
||||||
|
getDeviceName(), HW_VENDOR, xstr(APP_VERSION), xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr
|
||||||
createMeshBluetoothService(serve);
|
createMeshBluetoothService(serve);
|
||||||
|
|
||||||
// Start advertising - this must be done _after_ creating all services
|
// Start advertising - this must be done _after_ creating all services
|
||||||
@@ -293,6 +303,7 @@ void setBluetoothEnable(bool on)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We have to totally teardown our bluetooth objects to prevent leaks
|
// We have to totally teardown our bluetooth objects to prevent leaks
|
||||||
|
stopMeshBluetoothService(); // Must do before shutting down bluetooth
|
||||||
deinitBLE();
|
deinitBLE();
|
||||||
destroyMeshBluetoothService(); // must do after deinit, because it frees our service
|
destroyMeshBluetoothService(); // must do after deinit, because it frees our service
|
||||||
Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap());
|
Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap());
|
||||||
@@ -345,6 +356,9 @@ void loop()
|
|||||||
// axpDebugOutput.loop();
|
// axpDebugOutput.loop();
|
||||||
loopBLE();
|
loopBLE();
|
||||||
|
|
||||||
|
// for debug printing
|
||||||
|
// service.radio.rf95.canSleep();
|
||||||
|
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
if (axp192_found)
|
if (axp192_found)
|
||||||
{
|
{
|
||||||
@@ -356,11 +370,15 @@ void loop()
|
|||||||
|
|
||||||
DEBUG_MSG("pmu irq!\n");
|
DEBUG_MSG("pmu irq!\n");
|
||||||
|
|
||||||
isCharging = axp.isChargeing();
|
isCharging = axp.isChargeing() ? 1 : 0;
|
||||||
isUSBPowered = axp.isVBUSPlug();
|
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
|
||||||
|
|
||||||
axp.clearIRQ();
|
axp.clearIRQ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME AXP192 interrupt is not firing, remove this temporary polling of battery state
|
||||||
|
isCharging = axp.isChargeing() ? 1 : 0;
|
||||||
|
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -368,8 +386,7 @@ void loop()
|
|||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of this boilerplate)
|
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of this boilerplate)
|
||||||
static bool wasPressed = false;
|
static bool wasPressed = false;
|
||||||
static uint32_t minPressMs; // what tick should we call this press long enough
|
|
||||||
static uint32_t lastPingMs;
|
|
||||||
|
|
||||||
if (!digitalRead(BUTTON_PIN))
|
if (!digitalRead(BUTTON_PIN))
|
||||||
{
|
{
|
||||||
@@ -381,15 +398,6 @@ void loop()
|
|||||||
// esp_pm_dump_locks(stdout); // FIXME, do this someplace better
|
// esp_pm_dump_locks(stdout); // FIXME, do this someplace better
|
||||||
wasPressed = true;
|
wasPressed = true;
|
||||||
|
|
||||||
uint32_t now = millis();
|
|
||||||
minPressMs = now + 3000;
|
|
||||||
|
|
||||||
if (now - lastPingMs > 60 * 1000)
|
|
||||||
{ // if more than a minute since our last press, ask other nodes to update their state
|
|
||||||
service.sendNetworkPing();
|
|
||||||
lastPingMs = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
powerFSM.trigger(EVENT_PRESS);
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -397,16 +405,17 @@ void loop()
|
|||||||
{
|
{
|
||||||
// we just did a release
|
// we just did a release
|
||||||
wasPressed = false;
|
wasPressed = false;
|
||||||
if (millis() > minPressMs)
|
|
||||||
{
|
|
||||||
// held long enough
|
|
||||||
screen_print("Erasing prefs");
|
|
||||||
delay(5000); // Give some time to read the screen
|
|
||||||
// ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Show boot screen for first 3 seconds, then switch to normal operation.
|
||||||
|
static bool showingBootScreen = true;
|
||||||
|
if (showingBootScreen && (millis() > 3000))
|
||||||
|
{
|
||||||
|
screen.stopBootScreen();
|
||||||
|
showingBootScreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
// 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.
|
// i.e. don't just keep spinning in loop as fast as we can.
|
||||||
//DEBUG_MSG("msecs %d\n", msecstosleep);
|
//DEBUG_MSG("msecs %d\n", msecstosleep);
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "screen.h"
|
||||||
|
|
||||||
extern bool axp192_found;
|
extern bool axp192_found;
|
||||||
extern bool ssd1306_found;
|
extern bool ssd1306_found;
|
||||||
|
extern bool isCharging;
|
||||||
|
extern bool isUSBPowered;
|
||||||
|
|
||||||
|
// Global Screen singleton.
|
||||||
|
extern meshtastic::Screen screen;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msg
|
|||||||
pb_istream_t stream = pb_istream_from_buffer(srcbuf, srcbufsize);
|
pb_istream_t stream = pb_istream_from_buffer(srcbuf, srcbufsize);
|
||||||
if (!pb_decode(&stream, fields, dest_struct))
|
if (!pb_decode(&stream, fields, dest_struct))
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
|
DEBUG_MSG("Error: can't decode protobuf %s, pb_msgdesc 0x%p\n", PB_GET_ERROR(&stream), fields);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ PB_BIND(ToRadio, ToRadio, 2)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef PB_CONVERT_DOUBLE_FLOAT
|
#ifndef PB_CONVERT_DOUBLE_FLOAT
|
||||||
/* On some platforms (such as AVR), double is really float.
|
/* On some platforms (such as AVR), double is really float.
|
||||||
* To be able to encode/decode double on these platforms, you need.
|
* To be able to encode/decode double on these platforms, you need.
|
||||||
|
|||||||
@@ -31,16 +31,9 @@ typedef enum _ChannelSettings_ModemConfig {
|
|||||||
ChannelSettings_ModemConfig_Bw125Cr48Sf4096 = 3
|
ChannelSettings_ModemConfig_Bw125Cr48Sf4096 = 3
|
||||||
} ChannelSettings_ModemConfig;
|
} ChannelSettings_ModemConfig;
|
||||||
|
|
||||||
typedef enum _DeviceState_Version {
|
|
||||||
DeviceState_Version_Unset = 0,
|
|
||||||
DeviceState_Version_Minimum = 16,
|
|
||||||
DeviceState_Version_Current = 16
|
|
||||||
} DeviceState_Version;
|
|
||||||
|
|
||||||
/* Struct definitions */
|
/* Struct definitions */
|
||||||
typedef struct _ChannelSettings {
|
typedef struct _ChannelSettings {
|
||||||
int32_t tx_power;
|
int32_t tx_power;
|
||||||
uint32_t channel_num;
|
|
||||||
ChannelSettings_ModemConfig modem_config;
|
ChannelSettings_ModemConfig modem_config;
|
||||||
pb_byte_t psk[16];
|
pb_byte_t psk[16];
|
||||||
char name[12];
|
char name[12];
|
||||||
@@ -56,6 +49,9 @@ typedef struct _MyNodeInfo {
|
|||||||
int32_t my_node_num;
|
int32_t my_node_num;
|
||||||
bool has_gps;
|
bool has_gps;
|
||||||
int32_t num_channels;
|
int32_t num_channels;
|
||||||
|
char region[12];
|
||||||
|
char hw_model[12];
|
||||||
|
char firmware_version[12];
|
||||||
} MyNodeInfo;
|
} MyNodeInfo;
|
||||||
|
|
||||||
typedef struct _Position {
|
typedef struct _Position {
|
||||||
@@ -63,7 +59,6 @@ typedef struct _Position {
|
|||||||
double longitude;
|
double longitude;
|
||||||
int32_t altitude;
|
int32_t altitude;
|
||||||
int32_t battery_level;
|
int32_t battery_level;
|
||||||
bool from_hardware;
|
|
||||||
uint32_t time;
|
uint32_t time;
|
||||||
} Position;
|
} Position;
|
||||||
|
|
||||||
@@ -136,9 +131,9 @@ typedef struct _DeviceState {
|
|||||||
NodeInfo node_db[32];
|
NodeInfo node_db[32];
|
||||||
pb_size_t receive_queue_count;
|
pb_size_t receive_queue_count;
|
||||||
MeshPacket receive_queue[32];
|
MeshPacket receive_queue[32];
|
||||||
DeviceState_Version version;
|
|
||||||
bool has_rx_text_message;
|
bool has_rx_text_message;
|
||||||
MeshPacket rx_text_message;
|
MeshPacket rx_text_message;
|
||||||
|
uint32_t version;
|
||||||
} DeviceState;
|
} DeviceState;
|
||||||
|
|
||||||
typedef struct _FromRadio {
|
typedef struct _FromRadio {
|
||||||
@@ -170,42 +165,37 @@ typedef struct _ToRadio {
|
|||||||
#define _ChannelSettings_ModemConfig_MAX ChannelSettings_ModemConfig_Bw125Cr48Sf4096
|
#define _ChannelSettings_ModemConfig_MAX ChannelSettings_ModemConfig_Bw125Cr48Sf4096
|
||||||
#define _ChannelSettings_ModemConfig_ARRAYSIZE ((ChannelSettings_ModemConfig)(ChannelSettings_ModemConfig_Bw125Cr48Sf4096+1))
|
#define _ChannelSettings_ModemConfig_ARRAYSIZE ((ChannelSettings_ModemConfig)(ChannelSettings_ModemConfig_Bw125Cr48Sf4096+1))
|
||||||
|
|
||||||
#define _DeviceState_Version_MIN DeviceState_Version_Unset
|
|
||||||
#define _DeviceState_Version_MAX DeviceState_Version_Current
|
|
||||||
#define _DeviceState_Version_ARRAYSIZE ((DeviceState_Version)(DeviceState_Version_Current+1))
|
|
||||||
|
|
||||||
|
|
||||||
/* Initializer values for message structs */
|
/* Initializer values for message structs */
|
||||||
#define Position_init_default {0, 0, 0, 0, 0, 0}
|
#define Position_init_default {0, 0, 0, 0, 0}
|
||||||
#define Data_init_default {_Data_Type_MIN, {0, {0}}}
|
#define Data_init_default {_Data_Type_MIN, {0, {0}}}
|
||||||
#define User_init_default {"", "", "", {0}}
|
#define User_init_default {"", "", "", {0}}
|
||||||
#define SubPacket_init_default {0, {Position_init_default}, 0}
|
#define SubPacket_init_default {0, {Position_init_default}, 0}
|
||||||
#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0}
|
#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0}
|
||||||
#define ChannelSettings_init_default {0, 0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
|
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {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, 0}
|
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 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}
|
#define MyNodeInfo_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, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, _DeviceState_Version_MIN, false, MeshPacket_init_default}
|
#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, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, false, MeshPacket_init_default, 0}
|
||||||
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
|
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
|
||||||
#define ToRadio_init_default {0, {MeshPacket_init_default}}
|
#define ToRadio_init_default {0, {MeshPacket_init_default}}
|
||||||
#define Position_init_zero {0, 0, 0, 0, 0, 0}
|
#define Position_init_zero {0, 0, 0, 0, 0}
|
||||||
#define Data_init_zero {_Data_Type_MIN, {0, {0}}}
|
#define Data_init_zero {_Data_Type_MIN, {0, {0}}}
|
||||||
#define User_init_zero {"", "", "", {0}}
|
#define User_init_zero {"", "", "", {0}}
|
||||||
#define SubPacket_init_zero {0, {Position_init_zero}, 0}
|
#define SubPacket_init_zero {0, {Position_init_zero}, 0}
|
||||||
#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0}
|
#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0}
|
||||||
#define ChannelSettings_init_zero {0, 0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
|
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {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, 0}
|
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 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}
|
#define MyNodeInfo_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, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, _DeviceState_Version_MIN, false, MeshPacket_init_zero}
|
#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, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, false, MeshPacket_init_zero, 0}
|
||||||
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
|
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
|
||||||
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
|
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
|
||||||
|
|
||||||
/* Field tags (for use in manual encoding/decoding) */
|
/* Field tags (for use in manual encoding/decoding) */
|
||||||
#define ChannelSettings_tx_power_tag 1
|
#define ChannelSettings_tx_power_tag 1
|
||||||
#define ChannelSettings_channel_num_tag 2
|
|
||||||
#define ChannelSettings_modem_config_tag 3
|
#define ChannelSettings_modem_config_tag 3
|
||||||
#define ChannelSettings_psk_tag 4
|
#define ChannelSettings_psk_tag 4
|
||||||
#define ChannelSettings_name_tag 5
|
#define ChannelSettings_name_tag 5
|
||||||
@@ -214,11 +204,13 @@ typedef struct _ToRadio {
|
|||||||
#define MyNodeInfo_my_node_num_tag 1
|
#define MyNodeInfo_my_node_num_tag 1
|
||||||
#define MyNodeInfo_has_gps_tag 2
|
#define MyNodeInfo_has_gps_tag 2
|
||||||
#define MyNodeInfo_num_channels_tag 3
|
#define MyNodeInfo_num_channels_tag 3
|
||||||
|
#define MyNodeInfo_region_tag 4
|
||||||
|
#define MyNodeInfo_hw_model_tag 5
|
||||||
|
#define MyNodeInfo_firmware_version_tag 6
|
||||||
#define Position_latitude_tag 1
|
#define Position_latitude_tag 1
|
||||||
#define Position_longitude_tag 2
|
#define Position_longitude_tag 2
|
||||||
#define Position_altitude_tag 3
|
#define Position_altitude_tag 3
|
||||||
#define Position_battery_level_tag 4
|
#define Position_battery_level_tag 4
|
||||||
#define Position_from_hardware_tag 5
|
|
||||||
#define Position_time_tag 6
|
#define Position_time_tag 6
|
||||||
#define RadioConfig_UserPreferences_position_broadcast_secs_tag 1
|
#define RadioConfig_UserPreferences_position_broadcast_secs_tag 1
|
||||||
#define RadioConfig_UserPreferences_send_owner_interval_tag 2
|
#define RadioConfig_UserPreferences_send_owner_interval_tag 2
|
||||||
@@ -257,7 +249,7 @@ typedef struct _ToRadio {
|
|||||||
#define DeviceState_owner_tag 3
|
#define DeviceState_owner_tag 3
|
||||||
#define DeviceState_node_db_tag 4
|
#define DeviceState_node_db_tag 4
|
||||||
#define DeviceState_receive_queue_tag 5
|
#define DeviceState_receive_queue_tag 5
|
||||||
#define DeviceState_version_tag 6
|
#define DeviceState_version_tag 8
|
||||||
#define DeviceState_rx_text_message_tag 7
|
#define DeviceState_rx_text_message_tag 7
|
||||||
#define FromRadio_packet_tag 2
|
#define FromRadio_packet_tag 2
|
||||||
#define FromRadio_num_tag 1
|
#define FromRadio_num_tag 1
|
||||||
@@ -269,7 +261,6 @@ X(a, STATIC, SINGULAR, DOUBLE, latitude, 1) \
|
|||||||
X(a, STATIC, SINGULAR, DOUBLE, longitude, 2) \
|
X(a, STATIC, SINGULAR, DOUBLE, longitude, 2) \
|
||||||
X(a, STATIC, SINGULAR, INT32, altitude, 3) \
|
X(a, STATIC, SINGULAR, INT32, altitude, 3) \
|
||||||
X(a, STATIC, SINGULAR, INT32, battery_level, 4) \
|
X(a, STATIC, SINGULAR, INT32, battery_level, 4) \
|
||||||
X(a, STATIC, SINGULAR, BOOL, from_hardware, 5) \
|
|
||||||
X(a, STATIC, SINGULAR, UINT32, time, 6)
|
X(a, STATIC, SINGULAR, UINT32, time, 6)
|
||||||
#define Position_CALLBACK NULL
|
#define Position_CALLBACK NULL
|
||||||
#define Position_DEFAULT NULL
|
#define Position_DEFAULT NULL
|
||||||
@@ -310,7 +301,6 @@ X(a, STATIC, SINGULAR, UINT32, rx_time, 4)
|
|||||||
|
|
||||||
#define ChannelSettings_FIELDLIST(X, a) \
|
#define ChannelSettings_FIELDLIST(X, a) \
|
||||||
X(a, STATIC, SINGULAR, INT32, tx_power, 1) \
|
X(a, STATIC, SINGULAR, INT32, tx_power, 1) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, channel_num, 2) \
|
|
||||||
X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \
|
X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \
|
||||||
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, psk, 4) \
|
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, psk, 4) \
|
||||||
X(a, STATIC, SINGULAR, STRING, name, 5)
|
X(a, STATIC, SINGULAR, STRING, name, 5)
|
||||||
@@ -356,7 +346,10 @@ X(a, STATIC, SINGULAR, INT32, frequency_error, 6)
|
|||||||
#define MyNodeInfo_FIELDLIST(X, a) \
|
#define MyNodeInfo_FIELDLIST(X, a) \
|
||||||
X(a, STATIC, SINGULAR, INT32, my_node_num, 1) \
|
X(a, STATIC, SINGULAR, INT32, my_node_num, 1) \
|
||||||
X(a, STATIC, SINGULAR, BOOL, has_gps, 2) \
|
X(a, STATIC, SINGULAR, BOOL, has_gps, 2) \
|
||||||
X(a, STATIC, SINGULAR, INT32, num_channels, 3)
|
X(a, STATIC, SINGULAR, INT32, num_channels, 3) \
|
||||||
|
X(a, STATIC, SINGULAR, STRING, region, 4) \
|
||||||
|
X(a, STATIC, SINGULAR, STRING, hw_model, 5) \
|
||||||
|
X(a, STATIC, SINGULAR, STRING, firmware_version, 6)
|
||||||
#define MyNodeInfo_CALLBACK NULL
|
#define MyNodeInfo_CALLBACK NULL
|
||||||
#define MyNodeInfo_DEFAULT NULL
|
#define MyNodeInfo_DEFAULT NULL
|
||||||
|
|
||||||
@@ -366,8 +359,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \
|
|||||||
X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \
|
X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \
|
||||||
X(a, STATIC, REPEATED, MESSAGE, node_db, 4) \
|
X(a, STATIC, REPEATED, MESSAGE, node_db, 4) \
|
||||||
X(a, STATIC, REPEATED, MESSAGE, receive_queue, 5) \
|
X(a, STATIC, REPEATED, MESSAGE, receive_queue, 5) \
|
||||||
X(a, STATIC, SINGULAR, UENUM, version, 6) \
|
X(a, STATIC, OPTIONAL, MESSAGE, rx_text_message, 7) \
|
||||||
X(a, STATIC, OPTIONAL, MESSAGE, rx_text_message, 7)
|
X(a, STATIC, SINGULAR, UINT32, version, 8)
|
||||||
#define DeviceState_CALLBACK NULL
|
#define DeviceState_CALLBACK NULL
|
||||||
#define DeviceState_DEFAULT NULL
|
#define DeviceState_DEFAULT NULL
|
||||||
#define DeviceState_radio_MSGTYPE RadioConfig
|
#define DeviceState_radio_MSGTYPE RadioConfig
|
||||||
@@ -420,17 +413,17 @@ extern const pb_msgdesc_t ToRadio_msg;
|
|||||||
#define ToRadio_fields &ToRadio_msg
|
#define ToRadio_fields &ToRadio_msg
|
||||||
|
|
||||||
/* Maximum encoded size of messages (where known) */
|
/* Maximum encoded size of messages (where known) */
|
||||||
#define Position_size 48
|
#define Position_size 46
|
||||||
#define Data_size 256
|
#define Data_size 256
|
||||||
#define User_size 72
|
#define User_size 72
|
||||||
#define SubPacket_size 261
|
#define SubPacket_size 261
|
||||||
#define MeshPacket_size 292
|
#define MeshPacket_size 292
|
||||||
#define ChannelSettings_size 50
|
#define ChannelSettings_size 44
|
||||||
#define RadioConfig_size 126
|
#define RadioConfig_size 120
|
||||||
#define RadioConfig_UserPreferences_size 72
|
#define RadioConfig_UserPreferences_size 72
|
||||||
#define NodeInfo_size 157
|
#define NodeInfo_size 155
|
||||||
#define MyNodeInfo_size 24
|
#define MyNodeInfo_size 63
|
||||||
#define DeviceState_size 15085
|
#define DeviceState_size 15058
|
||||||
#define FromRadio_size 301
|
#define FromRadio_size 301
|
||||||
#define ToRadio_size 295
|
#define ToRadio_size 295
|
||||||
|
|
||||||
|
|||||||
519
src/screen.cpp
519
src/screen.cpp
@@ -20,53 +20,37 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <OLEDDisplay.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include "SSD1306Wire.h"
|
|
||||||
#include "OLEDDisplay.h"
|
|
||||||
#include "images.h"
|
|
||||||
#include "fonts.h"
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "OLEDDisplayUi.h"
|
|
||||||
#include "screen.h"
|
|
||||||
#include "mesh-pb-constants.h"
|
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "fonts.h"
|
||||||
|
#include "images.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "screen.h"
|
||||||
|
|
||||||
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
|
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
|
||||||
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
||||||
#define SCREEN_WIDTH 128
|
#define SCREEN_WIDTH 128
|
||||||
#define SCREEN_HEIGHT 64
|
#define SCREEN_HEIGHT 64
|
||||||
|
#define TRANSITION_FRAMERATE 30 // fps
|
||||||
#ifdef I2C_SDA
|
#define IDLE_FRAMERATE 10 // in fps
|
||||||
SSD1306Wire dispdev(SSD1306_ADDRESS, I2C_SDA, I2C_SCL);
|
#define COMPASS_DIAM 44
|
||||||
#else
|
|
||||||
SSD1306Wire dispdev(SSD1306_ADDRESS, 0, 0); // fake values to keep build happy, we won't ever init
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool disp; // true if we are using display
|
|
||||||
bool screenOn; // true if the display is currently powered
|
|
||||||
|
|
||||||
OLEDDisplayUi ui(&dispdev);
|
|
||||||
|
|
||||||
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
||||||
// A text message frame + debug frame + all the node infos
|
|
||||||
FrameCallback nonBootFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
|
||||||
|
|
||||||
Screen screen;
|
namespace meshtastic
|
||||||
static bool showingBluetooth;
|
|
||||||
|
|
||||||
/// If set to true (possibly from an ISR), we should turn on the screen the next time our idle loop runs.
|
|
||||||
static bool showingBootScreen = true; // start by showing the bootscreen
|
|
||||||
|
|
||||||
bool Screen::isOn() { return screenOn; }
|
|
||||||
|
|
||||||
void msOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
|
||||||
{
|
{
|
||||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
|
||||||
display->drawString(128, 0, String(millis()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
// A text message frame + debug frame + all the node infos
|
||||||
|
static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
||||||
|
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||||
|
static char btPIN[16] = "888888";
|
||||||
|
|
||||||
|
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
|
||||||
@@ -77,16 +61,10 @@ void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(ArialMT_Plain_16);
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
||||||
|
|
||||||
ui.disableIndicator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char btPIN[16] = "888888";
|
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
|
||||||
void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
{
|
||||||
// Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file
|
|
||||||
// Besides the default fonts there will be a program to convert TrueType fonts into this format
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(ArialMT_Plain_16);
|
||||||
display->drawString(64 + x, 2 + y, "Bluetooth");
|
display->drawString(64 + x, 2 + y, "Bluetooth");
|
||||||
@@ -97,80 +75,45 @@ void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->setFont(ArialMT_Plain_24);
|
display->setFont(ArialMT_Plain_24);
|
||||||
display->drawString(64 + x, 22 + y, btPIN);
|
display->drawString(64 + x, 22 + y, btPIN);
|
||||||
|
|
||||||
ui.disableIndicator();
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawFrame2(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
// Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file
|
|
||||||
// Besides the default fonts there will be a program to convert TrueType fonts into this format
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
|
||||||
display->drawString(0 + x, 10 + y, "Arial 10");
|
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_16);
|
|
||||||
display->drawString(0 + x, 20 + y, "Arial 16");
|
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_24);
|
|
||||||
display->drawString(0 + x, 34 + y, "Arial 24");
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawFrame3(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
// Text alignment demo
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
|
||||||
|
|
||||||
// The coordinates define the left starting point of the text
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
display->drawString(0 + x, 11 + y, "Left aligned (0,10)");
|
|
||||||
|
|
||||||
// The coordinates define the center of the text
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
||||||
display->drawString(64 + x, 22 + y, "Center aligned (64,22)");
|
|
||||||
|
|
||||||
// The coordinates define the right end of the text
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
|
||||||
display->drawString(128 + x, 33 + y, "Right aligned (128,33)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the last text message we received
|
/// Draw the last text message we received
|
||||||
void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
MeshPacket &mp = devicestate.rx_text_message;
|
MeshPacket &mp = devicestate.rx_text_message;
|
||||||
NodeInfo *node = nodeDB.getNode(mp.from);
|
NodeInfo *node = nodeDB.getNode(mp.from);
|
||||||
|
// DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from,
|
||||||
|
// mp.payload.variant.data.payload.bytes);
|
||||||
|
|
||||||
// Demo for drawStringMaxWidth:
|
// Demo for drawStringMaxWidth:
|
||||||
// with the third parameter you can define the width after which words will be wrapped.
|
// with the third parameter you can define the width after which words will
|
||||||
// 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(ArialMT_Plain_16);
|
||||||
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(ArialMT_Plain_10);
|
||||||
|
|
||||||
|
// the max length of this buffer is much longer than we can possibly print
|
||||||
static char tempBuf[96];
|
static char tempBuf[96];
|
||||||
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.variant.data.payload.bytes); // the max length of this buffer is much longer than we can possibly print
|
snprintf(tempBuf, sizeof(tempBuf), " %s",
|
||||||
|
mp.payload.variant.data.payload.bytes);
|
||||||
|
|
||||||
display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf);
|
display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf);
|
||||||
|
|
||||||
// ui.disableIndicator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a series of fields in a column, wrapping to multiple colums if needed
|
/// Draw a series of fields in a column, wrapping to multiple colums if needed
|
||||||
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
||||||
{
|
{
|
||||||
// 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);
|
||||||
|
|
||||||
const char **f = fields;
|
const char **f = fields;
|
||||||
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;
|
||||||
if (yo > SCREEN_HEIGHT - FONT_HEIGHT)
|
if (yo > SCREEN_HEIGHT - FONT_HEIGHT) {
|
||||||
{
|
|
||||||
xo += SCREEN_WIDTH / 2;
|
xo += SCREEN_WIDTH / 2;
|
||||||
yo = 0;
|
yo = 0;
|
||||||
}
|
}
|
||||||
@@ -180,21 +123,23 @@ void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields
|
|||||||
|
|
||||||
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
||||||
/// @return the max y we ended up printing to
|
/// @return the max y we ended up printing to
|
||||||
uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
||||||
{
|
{
|
||||||
// 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);
|
||||||
|
|
||||||
const char **f = fields;
|
const char **f = fields;
|
||||||
int xo = x, yo = y;
|
int xo = x, yo = y;
|
||||||
while (*f)
|
const int COLUMNS = 2; // hardwired for two columns per row....
|
||||||
{
|
int col = 0; // track which column we are on
|
||||||
|
while (*f) {
|
||||||
display->drawString(xo, yo, *f);
|
display->drawString(xo, yo, *f);
|
||||||
xo += SCREEN_WIDTH / 2; // hardwired for two columns per row....
|
xo += SCREEN_WIDTH / COLUMNS;
|
||||||
if (xo >= SCREEN_WIDTH)
|
// Wrap to next row, if needed.
|
||||||
{
|
if (++col > COLUMNS) {
|
||||||
|
xo = x;
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT;
|
||||||
xo = 0;
|
col = 0;
|
||||||
}
|
}
|
||||||
f++;
|
f++;
|
||||||
}
|
}
|
||||||
@@ -204,8 +149,9 @@ uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **field
|
|||||||
return yo;
|
return yo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ported from my old java code, returns distance in meters along the globe surface (by magic?)
|
/// Ported from my old java code, returns distance in meters along the globe
|
||||||
float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
/// surface (by magic?)
|
||||||
|
static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
||||||
{
|
{
|
||||||
double pk = (180 / 3.14169);
|
double pk = (180 / 3.14169);
|
||||||
double a1 = lat_a / pk;
|
double a1 = lat_a / pk;
|
||||||
@@ -214,10 +160,8 @@ float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
|||||||
double b2 = lng_b / pk;
|
double b2 = lng_b / pk;
|
||||||
double cos_b1 = cos(b1);
|
double cos_b1 = cos(b1);
|
||||||
double cos_a1 = cos(a1);
|
double cos_a1 = cos(a1);
|
||||||
double t1 =
|
double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
|
||||||
cos_a1 * cos(a2) * cos_b1 * cos(b2);
|
double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2);
|
||||||
double t2 =
|
|
||||||
cos_a1 * sin(a2) * cos_b1 * sin(b2);
|
|
||||||
double t3 = sin(a1) * sin(b1);
|
double t3 = sin(a1) * sin(b1);
|
||||||
double tt = acos(t1 + t2 + t3);
|
double tt = acos(t1 + t2 + t3);
|
||||||
if (isnan(tt))
|
if (isnan(tt))
|
||||||
@@ -226,18 +170,19 @@ float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
|||||||
return (float)(6366000 * tt);
|
return (float)(6366000 * tt);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double toRadians(double deg)
|
static inline double toRadians(double deg)
|
||||||
{
|
{
|
||||||
return deg * PI / 180;
|
return deg * PI / 180;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double toDegrees(double r)
|
static inline double toDegrees(double r)
|
||||||
{
|
{
|
||||||
return r * 180 / PI;
|
return r * 180 / PI;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the bearing in degrees between two points on Earth. Ported from my old Gaggle android app.
|
* Computes the bearing in degrees between two points on Earth. Ported from my
|
||||||
|
* old Gaggle android app.
|
||||||
*
|
*
|
||||||
* @param lat1
|
* @param lat1
|
||||||
* Latitude of the first point
|
* Latitude of the first point
|
||||||
@@ -250,7 +195,7 @@ inline double toDegrees(double r)
|
|||||||
* @return Bearing between the two points in radians. A value of 0 means due
|
* @return Bearing between the two points in radians. A value of 0 means due
|
||||||
* north.
|
* north.
|
||||||
*/
|
*/
|
||||||
float bearing(double lat1, double lon1, double lat2, double lon2)
|
static float bearing(double lat1, double lon1, double lat2, double lon2)
|
||||||
{
|
{
|
||||||
double lat1Rad = toRadians(lat1);
|
double lat1Rad = toRadians(lat1);
|
||||||
double lat2Rad = toRadians(lat2);
|
double lat2Rad = toRadians(lat2);
|
||||||
@@ -260,10 +205,13 @@ float bearing(double lat1, double lon1, double lat2, double lon2)
|
|||||||
return atan2(y, x);
|
return atan2(y, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
/// A basic 2D point class for drawing
|
/// A basic 2D point class for drawing
|
||||||
class Point
|
class Point
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
float x, y;
|
float x, y;
|
||||||
|
|
||||||
Point(float _x, float _y) : x(_x), y(_y) {}
|
Point(float _x, float _y) : x(_x), y(_y) {}
|
||||||
@@ -271,10 +219,8 @@ public:
|
|||||||
/// Apply a rotation around zero (standard rotation matrix math)
|
/// Apply a rotation around zero (standard rotation matrix math)
|
||||||
void rotate(float radian)
|
void rotate(float radian)
|
||||||
{
|
{
|
||||||
float cos = cosf(radian),
|
float cos = cosf(radian), sin = sinf(radian);
|
||||||
sin = sinf(radian);
|
float rx = x * cos - y * sin, ry = x * sin + y * cos;
|
||||||
float rx = x * cos - y * sin,
|
|
||||||
ry = x * sin + y * cos;
|
|
||||||
|
|
||||||
x = rx;
|
x = rx;
|
||||||
y = ry;
|
y = ry;
|
||||||
@@ -293,7 +239,9 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
|
} // namespace
|
||||||
|
|
||||||
|
static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
|
||||||
{
|
{
|
||||||
d->drawLine(p1.x, p1.y, p2.x, p2.y);
|
d->drawLine(p1.x, p1.y, p2.x, p2.y);
|
||||||
}
|
}
|
||||||
@@ -301,15 +249,15 @@ void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
|
|||||||
/**
|
/**
|
||||||
* Given a recent lat/lon return a guess of the heading the user is walking on.
|
* Given a recent lat/lon return a guess of the heading the user is walking on.
|
||||||
*
|
*
|
||||||
* We keep a series of "after you've gone 10 meters, what is your heading since the last reference point?"
|
* We keep a series of "after you've gone 10 meters, what is your heading since
|
||||||
|
* the last reference point?"
|
||||||
*/
|
*/
|
||||||
float estimatedHeading(double lat, double lon)
|
static float estimatedHeading(double lat, double lon)
|
||||||
{
|
{
|
||||||
static double oldLat, oldLon;
|
static double oldLat, oldLon;
|
||||||
static float b;
|
static float b;
|
||||||
|
|
||||||
if (oldLat == 0)
|
if (oldLat == 0) {
|
||||||
{
|
|
||||||
// just prepare for next time
|
// just prepare for next time
|
||||||
oldLat = lat;
|
oldLat = lat;
|
||||||
oldLon = lon;
|
oldLon = lon;
|
||||||
@@ -328,28 +276,28 @@ float estimatedHeading(double lat, double lon)
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sometimes we will have Position objects that only have a time, so check for valid lat/lon
|
/// Sometimes we will have Position objects that only have a time, so check for
|
||||||
bool hasPosition(NodeInfo *n)
|
/// valid lat/lon
|
||||||
|
static bool hasPosition(NodeInfo *n)
|
||||||
{
|
{
|
||||||
return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0);
|
return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0);
|
||||||
}
|
}
|
||||||
#define COMPASS_DIAM 44
|
|
||||||
|
|
||||||
/// We will skip one node - the one for us, so we just blindly loop over all nodes
|
/// We will skip one node - the one for us, so we just blindly loop over all
|
||||||
|
/// nodes
|
||||||
static size_t nodeIndex;
|
static size_t nodeIndex;
|
||||||
static int8_t prevFrame = -1;
|
static int8_t prevFrame = -1;
|
||||||
|
|
||||||
void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
// We only advance our nodeIndex if the frame # has changed - because drawNodeInfo will be called repeatedly while the frame is shown
|
// We only advance our nodeIndex if the frame # has changed - because
|
||||||
if (state->currentFrame != prevFrame)
|
// drawNodeInfo will be called repeatedly while the frame is shown
|
||||||
{
|
if (state->currentFrame != prevFrame) {
|
||||||
prevFrame = state->currentFrame;
|
prevFrame = state->currentFrame;
|
||||||
|
|
||||||
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
||||||
NodeInfo *n = nodeDB.getNodeByIndex(nodeIndex);
|
NodeInfo *n = nodeDB.getNodeByIndex(nodeIndex);
|
||||||
if (n->num == nodeDB.getNodeNum())
|
if (n->num == nodeDB.getNodeNum()) {
|
||||||
{
|
|
||||||
// Don't show our node, just skip to next
|
// Don't show our node, just skip to next
|
||||||
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
||||||
}
|
}
|
||||||
@@ -377,14 +325,14 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
|
|||||||
snprintf(lastStr, sizeof(lastStr), "%d hours ago", agoSecs / 60 / 60);
|
snprintf(lastStr, sizeof(lastStr), "%d hours ago", agoSecs / 60 / 60);
|
||||||
|
|
||||||
static float simRadian;
|
static float simRadian;
|
||||||
simRadian += 0.1; // For testing, have the compass spin unless both locations are valid
|
simRadian += 0.1; // For testing, have the compass spin unless both
|
||||||
|
// locations are valid
|
||||||
|
|
||||||
static char distStr[20];
|
static char distStr[20];
|
||||||
*distStr = 0; // might not have location data
|
*distStr = 0; // might not have location data
|
||||||
float headingRadian = simRadian;
|
float headingRadian = simRadian;
|
||||||
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
||||||
if (ourNode && hasPosition(ourNode) && hasPosition(node))
|
if (ourNode && hasPosition(ourNode) && hasPosition(node)) {
|
||||||
{
|
|
||||||
Position &op = ourNode->position, &p = node->position;
|
Position &op = ourNode->position, &p = node->position;
|
||||||
float d = latLongToMeter(p.latitude, p.longitude, op.latitude, op.longitude);
|
float d = latLongToMeter(p.latitude, p.longitude, op.latitude, op.longitude);
|
||||||
if (d < 2000)
|
if (d < 2000)
|
||||||
@@ -392,23 +340,20 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
|
|||||||
else
|
else
|
||||||
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
||||||
|
|
||||||
// FIXME, also keep the guess at the operators heading and add/substract it. currently we don't do this and instead draw north up only.
|
// FIXME, also keep the guess at the operators heading and add/substract
|
||||||
|
// it. currently we don't do this and instead draw north up only.
|
||||||
float bearingToOther = bearing(p.latitude, p.longitude, op.latitude, op.longitude);
|
float bearingToOther = bearing(p.latitude, p.longitude, op.latitude, op.longitude);
|
||||||
float myHeading = estimatedHeading(p.latitude, p.longitude);
|
float myHeading = estimatedHeading(p.latitude, p.longitude);
|
||||||
headingRadian = bearingToOther - myHeading;
|
headingRadian = bearingToOther - myHeading;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *fields[] = {
|
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
||||||
username,
|
|
||||||
distStr,
|
|
||||||
signalStr,
|
|
||||||
lastStr,
|
|
||||||
NULL};
|
|
||||||
drawColumns(display, x, y, fields);
|
drawColumns(display, x, y, fields);
|
||||||
|
|
||||||
// coordinates for the center of the compass
|
// coordinates for the center of the compass
|
||||||
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
||||||
// display->drawXbm(compassX, compassY, compass_width, compass_height, (const uint8_t *)compass_bits);
|
// display->drawXbm(compassX, compassY, compass_width, compass_height,
|
||||||
|
// (const uint8_t *)compass_bits);
|
||||||
|
|
||||||
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
||||||
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
||||||
@@ -416,8 +361,7 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
|
|||||||
|
|
||||||
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
|
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++) {
|
||||||
{
|
|
||||||
points[i]->rotate(headingRadian);
|
points[i]->rotate(headingRadian);
|
||||||
points[i]->scale(COMPASS_DIAM * 0.6);
|
points[i]->scale(COMPASS_DIAM * 0.6);
|
||||||
points[i]->translate(compassX, compassY);
|
points[i]->translate(compassX, compassY);
|
||||||
@@ -429,7 +373,7 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
|
|||||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(ArialMT_Plain_10);
|
||||||
|
|
||||||
@@ -442,28 +386,23 @@ void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
|
|||||||
static char channelStr[20];
|
static char channelStr[20];
|
||||||
snprintf(channelStr, sizeof(channelStr), "%s", channelSettings.name);
|
snprintf(channelStr, sizeof(channelStr), "%s", channelSettings.name);
|
||||||
|
|
||||||
const char *fields[] = {
|
// We don't show battery levels yet - for now just lie and show debug info
|
||||||
"Batt 89%",
|
static char batStr[20];
|
||||||
"GPS 75%",
|
snprintf(batStr, sizeof(batStr), "Batt %x%%", (isCharging << 1) + isUSBPowered);
|
||||||
usersStr,
|
|
||||||
channelStr,
|
static char gpsStr[20];
|
||||||
NULL};
|
if (myNodeInfo.has_gps)
|
||||||
|
snprintf(gpsStr, sizeof(gpsStr), "GPS %d%%",
|
||||||
|
75); // FIXME, use something based on hdop
|
||||||
|
else
|
||||||
|
gpsStr[0] = '\0'; // Just show emptystring
|
||||||
|
|
||||||
|
const char *fields[] = {batStr, gpsStr, usersStr, channelStr, NULL};
|
||||||
uint32_t yo = drawRows(display, x, y, fields);
|
uint32_t yo = drawRows(display, x, y, fields);
|
||||||
|
|
||||||
display->drawLogBuffer(x, yo);
|
display->drawLogBuffer(x, yo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This array keeps function pointers to all frames
|
|
||||||
// frames are the single views that slide in
|
|
||||||
FrameCallback bootFrames[] = {drawBootScreen};
|
|
||||||
|
|
||||||
// Overlays are statically drawn on top of a frame eg. a clock
|
|
||||||
OverlayCallback overlays[] = {/* msOverlay */};
|
|
||||||
|
|
||||||
// how many frames are there?
|
|
||||||
const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
|
||||||
const int overlaysCount = sizeof(overlays) / sizeof(overlays[0]);
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
void _screen_header()
|
void _screen_header()
|
||||||
{
|
{
|
||||||
@@ -488,181 +427,156 @@ void _screen_header()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void Screen::setOn(bool on)
|
Screen::Screen(uint8_t address, uint8_t sda, uint8_t scl)
|
||||||
|
: cmdQueue(32), useDisplay(sda || scl), dispdev(address, sda, scl), ui(&dispdev)
|
||||||
{
|
{
|
||||||
if (!disp)
|
}
|
||||||
|
|
||||||
|
void Screen::handleSetOn(bool on)
|
||||||
|
{
|
||||||
|
if (!useDisplay)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (on != screenOn)
|
if (on != screenOn) {
|
||||||
{
|
|
||||||
if (on) {
|
if (on) {
|
||||||
|
DEBUG_MSG("Turning on screen\n");
|
||||||
dispdev.displayOn();
|
dispdev.displayOn();
|
||||||
setPeriod(1); // redraw ASAP
|
} else {
|
||||||
}
|
DEBUG_MSG("Turning off screen\n");
|
||||||
else
|
|
||||||
dispdev.displayOff();
|
dispdev.displayOff();
|
||||||
|
}
|
||||||
screenOn = on;
|
screenOn = on;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void screen_print(const char *text, uint8_t x, uint8_t y, uint8_t alignment)
|
|
||||||
{
|
|
||||||
DEBUG_MSG(text);
|
|
||||||
|
|
||||||
if (!disp)
|
|
||||||
return;
|
|
||||||
|
|
||||||
dispdev.setTextAlignment((OLEDDISPLAY_TEXT_ALIGNMENT)alignment);
|
|
||||||
dispdev.drawString(x, y, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void screen_print(const char *text)
|
|
||||||
{
|
|
||||||
DEBUG_MSG("Screen: %s", text);
|
|
||||||
if (!disp)
|
|
||||||
return;
|
|
||||||
|
|
||||||
dispdev.print(text);
|
|
||||||
// ui.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Screen::setup()
|
void Screen::setup()
|
||||||
{
|
{
|
||||||
#ifdef I2C_SDA
|
if (!useDisplay)
|
||||||
// Display instance
|
return;
|
||||||
disp = true;
|
|
||||||
|
|
||||||
// The ESP is capable of rendering 60fps in 80Mhz mode
|
|
||||||
// but that won't give you much time for anything else
|
|
||||||
// run it in 160Mhz mode or just set it to 30 fps
|
|
||||||
// We do this now in loop()
|
|
||||||
// ui.setTargetFPS(30);
|
|
||||||
|
|
||||||
// Customize the active and inactive symbol
|
|
||||||
//ui.setActiveSymbol(activeSymbol);
|
|
||||||
//ui.setInactiveSymbol(inactiveSymbol);
|
|
||||||
|
|
||||||
ui.setTimePerTransition(300); // msecs
|
|
||||||
|
|
||||||
// You can change this to
|
|
||||||
// TOP, LEFT, BOTTOM, RIGHT
|
|
||||||
ui.setIndicatorPosition(BOTTOM);
|
|
||||||
|
|
||||||
// Defines where the first frame is located in the bar.
|
|
||||||
ui.setIndicatorDirection(LEFT_RIGHT);
|
|
||||||
|
|
||||||
// You can change the transition that is used
|
|
||||||
// SLIDE_LEFT, SLIDE_RIGHT, SLIDE_UP, SLIDE_DOWN
|
|
||||||
ui.setFrameAnimation(SLIDE_LEFT);
|
|
||||||
|
|
||||||
// Add frames - we subtract one from the framecount so there won't be a visual glitch when we take the boot screen out of the sequence.
|
|
||||||
ui.setFrames(bootFrames, bootFrameCount);
|
|
||||||
|
|
||||||
// Add overlays
|
|
||||||
ui.setOverlays(overlays, overlaysCount);
|
|
||||||
|
|
||||||
// Initialising the UI will init the display too.
|
|
||||||
ui.init();
|
|
||||||
|
|
||||||
// Scroll buffer
|
|
||||||
dispdev.setLogBuffer(3, 32);
|
|
||||||
|
|
||||||
setOn(true); // update our screenOn bool
|
|
||||||
|
|
||||||
|
// TODO(girts): how many of the devices come with the bicolor displays? With
|
||||||
|
// this enabled, the logo looklooks nice, but the regular screens look a bit
|
||||||
|
// wacky as the text crosses the color boundary and there's a 1px gap.
|
||||||
#ifdef BICOLOR_DISPLAY
|
#ifdef BICOLOR_DISPLAY
|
||||||
dispdev.flipScreenVertically(); // looks better without this on lora32
|
dispdev.flipScreenVertically(); // looks better without this on lora32
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// dispdev.setFont(Custom_ArialMT_Plain_10);
|
// Initialising the UI will init the display too.
|
||||||
|
ui.init();
|
||||||
|
ui.setTimePerTransition(300); // msecs
|
||||||
|
ui.setIndicatorPosition(BOTTOM);
|
||||||
|
// Defines where the first frame is located in the bar.
|
||||||
|
ui.setIndicatorDirection(LEFT_RIGHT);
|
||||||
|
ui.setFrameAnimation(SLIDE_LEFT);
|
||||||
|
// Don't show the page swipe dots while in boot screen.
|
||||||
|
ui.disableAllIndicators();
|
||||||
|
|
||||||
ui.disableAutoTransition(); // we now require presses
|
// Add frames.
|
||||||
#endif
|
static FrameCallback bootFrames[] = {drawBootScreen};
|
||||||
|
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
||||||
|
ui.setFrames(bootFrames, bootFrameCount);
|
||||||
|
// No overlays.
|
||||||
|
ui.setOverlays(nullptr, 0);
|
||||||
|
|
||||||
|
// Require presses to switch between frames.
|
||||||
|
ui.disableAutoTransition();
|
||||||
|
|
||||||
|
// Set up a log buffer with 3 lines, 32 chars each.
|
||||||
|
dispdev.setLogBuffer(3, 32);
|
||||||
|
|
||||||
|
// Turn on the display hardware.
|
||||||
|
handleSetOn(true);
|
||||||
|
|
||||||
|
// On some ssd1306 clones, the first draw command is discarded, so draw it
|
||||||
|
// twice initially.
|
||||||
|
ui.update();
|
||||||
|
ui.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TRANSITION_FRAMERATE 30 // fps
|
|
||||||
#define IDLE_FRAMERATE 10 // in fps
|
|
||||||
|
|
||||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
|
||||||
|
|
||||||
void Screen::doTask()
|
void Screen::doTask()
|
||||||
{
|
{
|
||||||
if (!disp)
|
// 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) {
|
||||||
setPeriod(0);
|
setPeriod(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!screenOn)
|
// Process incoming commands.
|
||||||
{ // If we didn't just wake and the screen is still off, then stop updating until it is on again
|
for (;;) {
|
||||||
|
CmdItem cmd;
|
||||||
|
if (!cmdQueue.dequeue(&cmd, 0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (cmd.cmd) {
|
||||||
|
case Cmd::SET_ON:
|
||||||
|
handleSetOn(true);
|
||||||
|
break;
|
||||||
|
case Cmd::SET_OFF:
|
||||||
|
handleSetOn(false);
|
||||||
|
break;
|
||||||
|
case Cmd::ON_PRESS:
|
||||||
|
handleOnPress();
|
||||||
|
break;
|
||||||
|
case Cmd::START_BLUETOOTH_PIN_SCREEN:
|
||||||
|
handleStartBluetoothPinScreen(cmd.bluetooth_pin);
|
||||||
|
break;
|
||||||
|
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
|
||||||
|
case Cmd::STOP_BOOT_SCREEN:
|
||||||
|
setFrames();
|
||||||
|
break;
|
||||||
|
case Cmd::PRINT:
|
||||||
|
handlePrint(cmd.print_text);
|
||||||
|
free(cmd.print_text);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DEBUG_MSG("BUG: invalid cmd");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!screenOn) { // If we didn't just wake and the screen is still off, then
|
||||||
|
// stop updating until it is on again
|
||||||
setPeriod(0);
|
setPeriod(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 otherwise that breaks
|
// but we should only call setTargetFPS when framestate changes, because
|
||||||
// animations.
|
// otherwise that breaks animations.
|
||||||
if (targetFramerate != IDLE_FRAMERATE && ui.getUiState()->frameState == FIXED)
|
if (targetFramerate != IDLE_FRAMERATE && ui.getUiState()->frameState == FIXED) {
|
||||||
{
|
|
||||||
// oldFrameState = ui.getUiState()->frameState;
|
// oldFrameState = ui.getUiState()->frameState;
|
||||||
DEBUG_MSG("Setting idle framerate\n");
|
DEBUG_MSG("Setting idle framerate\n");
|
||||||
targetFramerate = IDLE_FRAMERATE;
|
targetFramerate = IDLE_FRAMERATE;
|
||||||
ui.setTargetFPS(targetFramerate);
|
ui.setTargetFPS(targetFramerate);
|
||||||
}
|
}
|
||||||
ui.update();
|
|
||||||
|
|
||||||
// While showing the bluetooth pair screen all of our standard screen switching is stopped
|
// While showing the bootscreen or Bluetooth pair screen all of our
|
||||||
if (!showingBluetooth)
|
// standard screen switching is stopped.
|
||||||
{
|
if (showingNormalScreen) {
|
||||||
// Once we finish showing the bootscreen, remove it from the loop
|
// TODO(girts): decouple nodeDB from screen.
|
||||||
if (showingBootScreen)
|
// standard screen loop handling ehre
|
||||||
{
|
|
||||||
if (millis() > 3 * 1000) // we show the boot screen for a few seconds only
|
|
||||||
{
|
|
||||||
showingBootScreen = false;
|
|
||||||
screen_set_frames();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // standard screen loop handling ehre
|
|
||||||
{
|
|
||||||
// If the # nodes changes, we need to regen our list of screens
|
// If the # nodes changes, we need to regen our list of screens
|
||||||
if (nodeDB.updateGUI || nodeDB.updateTextMessage)
|
if (nodeDB.updateGUI || nodeDB.updateTextMessage) {
|
||||||
{
|
setFrames();
|
||||||
screen_set_frames();
|
|
||||||
nodeDB.updateGUI = false;
|
nodeDB.updateGUI = false;
|
||||||
nodeDB.updateTextMessage = false;
|
nodeDB.updateTextMessage = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate, ui.getUiState()->frameState);
|
ui.update();
|
||||||
// If we are scrolling we need to be called soon, otherwise just 1 fps (to save CPU)
|
|
||||||
// We also ask to be called twice as fast as we really need so that any rounding errors still result
|
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
|
||||||
// with the correct framerate
|
// ui.getUiState()->frameState); If we are scrolling we need to be called
|
||||||
|
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
|
||||||
|
// as fast as we really need so that any rounding errors still result with
|
||||||
|
// the correct framerate
|
||||||
setPeriod(1000 / targetFramerate);
|
setPeriod(1000 / targetFramerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "PowerFSM.h"
|
|
||||||
|
|
||||||
// Show the bluetooth PIN screen
|
|
||||||
void screen_start_bluetooth(uint32_t pin)
|
|
||||||
{
|
|
||||||
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
|
||||||
|
|
||||||
snprintf(btPIN, sizeof(btPIN), "%06d", pin);
|
|
||||||
|
|
||||||
DEBUG_MSG("showing bluetooth screen\n");
|
|
||||||
showingBluetooth = true;
|
|
||||||
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
|
|
||||||
|
|
||||||
ui.setFrames(btFrames, 1); // Just show the bluetooth frame
|
|
||||||
// we rely on our main loop to show this screen (because we are invoked deep inside of bluetooth callbacks)
|
|
||||||
// ui.update(); // manually draw once, because I'm not sure if loop is getting called
|
|
||||||
}
|
|
||||||
|
|
||||||
// restore our regular frame list
|
// restore our regular frame list
|
||||||
void screen_set_frames()
|
void Screen::setFrames()
|
||||||
{
|
{
|
||||||
DEBUG_MSG("showing standard frames\n");
|
DEBUG_MSG("showing standard frames\n");
|
||||||
|
showingNormalScreen = true;
|
||||||
|
|
||||||
size_t numnodes = nodeDB.getNumNodes();
|
size_t numnodes = nodeDB.getNumNodes();
|
||||||
// We don't show the node info our our node (if we have it yet - we should)
|
// We don't show the node info our our node (if we have it yet - we should)
|
||||||
@@ -673,28 +587,49 @@ void screen_set_frames()
|
|||||||
|
|
||||||
// If we have a text message - show it first
|
// If we have a text message - show it first
|
||||||
if (devicestate.has_rx_text_message)
|
if (devicestate.has_rx_text_message)
|
||||||
nonBootFrames[numframes++] = drawTextMessageFrame;
|
normalFrames[numframes++] = drawTextMessageFrame;
|
||||||
|
|
||||||
// then all the nodes
|
// then all the nodes
|
||||||
for (size_t i = 0; i < numnodes; i++)
|
for (size_t i = 0; i < numnodes; i++)
|
||||||
nonBootFrames[numframes++] = drawNodeInfo;
|
normalFrames[numframes++] = drawNodeInfo;
|
||||||
|
|
||||||
// then the debug info
|
// then the debug info
|
||||||
nonBootFrames[numframes++] = drawDebugInfo;
|
normalFrames[numframes++] = drawDebugInfo;
|
||||||
|
|
||||||
ui.setFrames(nonBootFrames, numframes);
|
ui.setFrames(normalFrames, numframes);
|
||||||
showingBluetooth = false;
|
ui.enableAllIndicators();
|
||||||
|
|
||||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
|
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
||||||
|
// just changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// handle press of the button
|
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
||||||
void Screen::onPress()
|
{
|
||||||
|
DEBUG_MSG("showing bluetooth screen\n");
|
||||||
|
showingNormalScreen = false;
|
||||||
|
|
||||||
|
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
||||||
|
|
||||||
|
snprintf(btPIN, sizeof(btPIN), "%06d", pin);
|
||||||
|
|
||||||
|
ui.disableAllIndicators();
|
||||||
|
ui.setFrames(btFrames, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::handlePrint(const char *text)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Screen: %s", text);
|
||||||
|
if (!useDisplay)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dispdev.print(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
setPeriod(1); // redraw ASAP
|
||||||
ui.nextFrame();
|
ui.nextFrame();
|
||||||
|
|
||||||
@@ -705,3 +640,5 @@ void Screen::onPress()
|
|||||||
ui.setTargetFPS(targetFramerate);
|
ui.setTargetFPS(targetFramerate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
|
|||||||
134
src/screen.h
134
src/screen.h
@@ -1,39 +1,127 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <OLEDDisplayUi.h>
|
||||||
|
#include <SSD1306Wire.h>
|
||||||
|
|
||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
|
#include "TypedQueue.h"
|
||||||
|
|
||||||
void screen_print(const char * text);
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Deals with showing things on the screen of the device.
|
||||||
// Show the bluetooth PIN screen
|
//
|
||||||
void screen_start_bluetooth(uint32_t pin);
|
// Other than setup(), this class is thread-safe. All state-changing calls are
|
||||||
|
// queued and executed when the main loop calls us.
|
||||||
// restore our regular frame list
|
|
||||||
void screen_set_frames();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Slowly I'm moving screen crap into this class
|
|
||||||
*/
|
|
||||||
class Screen : public PeriodicTask
|
class Screen : public PeriodicTask
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
Screen(uint8_t address, uint8_t sda, uint8_t scl);
|
||||||
|
|
||||||
|
Screen(const Screen &) = delete;
|
||||||
|
Screen &operator=(const Screen &) = delete;
|
||||||
|
|
||||||
|
/// Initializes the UI, turns on the display, starts showing boot screen.
|
||||||
|
//
|
||||||
|
// Not thread safe - must be called before any other methods are called.
|
||||||
void setup();
|
void setup();
|
||||||
|
|
||||||
virtual void doTask();
|
/// Turns the screen on/off.
|
||||||
|
void setOn(bool on) { enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); }
|
||||||
|
|
||||||
/// Turn on the screen asap
|
/// Handles a button press.
|
||||||
void doWakeScreen();
|
void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); }
|
||||||
|
|
||||||
/// Is the screen currently on
|
/// Starts showing the Bluetooth PIN screen.
|
||||||
bool isOn();
|
//
|
||||||
|
// Switches over to a static frame showing the Bluetooth pairing screen
|
||||||
|
// with the PIN.
|
||||||
|
void startBluetoothPinScreen(uint32_t pin)
|
||||||
|
{
|
||||||
|
CmdItem cmd;
|
||||||
|
cmd.cmd = Cmd::START_BLUETOOTH_PIN_SCREEN;
|
||||||
|
cmd.bluetooth_pin = pin;
|
||||||
|
enqueueCmd(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
/// Turn the screen on/off
|
/// Stops showing the bluetooth PIN screen.
|
||||||
void setOn(bool on);
|
void stopBluetoothPinScreen() { enqueueCmd(CmdItem{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); }
|
||||||
|
|
||||||
/// Handle a button press
|
/// Stops showing the boot screen.
|
||||||
void onPress();
|
void stopBootScreen() { enqueueCmd(CmdItem{.cmd = Cmd::STOP_BOOT_SCREEN}); }
|
||||||
|
|
||||||
|
/// Writes a string to the screen.
|
||||||
|
void print(const char *text)
|
||||||
|
{
|
||||||
|
CmdItem cmd;
|
||||||
|
cmd.cmd = Cmd::PRINT;
|
||||||
|
// TODO(girts): strdup() here is scary, but we can't use std::string as
|
||||||
|
// FreeRTOS queue is just dumbly copying memory contents. It would be
|
||||||
|
// nice if we had a queue that could copy objects by value.
|
||||||
|
cmd.print_text = strdup(text);
|
||||||
|
if (!enqueueCmd(cmd)) {
|
||||||
|
free(cmd.print_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Updates the UI.
|
||||||
|
//
|
||||||
|
// Called periodically from the main loop.
|
||||||
|
void doTask() final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class Cmd {
|
||||||
|
INVALID,
|
||||||
|
SET_ON,
|
||||||
|
SET_OFF,
|
||||||
|
ON_PRESS,
|
||||||
|
START_BLUETOOTH_PIN_SCREEN,
|
||||||
|
STOP_BLUETOOTH_PIN_SCREEN,
|
||||||
|
STOP_BOOT_SCREEN,
|
||||||
|
PRINT,
|
||||||
|
};
|
||||||
|
struct CmdItem {
|
||||||
|
Cmd cmd;
|
||||||
|
union {
|
||||||
|
uint32_t bluetooth_pin;
|
||||||
|
char *print_text;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Enques given command item to be processed by main loop().
|
||||||
|
bool enqueueCmd(const CmdItem &cmd)
|
||||||
|
{
|
||||||
|
bool success = cmdQueue.enqueue(cmd, 0);
|
||||||
|
setPeriod(1); // handle ASAP
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementations of various commands, called from doTask().
|
||||||
|
void handleSetOn(bool on);
|
||||||
|
void handleOnPress();
|
||||||
|
void handleStartBluetoothPinScreen(uint32_t pin);
|
||||||
|
void handlePrint(const char *text);
|
||||||
|
|
||||||
|
/// Rebuilds our list of frames (screens) to default ones.
|
||||||
|
void setFrames();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Queue of commands to execute in doTask.
|
||||||
|
TypedQueue<CmdItem> cmdQueue;
|
||||||
|
/// Whether we are using a display
|
||||||
|
bool useDisplay = false;
|
||||||
|
/// Whether the display is currently powered
|
||||||
|
bool screenOn = false;
|
||||||
|
// Whether we are showing the regular screen (as opposed to booth screen or
|
||||||
|
// Bluetooth PIN screen)
|
||||||
|
bool showingNormalScreen = false;
|
||||||
|
/// Display device
|
||||||
|
SSD1306Wire dispdev;
|
||||||
|
/// UI helper for rendering to frames and switching between them
|
||||||
|
OLEDDisplayUi ui;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Screen screen;
|
} // namespace meshtastic
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "rom/rtc.h"
|
#include "rom/rtc.h"
|
||||||
#include <driver/rtc_io.h>
|
#include <driver/rtc_io.h>
|
||||||
#include <TinyGPS++.h>
|
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include "BluetoothUtil.h"
|
#include "BluetoothUtil.h"
|
||||||
#include "MeshBluetoothService.h"
|
#include "MeshBluetoothService.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "screen.h"
|
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "Periodic.h"
|
#include "Periodic.h"
|
||||||
#include "esp32/pm.h"
|
#include "esp32/pm.h"
|
||||||
@@ -28,8 +26,6 @@ esp_sleep_source_t wakeCause; // the reason we booted this time
|
|||||||
#define xstr(s) str(s)
|
#define xstr(s) str(s)
|
||||||
#define str(s) #s
|
#define str(s) #s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Application
|
// Application
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -84,9 +80,24 @@ void initDeepSleep()
|
|||||||
wakeButtons = ((uint64_t)1) << buttons.gpios[0];
|
wakeButtons = ((uint64_t)1) << buttons.gpios[0];
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DEBUG_MSG("booted, wake cause %d (boot count %d)\n", wakeCause, bootCount);
|
// If we booted because our timer ran out or the user pressed reset, send those as fake events
|
||||||
}
|
const char *reason = "reset"; // our best guess
|
||||||
|
RESET_REASON hwReason = rtc_get_reset_reason(0);
|
||||||
|
|
||||||
|
if (hwReason == RTCWDT_BROWN_OUT_RESET)
|
||||||
|
reason = "brownout";
|
||||||
|
|
||||||
|
if (hwReason == TG0WDT_SYS_RESET)
|
||||||
|
reason = "taskWatchdog";
|
||||||
|
|
||||||
|
if (hwReason == TG1WDT_SYS_RESET)
|
||||||
|
reason = "intWatchdog";
|
||||||
|
|
||||||
|
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
|
||||||
|
reason = "timeout";
|
||||||
|
|
||||||
|
DEBUG_MSG("booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason);
|
||||||
|
}
|
||||||
|
|
||||||
void doDeepSleep(uint64_t msecToWake)
|
void doDeepSleep(uint64_t msecToWake)
|
||||||
{
|
{
|
||||||
@@ -164,6 +175,10 @@ void doDeepSleep(uint64_t msecToWake)
|
|||||||
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
|
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
|
||||||
uint64_t gpioMask = (1ULL << BUTTON_PIN);
|
uint64_t gpioMask = (1ULL << BUTTON_PIN);
|
||||||
|
|
||||||
|
#ifdef BUTTON_NEED_PULLUP
|
||||||
|
gpio_pullup_en((gpio_num_t) BUTTON_PIN);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Not needed because both of the current boards have external pullups
|
// Not needed because both of the current boards have external pullups
|
||||||
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of just the first)
|
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of just the first)
|
||||||
// gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
// gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
||||||
@@ -175,9 +190,6 @@ void doDeepSleep(uint64_t msecToWake)
|
|||||||
esp_deep_sleep_start(); // TBD mA sleep current (battery)
|
esp_deep_sleep_start(); // TBD mA sleep current (battery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* enter light sleep (preserves ram but stops everything about CPU).
|
* enter light sleep (preserves ram but stops everything about CPU).
|
||||||
*
|
*
|
||||||
@@ -196,15 +208,20 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
|
|||||||
// We want RTC peripherals to stay on
|
// We want RTC peripherals to stay on
|
||||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||||
|
|
||||||
|
#ifdef BUTTON_NEED_PULLUP
|
||||||
|
gpio_pullup_en((gpio_num_t) BUTTON_PIN);
|
||||||
|
#endif
|
||||||
|
|
||||||
gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low
|
gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low
|
||||||
gpio_wakeup_enable((gpio_num_t)DIO0_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high
|
gpio_wakeup_enable((gpio_num_t)DIO0_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high
|
||||||
#ifdef PMU_IRQ
|
#ifdef PMU_IRQ
|
||||||
gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_HIGH_LEVEL); // pmu irq
|
// FIXME, disable wake due to PMU because it seems to fire all the time?
|
||||||
|
// gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_HIGH_LEVEL); // pmu irq
|
||||||
#endif
|
#endif
|
||||||
assert(esp_sleep_enable_gpio_wakeup() == ESP_OK);
|
assert(esp_sleep_enable_gpio_wakeup() == ESP_OK);
|
||||||
assert(esp_sleep_enable_timer_wakeup(sleepUsec) == ESP_OK);
|
assert(esp_sleep_enable_timer_wakeup(sleepUsec) == ESP_OK);
|
||||||
assert(esp_light_sleep_start() == ESP_OK);
|
assert(esp_light_sleep_start() == ESP_OK);
|
||||||
//DEBUG_MSG("Exit light sleep\n");
|
//DEBUG_MSG("Exit light sleep b=%d, rf95=%d, pmu=%d\n", digitalRead(BUTTON_PIN), digitalRead(DIO0_GPIO), digitalRead(PMU_IRQ));
|
||||||
return esp_sleep_get_wakeup_cause();
|
return esp_sleep_get_wakeup_cause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user