Compare commits

..

71 Commits
0.1.6 ... 0.1.9

Author SHA1 Message Date
Kevin Hester
9075501917 Merge pull request #56 from geeksville/master
misc changes as kevin bangs through the post illness bug-queue
2020-03-24 15:30:04 -07:00
geeksville
a3b70e7538 0.1.9 2020-03-24 15:23:50 -07:00
geeksville
b4b8abe6ec omg I was not setting the correct flag to tell phone we had gps 2020-03-24 15:20:24 -07:00
geeksville
d647be73df oops nasty bug probably responsible for ble mutex seeming bugs #33
Was calling the wrong superclass method and therfore not properly
populating radio
2020-03-24 15:16:32 -07:00
geeksville
42d7966858 add instructions on how to reinstall 2020-03-24 15:13:28 -07:00
geeksville
f4d368e1f4 fix #27 - add a device-install.sh script to the release 2020-03-24 14:48:52 -07:00
geeksville
3a756b0e08 keep the elf files in the zip package, useful with @girtsf tool 2020-03-24 13:58:17 -07:00
geeksville
34ead2d68e add support for reporting device errors up through the phone to analytics
related to https://github.com/meshtastic/Meshtastic-esp32/issues/53
2020-03-24 13:33:24 -07:00
geeksville
3f1161b68b bug #53 - report the error on console and fixup (will add analytics in
a separate call)
2020-03-24 13:04:28 -07:00
Kevin Hester
f108e24bc1 Merge pull request #51 from girtsf/fix-upside-down-screen
screen.cpp: flip the display 180
2020-03-24 11:09:06 -07:00
Kevin Hester
1f83f7d9df Merge branch 'master' into fix-upside-down-screen 2020-03-24 10:55:56 -07:00
Kevin Hester
756c7ca7b3 Merge pull request #55 from girtsf/fix-ble
fix #52: bluetooth not pairing
2020-03-24 10:55:00 -07:00
Kevin Hester
79f7bf77c9 Merge branch 'master' into fix-ble 2020-03-24 10:52:10 -07:00
Girts Folkmanis
dee3e530de fix #52: bluetooth not pairing
Silly type error on my part - PIN was always truncated to lower 8 bits.
😬

Tested: Pairing now works from both nRF Connect and phone.
2020-03-22 19:18:49 -07:00
Girts Folkmanis
3e44c2c3e1 screen.cpp: flip the display 180 2020-03-19 20:15:51 -07:00
Kevin Hester
3886665041 Merge pull request #47 from geeksville/master
update todo list
2020-03-18 20:37:46 -07:00
geeksville
c9b269c3c0 Merge remote-tracking branch 'root/master' 2020-03-18 19:36:37 -07:00
geeksville
eb51c92d08 update todo list 2020-03-18 19:28:55 -07:00
Kevin Hester
992b525588 Merge pull request #46 from geeksville/master
a quick window to sneak in and fix formatting
2020-03-18 19:19:18 -07:00
geeksville
25288d8ed6 Merge remote-tracking branch 'root/master' 2020-03-18 19:16:56 -07:00
geeksville
32ac5ac9ae reformat everything
using @girtsf clang-format prefs settings.  This should allow us to turn
on auto format in our editors without causing spurious file changes.
2020-03-18 19:15:51 -07:00
Kevin Hester
ef5cdefca6 Merge pull request #44 from geeksville/master
various minor commits based on bugs I see while testing app
2020-03-18 18:55:57 -07:00
geeksville
f6f9dfa463 0.1.8 2020-03-18 18:53:55 -07:00
geeksville
2161ce21df the firmware version xml file should not be checked in, it is used directly
by the android build and derived from version.sh
2020-03-18 18:53:42 -07:00
geeksville
534691f0c2 Merge remote-tracking branch 'root/master'
# Conflicts:
#	src/main.cpp
#	src/screen.cpp
#	src/screen.h
2020-03-18 18:44:12 -07:00
Kevin Hester
6bc8e1b10a Merge pull request #45 from girtsf/screen-cpp-refactor
Screen cleanups and refactoring
2020-03-18 18:35:51 -07:00
geeksville
c8b95f7691 oops - I broke compass display with my gps changes and didn't notice till
testing with two gps equipped devices.  fixed.
2020-03-18 18:34:22 -07:00
Girts Folkmanis
daf8594b99 Screen cleanups and refactoring
Work towards separating out how Screen interacts with other stuff.
* `Screen` should now be thread-safe. All commands to it are put in a
  queue and handled in `doTask` from the `loop()` task.
* Break dependency from `BluetoothUtil` to `Screen` by changing the
  pairing request into a callback.
* All accesses to screen now happen through the class.
* Fix `drawRows` so that the text scrolls along with frame animations.
* Remove example code that wasn't used.
2020-03-18 18:11:35 -07:00
Girts Folkmanis
5b54fd6359 screen.cpp: reformat with clang-format 2020-03-18 17:16:19 -07:00
geeksville
53765298e1 add a real BOOT state, to avoid glitch from redrawing bootscreen twice
also its the right thing to do ;-)
2020-03-18 15:00:17 -07:00
geeksville
0d94458c4e bump preferences # 2020-03-18 14:59:30 -07:00
geeksville
5e55695862 fix build warning 2020-03-18 14:51:54 -07:00
Kevin Hester
c9e2e6c386 Merge pull request #43 from geeksville/one-wire-gps
fixes to make one-wire gpses work and cope with tbeams with crummy rx buffers
2020-03-18 14:02:46 -07:00
geeksville
dbbb62f63e fix press to properly force any node we are watching to send us a new
position report
2020-03-18 13:51:32 -07:00
geeksville
79ce7d929c send dynamic probed GPS status to the phone 2020-03-18 13:29:22 -07:00
geeksville
33437b5246 oops - I accidentally shadowed a variable I didn't want to shadow ;-) 2020-03-18 09:37:38 -07:00
geeksville
f4bacb9d87 some tbeams have occasional crap sitting in their gps rx buffer at boot? 2020-03-18 09:29:20 -07:00
geeksville
0ac218b06d allow gpses which only have the RX pin connected to also work.
(and because I'm lazy, let the autoreformat rule work on this file)
2020-03-18 09:21:28 -07:00
Kevin Hester
2ce1b4bb2c Merge pull request #42 from geeksville/master
release 0.1.7
2020-03-17 20:02:01 -07:00
geeksville
f002bee4f4 release 0.1.7 2020-03-17 19:50:26 -07:00
Kevin Hester
9c69326ed9 Merge pull request #39 from geeksville/bug17
Bug17
2020-03-17 19:42:44 -07:00
Kevin Hester
7a617d5378 Merge branch 'master' into bug17 2020-03-17 19:40:25 -07:00
Kevin Hester
90edae1ce0 Merge pull request #41 from geeksville/new-oled
fix #40 force an extra redraw for the bootscreen,
2020-03-17 19:39:32 -07:00
geeksville
2134b4db9b fix #40 force an extra redraw for the bootscreen,
some clones drop the first draw cmd
2020-03-17 16:36:48 -07:00
geeksville
7b1ffb5c09 #17 WIP add tool to capture the system portion of flash
based on initial encouraging feedback from @illperipherals

if more people report success, we'll need to change the install instructions
to:

esptool.py --port COM10 --baud 921600 erase_flash
esptool.py --port COM10 --baud 921600 write_flash 0x1000 system-info.bin
esptool.py --port COM10 --baud 921600 write_flash 0x10000 firmware-HELTEC-US-0.1.6.bin
2020-03-17 11:17:58 -07:00
geeksville
535f07d927 add a tbeam program script 2020-03-17 11:16:22 -07:00
Kevin Hester
bfad136137 Merge pull request #31 from geeksville/master
@girtsf can you review these changes and blessmotize if they seem good?
2020-03-16 09:37:31 -07:00
geeksville
36bee8fa53 Merge remote-tracking branch 'root/master' 2020-03-16 09:30:24 -07:00
Kevin Hester
99cb0b3855 Merge pull request #37 from girtsf/fix-build-2
fix build: add missing include to screen.cpp
2020-03-16 09:30:14 -07:00
Girts Folkmanis
41c95eaff7 fix build: add missing include to screen.cpp 2020-03-16 09:26:40 -07:00
geeksville
ca8a25f585 Merge remote-tracking branch 'root/master' 2020-03-16 09:03:21 -07:00
Kevin Hester
51a8700391 Merge pull request #35 from girtsf/underp-lock-paths
underp include paths in lock.h
2020-03-16 09:02:54 -07:00
Kevin Hester
f099a31a29 Merge pull request #34 from girtsf/patch-1
run CI on pull requests as well
2020-03-16 09:01:28 -07:00
Kevin Hester
52dbc4e15d Merge pull request #36 from girtsf/various-cleanups
Various cleanups
2020-03-16 09:00:42 -07:00
Girts Folkmanis
8cabb3ea3d add .clang-format file
Tried to infer the style from existing files.
2020-03-15 19:29:55 -07:00
Girts Folkmanis
7a4a1af332 TypedQueue: make functions return bools instead of BaseType_t
Minor cleanup to hide away some FreeRTOS bits.

Note: I believe src/CustomRF95.cpp:62 had a bug where it had the
condition inverted.
2020-03-15 19:29:04 -07:00
Girts Folkmanis
90ecdf229e add locks to PeriodicTask 2020-03-15 19:29:00 -07:00
geeksville
24ac907780 auto generate channel numbers from name
NOTE: All radios on a channel will need to be updated to this release
before they can talk together again.
2020-03-15 17:51:57 -07:00
geeksville
5037fb830e fix build (and autoformat in visual studio code) 2020-03-15 17:50:48 -07:00
Girts Folkmanis
79f1346359 underp include paths in lock.h
Had the casing wrong, but could get away with it on a mac.
2020-03-15 17:43:42 -07:00
Girts
35dada683a run CI on pull requests as well 2020-03-15 17:42:48 -07:00
geeksville
30a431788d we now do bidirectional comms to GPS at startup, so we can always trust isConnected 2020-03-15 16:57:21 -07:00
geeksville
d0b8adab75 In my work for #11 I accidentially created a serious bug on Heltec...
devices.  It caused bogus i2c transactions when device would go to sleep.
Fixed now, also, I now treat GPS usage uniformly between TBEAM and HELTEC
we always probe for and use the GPS if we find it.

Which means for the extra nerds
(someone requested this, I'm sorry - I don't remember who) you can now
optionally attach an external GPS to HELTECs if you want.  The pins are:

 #define GPS_RX_PIN 34
 #define GPS_TX_PIN 12

(@girtsf, sorry about including formatting changes in this PR, apparently
I had my IDE set to not autoreformat until just now
2020-03-15 16:57:21 -07:00
geeksville
74f7b7b622 print extra info the next time this error occurs 2020-03-15 16:57:21 -07:00
geeksville
9ec8562ce7 fix old geeksville link (though github provides redirects) 2020-03-15 16:57:21 -07:00
Kevin Hester
0cbcb7a9bd Merge pull request #32 from girtsf/add-lock-etc
add a Lock, LockGuard and printThreadInfo
2020-03-15 16:55:48 -07:00
Girts Folkmanis
2874b22d6c add a Lock, LockGuard and printThreadInfo
* `Lock`: trivial wrapper for FreeRTOS binary semaphores
* `LockGuard`: RAII wrapper for using `Lock`
* `printThreadInfo`: helper for showing which core/FreeRTOS task we are
  running under
2020-03-15 16:52:19 -07:00
Kevin Hester
3c9be48445 Merge pull request #30 from girtsf/fix-build
fix the build: remove includes for TinyGPS that's not longer used or …
2020-03-15 14:30:04 -07:00
Girts Folkmanis
b5201f928b fix the build: remove includes for TinyGPS that's not longer used or in deps 2020-03-15 13:27:00 -07:00
Kevin Hester
2591859df5 Merge pull request #29 from girtsf/exception-decoder
check in script to decode backtraces
2020-03-15 12:38:44 -07:00
Girts Folkmanis
c411db111b check in script to decode backtraces 2020-03-15 12:29:15 -07:00
58 changed files with 2435 additions and 1919 deletions

6
.clang-format Normal file
View File

@@ -0,0 +1,6 @@
Language: Cpp
IndentWidth: 4
ColumnLimit: 130
PointerAlignment: Right
BreakBeforeBraces: Linux
AllowShortFunctionsOnASingleLine: Inline

View File

@@ -1,5 +1,7 @@
name: Continuous Integration name: Continuous Integration
on: push on:
- push
- pull_request
jobs: jobs:
main: main:

View File

@@ -51,31 +51,77 @@ Warning: ESP32 has no Chip ID. Reading MAC instead.
MAC: 24:6f:28:b5:36:71 MAC: 24:6f:28:b5:36:71
Hard resetting via RTS pin... Hard resetting via RTS pin...
``` ```
6. Install the correct firmware for your board with "esptool.py write_flash 0x10000 firmware-_board_-_country_.bin". For instance "esptool.py write_flash 0x10000 release/firmware-HELTEC-US-0.0.3.bin". You should see something like this: 6. cd into the directory where the release zip file was expanded.
7. Install the correct firmware for your board with "device-install.sh firmware-_board_-_country_.bin". For instance "./device-install.sh firmware-HELTEC-US-0.0.3.bin".
Note: If you have previously installed meshtastic, you don't need to run this full script instead just run "esptool.py --baud 921600 write_flash 0x10000 firmware-_board_-_country_.bin". This will be faster, also all of your current preferences will be preserved.
You should see something like this:
``` ```
~/development/meshtastic/meshtastic-esp32$ esptool.py write_flash 0x10000 release/firmware-HELTEC-US-0.0.3.bin kevinh@kevin-server:~/development/meshtastic/meshtastic-esp32/release/latest$ ./device-install.sh firmware-TBEAM-US-0.1.8.bin
Trying to flash firmware-TBEAM-US-0.1.8.bin, but first erasing and writing system information
esptool.py v2.6 esptool.py v2.6
Found 2 serial ports Found 2 serial ports
Serial port /dev/ttyUSB0 Serial port /dev/ttyUSB0
Connecting...... Connecting........____
Detecting chip type... ESP32 Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1) Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 24:6f:28:b5:36:71 MAC: 24:6f:28:b2:01:6c
Uploading stub... Uploading stub...
Running stub... Running stub...
Stub running... Stub running...
Changing baud rate to 921600
Changed.
Erasing flash (this may take a while)...
Chip erase completed successfully in 6.1s
Hard resetting via RTS pin...
esptool.py v2.6
Found 2 serial ports
Serial port /dev/ttyUSB0
Connecting.......
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 24:6f:28:b2:01:6c
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size... Configuring flash size...
Auto-detected Flash size: 8MB Auto-detected Flash size: 4MB
Compressed 1184800 bytes to 652635... Flash params set to 0x0220
Wrote 1184800 bytes (652635 compressed) at 0x00010000 in 57.6 seconds (effective 164.5 kbit/s)... Compressed 61440 bytes to 11950...
Wrote 61440 bytes (11950 compressed) at 0x00001000 in 0.2 seconds (effective 3092.4 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
esptool.py v2.6
Found 2 serial ports
Serial port /dev/ttyUSB0
Connecting.....
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 24:6f:28:b2:01:6c
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 1223568 bytes to 678412...
Wrote 1223568 bytes (678412 compressed) at 0x00010000 in 10.7 seconds (effective 912.0 kbit/s)...
Hash of data verified. Hash of data verified.
Leaving... Leaving...
Hard resetting via RTS pin... Hard resetting via RTS pin...
``` ```
7. The board will boot and show the Meshtastic logo. 8. The board will boot and show the Meshtastic logo.
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. 9. 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/meshtastic/Meshtastic-Android). The source code for the (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android).

View File

@@ -9,6 +9,7 @@ COUNTRIES="US EU433 EU865 CN JP"
SRCMAP=.pio/build/esp32/output.map SRCMAP=.pio/build/esp32/output.map
SRCBIN=.pio/build/esp32/firmware.bin SRCBIN=.pio/build/esp32/firmware.bin
SRCELF=.pio/build/esp32/firmware.elf
OUTDIR=release/latest OUTDIR=release/latest
# We keep all old builds (and their map files in the archive dir) # We keep all old builds (and their map files in the archive dir)
@@ -26,12 +27,14 @@ for COUNTRY in $COUNTRIES; do
rm -f $SRCBIN $SRCMAP rm -f $SRCBIN $SRCMAP
pio run # -v pio run # -v
cp $SRCBIN $OUTDIR/firmware-TBEAM-$COUNTRY-$VERSION.bin cp $SRCBIN $OUTDIR/firmware-TBEAM-$COUNTRY-$VERSION.bin
cp $SRCELF $OUTDIR/firmware-TBEAM-$COUNTRY-$VERSION.elf
#cp $SRCMAP $ARCHIVEDIR/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 $OUTDIR/firmware-HELTEC-$COUNTRY-$VERSION.bin cp $SRCBIN $OUTDIR/firmware-HELTEC-$COUNTRY-$VERSION.bin
cp $SRCELF $OUTDIR/firmware-HELTEC-$COUNTRY-$VERSION.elf
#cp $SRCMAP $ARCHIVEDIR/firmware-HELTEC-$COUNTRY-$VERSION.map #cp $SRCMAP $ARCHIVEDIR/firmware-HELTEC-$COUNTRY-$VERSION.map
done done
@@ -51,6 +54,6 @@ Generated by bin/buildall.sh -->
XML XML
rm -f $ARCHIVEDIR/firmware-$VERSION.zip rm -f $ARCHIVEDIR/firmware-$VERSION.zip
zip $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/firmware-*-$VERSION.bin zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh
echo BUILT ALL echo BUILT ALL

11
bin/device-install.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -e
FILENAME=$1
echo "Trying to flash $FILENAME, but first erasing and writing system information"
esptool.py --baud 921600 erase_flash
esptool.py --baud 921600 write_flash 0x1000 system-info.bin
esptool.py --baud 921600 write_flash 0x10000 $FILENAME

329
bin/exception_decoder.py Executable file
View 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-tbeam.sh Executable file
View 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
View File

@@ -0,0 +1 @@
esptool.py --baud 921600 read_flash 0x1000 0xf000 system-info.img

View File

@@ -1,3 +1,3 @@
export VERSION=0.1.6 export VERSION=0.1.9

View File

@@ -2,202 +2,202 @@
Items to complete soon (next couple of alpha releases). Items to complete soon (next couple of alpha releases).
* 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.
* use gps sleep mode instead of killing its power (to allow fast position when we wake) - remeasure wake time power draws now that we run CPU down at 80MHz
* enable fast lock and low power inside the gps chip
* 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 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 - figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?"
- "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.
* Make a FAQ - good source of battery/signal/gps icons https://materialdesignicons.com/
* good source of battery/signal/gps icons https://materialdesignicons.com/ - research and implement better mesh algorithm - investigate changing routing to https://github.com/sudomesh/LoRaLayer2 ?
* 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/
* 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
* use fuse bits to store the board type and region. So one load can be used on all boards - the BLE stack is leaking about 200 bytes each time we go to light sleep
* research and implement better mesh algorithm - 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.
* the BLE stack is leaking about 200 bytes each time we go to light sleep - 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"
* 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. - Note: we do not do address filtering at the chip level, because we might need to route for the mesh
* 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" - 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. Possibly do this crypto at the data payload level only, so that all of the packet routing metadata
* Note: we do not do address filtering at the chip level, because we might need to route for the mesh is in cleartext (so that nodes will route for other radios that are cryptoed with a key we don't know)
* 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 - add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting
* add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting - share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.
* share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app. - 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
* 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) 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
* 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
* 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
* triple check fcc compliance - triple check fcc compliance
* 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. - 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? (Use CAD mode of the RF95 to automatically find low noise channels) - scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels)
* make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired - 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 - 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 - reneable the bluetooth battery level service on the T-BEAM, because we can read battery level there
# Spinoff project ideas # Spinoff project ideas
* an open source version of https://www.burnair.ch/skynet/ - an open source version of https://www.burnair.ch/skynet/
* a paragliding app like http://airwhere.co.uk/ - 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 - 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? - 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 - 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 variable length arduino Strings in protobufs (instead of current fixed buffers) - 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 - 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 - make an install script to let novices install software on their boards
* use std::map<NodeInfo*, std::string> in node db - 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 - 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 - 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 - use https://platformio.org/lib/show/1260/OneButton if necessary
* don't send location packets if we haven't moved - don't send location packets if we haven't moved
* scrub default radio config settings for bandwidth/range/speed - 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) - 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 - 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. - 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) - 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. - 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
* 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.
* 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
* 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
* 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
* report esp32 device code bugs back to the mothership via android - report esp32 device code bugs back to the mothership via android
# Done # Done
* change the partition table to take advantage of the 4MB flash on the wroom: http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables - change the partition table to take advantage of the 4MB flash on the wroom: http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
* wrap in nice MeshRadio class - wrap in nice MeshRadio class
* add mesh send & rx - add mesh send & rx
* make message send from android go to service, then to mesh radio - make message send from android go to service, then to mesh radio
* make message receive from radio go through to android - make message receive from radio go through to android
* test loopback tx/rx path code without using radio - test loopback tx/rx path code without using radio
* notify phone when rx packets arrive, currently the phone polls at startup only - notify phone when rx packets arrive, currently the phone polls at startup only
* figure out if we can use PA_BOOST - yes, it seems to be on both boards - figure out if we can use PA_BOOST - yes, it seems to be on both boards
* implement new ble characteristics - implement new ble characteristics
* have MeshService keep a node DB by sniffing user messages - have MeshService keep a node DB by sniffing user messages
* have a state machine return the correct FromRadio packet to the phone, it isn't always going to be a MeshPacket. Do a notify on fromnum to force the radio to read our state machine generated packets - have a state machine return the correct FromRadio packet to the phone, it isn't always going to be a MeshPacket. Do a notify on fromnum to force the radio to read our state machine generated packets
* send my_node_num when phone sends WantsNodes - send my_node_num when phone sends WantsNodes
* have meshservice periodically send location data on mesh (if device has a GPS) - have meshservice periodically send location data on mesh (if device has a GPS)
* implement getCurrentTime() - set based off gps but then updated locally - implement getCurrentTime() - set based off gps but then updated locally
* make default owner record have valid usernames - make default owner record have valid usernames
* message loop between node 0x28 and 0x7c - message loop between node 0x28 and 0x7c
* check in my radiolib fixes - check in my radiolib fixes
* figure out what is busted with rx - figure out what is busted with rx
* send our owner info at boot, reply if we see anyone send theirs - send our owner info at boot, reply if we see anyone send theirs
* add manager layers - add manager layers
* confirm second device receives that gps message and updates device db - confirm second device receives that gps message and updates device db
* send correct hw vendor in the bluetooth info - needed so the android app can update different radio models - send correct hw vendor in the bluetooth info - needed so the android app can update different radio models
* correctly map nodeids to nodenums, currently we just do a proof of concept by always doing a broadcast - correctly map nodeids to nodenums, currently we just do a proof of concept by always doing a broadcast
* add interrupt detach/sleep mode config to lora radio so we can enable deepsleep without panicing - add interrupt detach/sleep mode config to lora radio so we can enable deepsleep without panicing
* make jtag work on second board - make jtag work on second board
* implement regen owner and radio prefs - implement regen owner and radio prefs
* use a better font - use a better font
* make nice screens (boot, about to sleep, debug info (gps signal, #people), latest text, person info - one frame per person on network) - make nice screens (boot, about to sleep, debug info (gps signal, #people), latest text, person info - one frame per person on network)
* turn framerate from ui->state.frameState to 1 fps (or less) unless in transition - turn framerate from ui->state.frameState to 1 fps (or less) unless in transition
* switch to my gui layout manager - switch to my gui layout manager
* make basic gui. different screens: debug, one page for each user in the user db, last received text message - make basic gui. different screens: debug, one page for each user in the user db, last received text message
* make button press cycle between screens - make button press cycle between screens
* save our node db on entry to sleep - save our node db on entry to sleep
* fix the logo - fix the logo
* sent/received packets (especially if a node was just reset) have variant of zero sometimes - I think there is a bug (race-condtion?) in the radio send/rx path. - sent/received packets (especially if a node was just reset) have variant of zero sometimes - I think there is a bug (race-condtion?) in the radio send/rx path.
* DONE dynamic nodenum assignment tasks - DONE dynamic nodenum assignment tasks
* make jtag debugger id stable: https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters - make jtag debugger id stable: https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters
* reported altitude is crap - reported altitude is crap
* good tips on which bands might be more free https://github.com/TheThingsNetwork/ttn/issues/119 - good tips on which bands might be more free https://github.com/TheThingsNetwork/ttn/issues/119
* finish power measurements (GPS on during sleep vs LCD on during sleep vs LORA on during sleep) and est battery life - finish power measurements (GPS on during sleep vs LCD on during sleep vs LORA on during sleep) and est battery life
* make screen sleep behavior work - make screen sleep behavior work
* make screen advance only when a new node update arrives, a new text arrives or the user presses a button, turn off screen after a while - make screen advance only when a new node update arrives, a new text arrives or the user presses a button, turn off screen after a while
* after reboot, channel number is getting reset to zero! fix! - after reboot, channel number is getting reset to zero! fix!
* send user and location events much less often - send user and location events much less often
* send location (or if not available user) when the user wakes the device from display sleep (both for testing and to improve user experience) - send location (or if not available user) when the user wakes the device from display sleep (both for testing and to improve user experience)
* make real implementation of getNumOnlineNodes - make real implementation of getNumOnlineNodes
* very occasionally send our position and user packet based on the schedule in the radio info (if for nothing else so that other nodes update last_seen) - very occasionally send our position and user packet based on the schedule in the radio info (if for nothing else so that other nodes update last_seen)
* show real text info on the text screen - show real text info on the text screen
* apply radio settings from android land - apply radio settings from android land
* cope with nodes that have 0xff or 0x00 as the last byte of their mac - cope with nodes that have 0xff or 0x00 as the last byte of their mac
* allow setting full radio params from android - allow setting full radio params from android
* add receive timestamps to messages, inserted by esp32 when message is received but then shown on the phone - add receive timestamps to messages, inserted by esp32 when message is received but then shown on the phone
* update build to generate both board types - update build to generate both board types
* have node info screen show real info (including distance and heading) - have node info screen show real info (including distance and heading)
* blink the power led less often - blink the power led less often
* have radiohead ISR send messages to RX queue directly, to allow that thread to block until we have something to send - have radiohead ISR send messages to RX queue directly, to allow that thread to block until we have something to send
* move lora rx/tx to own thread and block on IO - move lora rx/tx to own thread and block on IO
* keep our pseudo time moving forward even if we enter deep sleep (use esp32 rtc) - keep our pseudo time moving forward even if we enter deep sleep (use esp32 rtc)
* for non GPS equipped devices, set time from phone - for non GPS equipped devices, set time from phone
* GUI on oled hangs for a few seconds occasionally, but comes back - GUI on oled hangs for a few seconds occasionally, but comes back
* update local GPS position (but do not broadcast) at whatever rate the GPS is giving it - update local GPS position (but do not broadcast) at whatever rate the GPS is giving it
* don't send our times to other nodes - don't send our times to other nodes
* don't trust times from other nodes - don't trust times from other nodes
* draw compass rose based off local walking track - draw compass rose based off local walking track
* add requestResponse optional bool - use for location broadcasts when sending tests - add requestResponse optional bool - use for location broadcasts when sending tests
* post sample video to signal forum - post sample video to signal forum
* support non US frequencies - support non US frequencies
* send pr https://github.com/ThingPulse/esp8266-oled-ssd1306 to tell them about this project - send pr https://github.com/ThingPulse/esp8266-oled-ssd1306 to tell them about this project
* document rules for sleep wrt lora/bluetooth/screen/gps. also: if I have text messages (only) for the phone, then give a few seconds in the hopes BLE can get it across before we have to go back to sleep. - document rules for sleep wrt lora/bluetooth/screen/gps. also: if I have text messages (only) for the phone, then give a few seconds in the hopes BLE can get it across before we have to go back to sleep.
* wake from light sleep as needed for our next scheduled periodic task (needed for gps position broadcasts etc) - wake from light sleep as needed for our next scheduled periodic task (needed for gps position broadcasts etc)
* turn bluetooth off based on our sleep policy - turn bluetooth off based on our sleep policy
* blink LED while in LS sleep mode - blink LED while in LS sleep mode
* scrolling between screens based on press is busted - scrolling between screens based on press is busted
* Use Neo-M8M API to put it in sleep mode (on hold until my new boards arrive) - Use Neo-M8M API to put it in sleep mode (on hold until my new boards arrive)
* update the prebuilt bins for different regulatory regions - update the prebuilt bins for different regulatory regions
* 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 - leave lora receiver always on
* protobufs are sometimes corrupted after sleep! - protobufs are sometimes corrupted after sleep!
* stay awake while charging - stay awake while charging
* check gps battery voltage - 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. - 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 - retest BLE software update for both board types
* report on wikifactory - report on wikifactory
* send note to the guy who designed the cases - send note to the guy who designed the cases
* turn light sleep on aggressively (while lora is on but BLE off) - turn light sleep on aggressively (while lora is on but BLE off)
* Use the Periodic class for both position and user periodic broadcasts - 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) - 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 - 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 - 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. 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/ - 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 - fix GPS.zeroOffset calculation it is wrong
* (needs testing) fixed the following during a plane flight: - (needs testing) fixed the following during a plane flight:
Have state machine properly enter deep sleep based on loss of mesh and phone comms. 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). 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 - (fixed I think) text messages are not showing on local screen if screen was on
* add links to todos - add links to todos
* link to the kanban page - link to the kanban page
* add a getting started page - add a getting started page
* finish mesh alg reeval - 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) - 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 - 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 - 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 - Use Neo-M8M API to put it in sleep mode
- 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
- Make a FAQ
- add a SF12 transmit option for _super_ long range

View File

@@ -14,3 +14,12 @@ in these instructions I describe use of their command line tool.
5. Plug the radio into your USB port 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) 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 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).

BIN
images/system-info.bin Normal file

Binary file not shown.

View File

@@ -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.setFrames(); 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

View File

@@ -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

View File

@@ -65,7 +65,7 @@ lib_deps =
; 1260 ; OneButton - not used yet ; 1260 ; OneButton - not used yet
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib 1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
Wire ; explicitly needed here because the AXP202 library forgets to add it 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 https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
;[env:tbeam] ;[env:tbeam]

2
proto

Submodule proto updated: 66e926740a...1b2449b50d

1
release/.gitignore vendored
View File

@@ -1,3 +1,4 @@
*.elf
*.bin *.bin
*.map *.map
*.zip *.zip

1
release/latest/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
curfirmwareversion.xml

View File

@@ -1,9 +0,0 @@
<?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">0.1.6</string>
</resources>

View File

@@ -1,20 +1,16 @@
#include "CustomRF95.h" #include "CustomRF95.h"
#include <pb_encode.h>
#include <pb_decode.h>
#include "configuration.h"
#include "assert.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "assert.h"
#include "configuration.h"
#include <pb_decode.h>
#include <pb_encode.h>
/// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need /// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need
#define MAX_RHPACKETLEN 251 #define MAX_RHPACKETLEN 251
static uint8_t radiobuf[MAX_RHPACKETLEN]; static uint8_t radiobuf[MAX_RHPACKETLEN];
CustomRF95::CustomRF95(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest) CustomRF95::CustomRF95(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
: RH_RF95(NSS_GPIO, DIO0_GPIO), : RH_RF95(NSS_GPIO, DIO0_GPIO), pool(_pool), rxDest(_rxDest), txQueue(MAX_TX_QUEUE), sendingPacket(NULL)
pool(_pool),
rxDest(_rxDest),
txQueue(MAX_TX_QUEUE),
sendingPacket(NULL)
{ {
} }
@@ -49,15 +45,12 @@ ErrorCode CustomRF95::send(MeshPacket *p)
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
// we almost certainly guarantee no one outside will like the packet we are sending. // we almost certainly guarantee no one outside will like the packet we are sending.
if (_mode == RHModeIdle || (_mode == RHModeRx && !_isReceiving)) if (_mode == RHModeIdle || (_mode == RHModeRx && !_isReceiving)) {
{
// if the radio is idle, we can send right away // if the radio is idle, we can send right away
DEBUG_MSG("immedate send on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", txGood(), rxGood(), rxBad()); DEBUG_MSG("immedate send on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", txGood(), rxGood(), rxBad());
startSend(p); startSend(p);
return ERRNO_OK; return ERRNO_OK;
} } else {
else
{
DEBUG_MSG("enquing packet for send from=0x%x, to=0x%x\n", p->from, p->to); DEBUG_MSG("enquing packet for send from=0x%x, to=0x%x\n", p->from, p->to);
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
@@ -68,7 +61,8 @@ ErrorCode CustomRF95::send(MeshPacket *p)
} }
} }
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as necessary // After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as
// necessary
void CustomRF95::handleInterrupt() void CustomRF95::handleInterrupt()
{ {
RH_RF95::handleInterrupt(); RH_RF95::handleInterrupt();
@@ -85,8 +79,7 @@ void CustomRF95::handleInterrupt()
} }
// If we just finished receiving a packet, forward it into a queue // If we just finished receiving a packet, forward it into a queue
if (_rxBufValid) if (_rxBufValid) {
{
// We received a packet // We received a packet
// Skip the 4 headers that are at the beginning of the rxBuf // Skip the 4 headers that are at the beginning of the rxBuf
@@ -111,23 +104,18 @@ void CustomRF95::handleInterrupt()
// Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will // Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will
// already have a record we can hide this debugging info in. // already have a record we can hide this debugging info in.
NodeInfo *info = nodeDB.getNode(mp->from); NodeInfo *info = nodeDB.getNode(mp->from);
if (info) if (info) {
{
info->snr = snr; info->snr = snr;
info->frequency_error = freqerr; info->frequency_error = freqerr;
} }
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) {
{
pool.releaseFromISR(mp, &higherPriWoken); pool.releaseFromISR(mp, &higherPriWoken);
} } else {
else
{
// 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
@@ -153,8 +141,7 @@ bool CustomRF95::handleIdleISR()
MeshPacket *txp = txQueue.dequeuePtrFromISR(0); MeshPacket *txp = txQueue.dequeuePtrFromISR(0);
if (txp) if (txp)
startSend(txp); startSend(txp);
else else {
{
// Nothing to send, let's switch back to receive mode // Nothing to send, let's switch back to receive mode
setModeRx(); setModeRx();
} }

View File

@@ -1,15 +1,14 @@
#pragma once #pragma once
#include <RH_RF95.h>
#include <RHMesh.h>
#include "MemoryPool.h" #include "MemoryPool.h"
#include "mesh.pb.h"
#include "PointerQueue.h"
#include "MeshTypes.h" #include "MeshTypes.h"
#include "PointerQueue.h"
#include "mesh.pb.h"
#include <RHMesh.h>
#include <RH_RF95.h>
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
/** /**
* A version of the RF95 driver which is smart enough to manage packets via queues (no polling or blocking in user threads!) * A version of the RF95 driver which is smart enough to manage packets via queues (no polling or blocking in user threads!)
*/ */
@@ -46,7 +45,8 @@ public:
bool init(); bool init();
protected: protected:
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as necessary // After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as
// necessary
virtual void handleInterrupt(); virtual void handleInterrupt();
private: private:

View File

@@ -1,25 +1,25 @@
#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"
HardwareSerial _serial_gps(GPS_SERIAL_NUM); HardwareSerial _serial_gps(GPS_SERIAL_NUM);
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;
// stuff that really should be in in the instance instead... // 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 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 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 hasValidLocation; // default to false, until we complete our first read
static bool wantNewLocation = true; static bool wantNewLocation = true;
GPS::GPS() : PeriodicTask() GPS::GPS() : PeriodicTask() {}
{
}
void GPS::setup() void GPS::setup()
{ {
@@ -35,28 +35,25 @@ void GPS::setup()
isConnected = ublox.begin(_serial_gps); isConnected = ublox.begin(_serial_gps);
// try a second time, the ublox lib serial parsing is buggy? // try a second time, the ublox lib serial parsing is buggy?
// if(!isConnected) isConnected = ublox.begin(_serial_gps); if (!isConnected)
isConnected = ublox.begin(_serial_gps);
if (isConnected) if (isConnected) {
{
DEBUG_MSG("Connected to GPS successfully, TXpin=%d\n", GPS_TX_PIN); DEBUG_MSG("Connected to GPS successfully, TXpin=%d\n", GPS_TX_PIN);
bool factoryReset = false; bool factoryReset = false;
bool ok; bool ok;
if (factoryReset) if (factoryReset) {
{ // It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have GPS_TX connected) // GPS_TX connected)
ublox.factoryReset(); ublox.factoryReset();
delay(2000); delay(2000);
isConnected = ublox.begin(_serial_gps); isConnected = ublox.begin(_serial_gps);
DEBUG_MSG("Factory reset success=%d\n", isConnected); DEBUG_MSG("Factory reset success=%d\n", isConnected);
if (isConnected) if (isConnected) {
{
ublox.assumeAutoPVT(true, true); // Just parse NEMA for now ublox.assumeAutoPVT(true, true); // Just parse NEMA for now
} }
} } else {
else
{
ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API
assert(ok); assert(ok);
ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low
@@ -70,15 +67,14 @@ void GPS::setup()
} }
ok = ublox.saveConfiguration(2000); ok = ublox.saveConfiguration(2000);
assert(ok); assert(ok);
} } else {
else
{
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
// assume NEMA at 9600 baud. // assume NEMA at 9600 baud.
DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n"); 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 // 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) // 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); ublox.assumeAutoPVT(true, true);
} }
#endif #endif
@@ -88,8 +84,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);
@@ -101,8 +96,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);
@@ -137,33 +131,38 @@ bool GPS::canSleep()
/// 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()
{ {
if (isConnected)
ublox.powerOff(); ublox.powerOff();
} }
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
// getPVT automatically calls checkUblox // getPVT automatically calls checkUblox
ublox.checkUblox(); // See if new data is available. Process bytes as they come in. ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
// If we don't have a fix (a quick check), don't try waiting for a solution)
// Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions
// turn off for now
// fixtype = ublox.getFixType();
DEBUG_MSG("fix type %d\n", fixtype);
}
// DEBUG_MSG("sec %d\n", ublox.getSecond()); // DEBUG_MSG("sec %d\n", ublox.getSecond());
// DEBUG_MSG("lat %d\n", ublox.getLatitude()); // DEBUG_MSG("lat %d\n", ublox.getLatitude());
// If we don't have a fix (a quick check), don't try waiting for a solution)
uint8_t fixtype = ublox.getFixType();
DEBUG_MSG("fix type %d\n", fixtype);
// any fix that has time // any fix that has time
if ((fixtype >= 2 && fixtype <= 5) && !timeSetFromGPS && ublox.getT()) if (!timeSetFromGPS && ublox.getT()) {
{
struct timeval tv; struct timeval tv;
isConnected = true; // We just received a packet, so we must have a GPS
/* 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 = ublox.getSecond(); t.tm_sec = ublox.getSecond();
@@ -185,27 +184,23 @@ void GPS::doTask()
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only
{ {
// we only notify if position has changed // we only notify if position has changed
isConnected = true; // We just received a packet, so we must have a GPS
latitude = ublox.getLatitude() * 1e-7; latitude = ublox.getLatitude() * 1e-7;
longitude = ublox.getLongitude() * 1e-7; longitude = ublox.getLongitude() * 1e-7;
altitude = ublox.getAltitude() / 1000; // in mm convert to meters altitude = ublox.getAltitude() / 1000; // in mm convert to meters
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude, longitude, altitude); 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 hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
if (hasValidLocation) if (hasValidLocation) {
{
wantNewLocation = false; wantNewLocation = false;
notifyObservers(); notifyObservers();
// ublox.powerOff(); // 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 #endif
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s 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
// the serial
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000); setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
} }

View File

@@ -1,9 +1,9 @@
#pragma once #pragma once
#include "PeriodicTask.h"
#include "Observer.h" #include "Observer.h"
#include "sys/time.h" #include "PeriodicTask.h"
#include "SparkFun_Ublox_Arduino_Library.h" #include "SparkFun_Ublox_Arduino_Library.h"
#include "sys/time.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)
@@ -50,4 +50,3 @@ private:
}; };
extern GPS gps; extern GPS gps;

View File

@@ -10,8 +10,7 @@
* *
* Eventually this routine will even be safe for ISR use... * Eventually this routine will even be safe for ISR use...
*/ */
template <class T> template <class T> class MemoryPool
class MemoryPool
{ {
PointerQueue<T> dead; PointerQueue<T> dead;
@@ -29,10 +28,7 @@ public:
release(&buf[i]); release(&buf[i]);
} }
~MemoryPool() ~MemoryPool() { delete[] buf; }
{
delete[] buf;
}
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available /// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
T *allocZeroed() T *allocZeroed()
@@ -43,7 +39,8 @@ public:
return p; return p;
} }
/// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably don't want this version) /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably
/// don't want this version)
T *allocZeroed(TickType_t maxWait) T *allocZeroed(TickType_t maxWait)
{ {
T *p = dead.dequeuePtr(maxWait); T *p = dead.dequeuePtr(maxWait);
@@ -66,16 +63,18 @@ 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 &&
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool (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 &&
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool (p - buf) <
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
} }
}; };

View File

@@ -1,19 +1,22 @@
#include "BluetoothUtil.h"
#include "MeshBluetoothService.h" #include "MeshBluetoothService.h"
#include <esp_gatt_defs.h> #include "BluetoothUtil.h"
#include <BLE2902.h>
#include <Arduino.h> #include <Arduino.h>
#include <BLE2902.h>
#include <assert.h> #include <assert.h>
#include <esp_gatt_defs.h>
#include "mesh.pb.h"
#include "MeshService.h"
#include "mesh-pb-constants.h"
#include "NodeDB.h"
#include "configuration.h"
#include "PowerFSM.h"
#include "CallbackCharacteristic.h" #include "CallbackCharacteristic.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "mesh.pb.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 #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
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)];
class ProtobufCharacteristic : public CallbackCharacteristic class ProtobufCharacteristic : public CallbackCharacteristic
@@ -23,9 +26,7 @@ class ProtobufCharacteristic : public CallbackCharacteristic
public: public:
ProtobufCharacteristic(const char *uuid, uint32_t btprops, const pb_msgdesc_t *_fields, void *_my_struct) ProtobufCharacteristic(const char *uuid, uint32_t btprops, const pb_msgdesc_t *_fields, void *_my_struct)
: CallbackCharacteristic(uuid, btprops), : CallbackCharacteristic(uuid, btprops), fields(_fields), my_struct(_my_struct)
fields(_fields),
my_struct(_my_struct)
{ {
setCallbacks(this); setCallbacks(this);
} }
@@ -61,7 +62,8 @@ class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCall
{ {
public: public:
NodeInfoCharacteristic() NodeInfoCharacteristic()
: BLECharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) : BLECharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8",
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
{ {
setCallbacks(this); setCallbacks(this);
} }
@@ -72,14 +74,12 @@ public:
const NodeInfo *info = nodeDB.readNextInfo(); const NodeInfo *info = nodeDB.readNextInfo();
if (info) if (info) {
{ DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id,
DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id, info->user.long_name); info->user.long_name);
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), NodeInfo_fields, info); size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), NodeInfo_fields, info);
c->setValue(trBytes, numbytes); c->setValue(trBytes, numbytes);
} } else {
else
{
c->setValue(trBytes, 0); // Send an empty response c->setValue(trBytes, 0); // Send an empty response
DEBUG_MSG("Done sending nodeinfos\n"); DEBUG_MSG("Done sending nodeinfos\n");
} }
@@ -98,10 +98,18 @@ class RadioCharacteristic : public ProtobufCharacteristic
{ {
public: public:
RadioCharacteristic() RadioCharacteristic()
: ProtobufCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, RadioConfig_fields, &radioConfig) : ProtobufCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262",
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, RadioConfig_fields,
&radioConfig)
{ {
} }
void onRead(BLECharacteristic *c)
{
DEBUG_MSG("Reading radio config\n");
ProtobufCharacteristic::onRead(c);
}
void onWrite(BLECharacteristic *c) void onWrite(BLECharacteristic *c)
{ {
ProtobufCharacteristic::onWrite(c); ProtobufCharacteristic::onWrite(c);
@@ -114,31 +122,29 @@ class OwnerCharacteristic : public ProtobufCharacteristic
{ {
public: public:
OwnerCharacteristic() OwnerCharacteristic()
: ProtobufCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, User_fields, &owner) : ProtobufCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6",
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, User_fields, &owner)
{ {
} }
void onWrite(BLECharacteristic *c) void onWrite(BLECharacteristic *c)
{ {
BLEKeepAliveCallbacks::onWrite(c); // NOTE: We do not call the standard ProtobufCharacteristic superclass, because we want custom write behavior BLEKeepAliveCallbacks::onWrite(
c); // NOTE: We do not call the standard ProtobufCharacteristic superclass, because we want custom write behavior
static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr
if (writeToDest(c, &o)) if (writeToDest(c, &o)) {
{
int changed = 0; int changed = 0;
if (*o.long_name) if (*o.long_name) {
{
changed |= strcmp(owner.long_name, o.long_name); changed |= strcmp(owner.long_name, o.long_name);
strcpy(owner.long_name, o.long_name); strcpy(owner.long_name, o.long_name);
} }
if (*o.short_name) if (*o.short_name) {
{
changed |= strcmp(owner.short_name, o.short_name); changed |= strcmp(owner.short_name, o.short_name);
strcpy(owner.short_name, o.short_name); strcpy(owner.short_name, o.short_name);
} }
if (*o.id) if (*o.id) {
{
changed |= strcmp(owner.id, o.id); changed |= strcmp(owner.id, o.id);
strcpy(owner.id, o.id); strcpy(owner.id, o.id);
} }
@@ -152,10 +158,7 @@ public:
class ToRadioCharacteristic : public CallbackCharacteristic class ToRadioCharacteristic : public CallbackCharacteristic
{ {
public: public:
ToRadioCharacteristic() ToRadioCharacteristic() : CallbackCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE) {}
: CallbackCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE)
{
}
void onWrite(BLECharacteristic *c) void onWrite(BLECharacteristic *c)
{ {
@@ -169,8 +172,7 @@ public:
class FromRadioCharacteristic : public CallbackCharacteristic class FromRadioCharacteristic : public CallbackCharacteristic
{ {
public: public:
FromRadioCharacteristic() FromRadioCharacteristic() : CallbackCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ)
: CallbackCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ)
{ {
} }
@@ -181,13 +183,10 @@ public:
// Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue
// or make empty if the queue is empty // or make empty if the queue is empty
if (!mp) if (!mp) {
{
DEBUG_MSG("toPhone queue is empty\n"); DEBUG_MSG("toPhone queue is empty\n");
c->setValue((uint8_t *)"", 0); c->setValue((uint8_t *)"", 0);
} } else {
else
{
static FromRadio fRadio; static FromRadio fRadio;
// Encapsulate as a FromRadio packet // Encapsulate as a FromRadio packet
@@ -208,8 +207,9 @@ class FromNumCharacteristic : public CallbackCharacteristic
{ {
public: public:
FromNumCharacteristic() FromNumCharacteristic()
: CallbackCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453", : CallbackCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453", BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY) BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY)
{ {
} }
@@ -220,6 +220,27 @@ public:
} }
}; };
class MyNodeInfoCharacteristic : public ProtobufCharacteristic
{
public:
MyNodeInfoCharacteristic()
: ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields,
&myNodeInfo)
{
}
void onRead(BLECharacteristic *c)
{
// update gps connection state
myNodeInfo.has_gps = gps.isConnected;
ProtobufCharacteristic::onRead(c);
myNodeInfo.error_code = 0; // The phone just read us, so throw it away
myNodeInfo.error_address = 0;
}
};
FromNumCharacteristic *meshFromNumCharacteristic; FromNumCharacteristic *meshFromNumCharacteristic;
/** /**
@@ -227,8 +248,7 @@ FromNumCharacteristic *meshFromNumCharacteristic;
*/ */
void bluetoothNotifyFromNum(uint32_t newValue) void bluetoothNotifyFromNum(uint32_t newValue)
{ {
if (meshFromNumCharacteristic) if (meshFromNumCharacteristic) {
{
// if bt not running ignore // if bt not running ignore
meshFromNumCharacteristic->setValue(newValue); meshFromNumCharacteristic->setValue(newValue);
meshFromNumCharacteristic->notify(); meshFromNumCharacteristic->notify();
@@ -251,8 +271,7 @@ BLEService *createMeshBluetoothService(BLEServer *server)
addWithDesc(service, meshFromNumCharacteristic, "fromRadio"); addWithDesc(service, meshFromNumCharacteristic, "fromRadio");
addWithDesc(service, new ToRadioCharacteristic, "toRadio"); addWithDesc(service, new ToRadioCharacteristic, "toRadio");
addWithDesc(service, new FromRadioCharacteristic, "fromNum"); addWithDesc(service, new FromRadioCharacteristic, "fromNum");
addWithDesc(service, new MyNodeInfoCharacteristic, "myNode");
addWithDesc(service, new ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields, &myNodeInfo), "myNode");
addWithDesc(service, new RadioCharacteristic, "radio"); addWithDesc(service, new RadioCharacteristic, "radio");
addWithDesc(service, new OwnerCharacteristic, "owner"); addWithDesc(service, new OwnerCharacteristic, "owner");
addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo"); addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo");
@@ -263,8 +282,7 @@ BLEService *createMeshBluetoothService(BLEServer *server)
// We only add to advertisting once, because the ESP32 arduino code is dumb and that object never dies // We only add to advertisting once, because the ESP32 arduino code is dumb and that object never dies
static bool firstTime = true; static bool firstTime = true;
if (firstTime) if (firstTime) {
{
firstTime = false; firstTime = false;
server->getAdvertising()->addServiceUUID(service->getUUID()); server->getAdvertising()->addServiceUUID(service->getUUID());
} }
@@ -282,8 +300,6 @@ void stopMeshBluetoothService()
meshService->stop(); meshService->stop();
} }
void destroyMeshBluetoothService() void destroyMeshBluetoothService()
{ {
assert(meshService); assert(meshService);

View File

@@ -1,8 +1,8 @@
#pragma once #pragma once
#include <BLEService.h>
#include <BLEServer.h>
#include <Arduino.h> #include <Arduino.h>
#include <BLEServer.h>
#include <BLEService.h>
BLEService *createMeshBluetoothService(BLEServer *server); BLEService *createMeshBluetoothService(BLEServer *server);
void destroyMeshBluetoothService(); void destroyMeshBluetoothService();

View File

@@ -1,18 +1,18 @@
#include <SPI.h>
#include "RH_RF95.h" #include "RH_RF95.h"
#include "error.h"
#include <RHMesh.h> #include <RHMesh.h>
#include <SPI.h>
#include <assert.h> #include <assert.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "MeshRadio.h" #include "MeshRadio.h"
#include "configuration.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "configuration.h"
#define DEFAULT_CHANNEL_NUM 3 // we randomly pick one #include <pb_decode.h>
#include <pb_encode.h>
/// 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};
/** /**
* ## LoRaWAN for North America * ## LoRaWAN for North America
@@ -21,25 +21,23 @@ LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
The maximum output power for North America is +30 dBM. The maximum output power for North America is +30 dBM.
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are separated by 2.16 MHz with respect to the adjacent channels. The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are
Channel zero starts at 903.08 MHz center frequency. separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
*/ */
/// Sometimes while debugging it is useful to set this false, to disable rf95 accesses /// Sometimes while debugging it is useful to set this false, to disable rf95 accesses
bool useHardware = true; bool useHardware = true;
MeshRadio::MeshRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest) MeshRadio::MeshRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest) : rf95(_pool, _rxDest), manager(rf95)
: rf95(_pool, _rxDest),
manager(rf95)
{ {
myNodeInfo.num_channels = NUM_CHANNELS; myNodeInfo.num_channels = NUM_CHANNELS;
// radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast // radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
//channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide bandwidth so incompatible radios can talk together // channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide bandwidth so
// incompatible radios can talk together
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range channelSettings.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
@@ -64,10 +62,10 @@ bool MeshRadio::init()
delay(10); delay(10);
#endif #endif
manager.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time. manager.setThisAddress(
nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time.
if (!manager.init()) if (!manager.init()) {
{
DEBUG_MSG("LoRa radio init failed\n"); DEBUG_MSG("LoRa radio init failed\n");
DEBUG_MSG("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info\n"); DEBUG_MSG("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info\n");
return false; return false;
@@ -81,22 +79,37 @@ 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
// Set up default configuration // Set up default configuration
// No Sync Words in LORA mode. // No Sync Words in LORA mode.
rf95.setModemConfig((RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default rf95.setModemConfig(
(RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default
// 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;
if (!rf95.setFrequency(center_freq)) float center_freq = CH0 + CH_SPACING * channel_num;
{ if (!rf95.setFrequency(center_freq)) {
DEBUG_MSG("setFrequency failed\n"); DEBUG_MSG("setFrequency failed\n");
assert(0); // fixme panic assert(0); // fixme panic
} }
@@ -109,7 +122,8 @@ 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();
@@ -117,16 +131,26 @@ void MeshRadio::reloadConfig()
ErrorCode MeshRadio::send(MeshPacket *p) ErrorCode MeshRadio::send(MeshPacket *p)
{ {
lastTxStart = millis();
if (useHardware) if (useHardware)
return rf95.send(p); return rf95.send(p);
else else {
{
rf95.pool.release(p); rf95.pool.release(p);
return ERRNO_OK; return ERRNO_OK;
} }
} }
#define TX_WATCHDOG_TIMEOUT 30 * 1000
void MeshRadio::loop() void MeshRadio::loop()
{ {
// Currently does nothing, since we do it all in ISRs now // It should never take us more than 30 secs to send a packet, if it does, we have a bug
uint32_t now = millis();
if (lastTxStart != 0 && (now - lastTxStart) > TX_WATCHDOG_TIMEOUT && rf95.mode() == RHGenericDriver::RHModeTx) {
DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode");
rf95.setModeRx();
recordCriticalError(ErrTxWatchdog);
lastTxStart = 0; // Stop checking for now, because we just warned the developer
}
} }

View File

@@ -1,12 +1,12 @@
#pragma once #pragma once
#include "CustomRF95.h" #include "CustomRF95.h"
#include <RHMesh.h>
#include "MemoryPool.h" #include "MemoryPool.h"
#include "mesh.pb.h"
#include "PointerQueue.h"
#include "MeshTypes.h" #include "MeshTypes.h"
#include "PointerQueue.h"
#include "configuration.h" #include "configuration.h"
#include "mesh.pb.h"
#include <RHMesh.h>
// US channel settings // US channel settings
#define CH0_US 903.08f // MHz #define CH0_US 903.08f // MHz
@@ -58,13 +58,14 @@
#error "HW_VERSION not set" #error "HW_VERSION not set"
#endif #endif
/** /**
* A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids) * A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids)
*/ */
class MeshRadio { class MeshRadio
{
public: public:
CustomRF95 rf95; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing CustomRF95
rf95; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing
/** pool is the pool we will alloc our rx packets from /** pool is the pool we will alloc our rx packets from
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
@@ -86,11 +87,11 @@ public:
void reloadConfig(); void reloadConfig();
private: private:
// RHDatagram manager;
// RHReliableDatagram manager; // don't use mesh yet // RHReliableDatagram manager; // don't use mesh yet
RHMesh manager; RHMesh manager;
// MeshRXHandler rxHandler;
/// Used for the tx timer watchdog, to check for bugs in our transmit code, msec of last time we did a send
uint32_t lastTxStart = 0;
/// low level send, might block for mutiple seconds /// low level send, might block for mutiple seconds
ErrorCode sendTo(NodeNum dest, const uint8_t *buf, size_t len); ErrorCode sendTo(NodeNum dest, const uint8_t *buf, size_t len);
@@ -98,5 +99,3 @@ private:
/// enqueue a received packet in rxDest /// enqueue a received packet in rxDest
void handleReceive(MeshPacket *p); void handleReceive(MeshPacket *p);
}; };

View File

@@ -2,37 +2,41 @@
#include <Arduino.h> #include <Arduino.h>
#include <assert.h> #include <assert.h>
#include "mesh-pb-constants.h"
#include "MeshService.h"
#include "MeshBluetoothService.h"
#include "NodeDB.h"
#include "GPS.h" #include "GPS.h"
#include "screen.h" #include "MeshBluetoothService.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Periodic.h" #include "Periodic.h"
#include "PowerFSM.h" #include "PowerFSM.h"
#include "main.h"
#include "mesh-pb-constants.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.
It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were alloced with new). It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were
After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move sent packets into a 'sentToPhone' queue alloced with new). After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move
of packets we can delete just as soon as we are sure the phone has acked those packets - when the phone writes to FromNum) sent packets into a 'sentToPhone' queue of packets we can delete just as soon as we are sure the phone has acked those packets -
when the phone writes to FromNum)
mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes, arbitrating to select mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes,
a node number and keeping the current nodedb. arbitrating to select a node number and keeping the current nodedb.
*/ */
/* Broadcast when a newly powered mesh node wants to find a node num it can use /* Broadcast when a newly powered mesh node wants to find a node num it can use
The algoritm is as follows: The algoritm is as follows:
* when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so the new node can build its node db) * when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so
* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that indicates a collision has occurred and the following steps should happen: the new node can build its node db)
* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that
indicates a collision has occurred and the following steps should happen:
If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to keep its nodenum. We send a broadcast message If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to
of OUR User (we use a broadcast so that the other node can receive our message, considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay. keep its nodenum. We send a broadcast message of OUR User (we use a broadcast so that the other node can receive our message,
considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay.
If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random nodenum (filtering against whatever it knows about the nodedb) and If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random
rebroadcast their User. nodenum (filtering against whatever it knows about the nodedb) and rebroadcast their User.
FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first. FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first.
*/ */
@@ -40,15 +44,15 @@ FIXME in the initial proof of concept we just skip the entire want/deny flow and
MeshService service; MeshService service;
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX // I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
#define MAX_PACKETS (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + 2) // max number of packets which can be in flight (either queued from reception or queued for sending) #define MAX_PACKETS \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
#define MAX_RX_FROMRADIO 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big #define MAX_RX_FROMRADIO \
4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big
MeshService::MeshService() MeshService::MeshService()
: packetPool(MAX_PACKETS), : packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE), fromRadioQueue(MAX_RX_FROMRADIO), fromNum(0),
toPhoneQueue(MAX_RX_TOPHONE),
fromRadioQueue(MAX_RX_FROMRADIO),
fromNum(0),
radio(packetPool, fromRadioQueue) radio(packetPool, fromRadioQueue)
{ {
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
@@ -88,29 +92,25 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
// we win if we have a lower macaddr // we win if we have a lower macaddr
bool weWin = memcmp(&owner.macaddr, &mp->payload.variant.user.macaddr, sizeof(owner.macaddr)) < 0; bool weWin = memcmp(&owner.macaddr, &mp->payload.variant.user.macaddr, sizeof(owner.macaddr)) < 0;
if (isCollision) if (isCollision) {
{ if (weWin) {
if (weWin)
{
DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n"); DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n");
packetPool.release(mp); // discard it packetPool.release(mp); // discard it
mp = NULL; mp = NULL;
sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum
} } else {
else
{
// we lost, we need to try for a new nodenum! // we lost, we need to try for a new nodenum!
DEBUG_MSG("NOTE! Received a nodenum collision we lost, so picking a new nodenum\n"); DEBUG_MSG("NOTE! Received a nodenum collision we lost, so picking a new nodenum\n");
nodeDB.updateFrom(*mp); // update the DB early - before trying to repick (so we don't select the same node number again) nodeDB.updateFrom(
*mp); // update the DB early - before trying to repick (so we don't select the same node number again)
nodeDB.pickNewNodeNum(); nodeDB.pickNewNodeNum();
sendOurOwner(); // broadcast our new attempt at a node number sendOurOwner(); // broadcast our new attempt at a node number
} }
} } else if (wasBroadcast) {
else if (wasBroadcast) // If we haven't yet abandoned the packet and it was a broadcast, reply (just to them) with our User record so they can
{ // build their DB
// If we haven't yet abandoned the packet and it was a broadcast, reply (just to them) with our User record so they can build their DB
// Someone just sent us a User, reply with our Owner // Someone just sent us a User, reply with our Owner
DEBUG_MSG("Received broadcast Owner from 0x%x, replying with our owner\n", mp->from); DEBUG_MSG("Received broadcast Owner from 0x%x, replying with our owner\n", mp->from);
@@ -118,7 +118,7 @@ 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;
@@ -126,12 +126,10 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
void MeshService::handleIncomingPosition(MeshPacket *mp) void MeshService::handleIncomingPosition(MeshPacket *mp)
{ {
if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag) if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag) {
{
DEBUG_MSG("handled incoming position time=%u\n", mp->payload.variant.position.time); DEBUG_MSG("handled incoming position time=%u\n", mp->payload.variant.position.time);
if (mp->payload.variant.position.time) if (mp->payload.variant.position.time) {
{
struct timeval tv; struct timeval tv;
uint32_t secs = mp->payload.variant.position.time; uint32_t secs = mp->payload.variant.position.time;
@@ -153,32 +151,28 @@ void MeshService::handleFromRadio(MeshPacket *mp)
if (!myNodeInfo.has_gps) if (!myNodeInfo.has_gps)
handleIncomingPosition(mp); 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);
} }
// If we veto a received User packet, we don't put it into the DB or forward it to the phone (to prevent confusing it) // If we veto a received User packet, we don't put it into the DB or forward it to the phone (to prevent confusing it)
if (mp) if (mp) {
{
DEBUG_MSG("Forwarding to phone, from=0x%x, rx_time=%u\n", mp->from, mp->rx_time); DEBUG_MSG("Forwarding to phone, from=0x%x, rx_time=%u\n", mp->from, mp->rx_time);
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
fromNum++; fromNum++;
if (toPhoneQueue.numFree() == 0) if (toPhoneQueue.numFree() == 0) {
{
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n"); DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
MeshPacket *d = toPhoneQueue.dequeuePtr(0); MeshPacket *d = toPhoneQueue.dequeuePtr(0);
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
DEBUG_MSG("Dropping vetoed User message\n"); DEBUG_MSG("Dropping vetoed User message\n");
} }
@@ -186,8 +180,7 @@ void MeshService::handleFromRadio()
{ {
MeshPacket *mp; MeshPacket *mp;
uint32_t oldFromNum = fromNum; uint32_t oldFromNum = fromNum;
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
{
handleFromRadio(mp); handleFromRadio(mp);
} }
if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets
@@ -227,23 +220,20 @@ void MeshService::handleToRadio(std::string s)
{ {
static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning
if (pb_decode_from_bytes((const uint8_t *)s.c_str(), s.length(), ToRadio_fields, &r)) if (pb_decode_from_bytes((const uint8_t *)s.c_str(), s.length(), ToRadio_fields, &r)) {
{ switch (r.which_variant) {
switch (r.which_variant) 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
handleIncomingPosition(&r.variant.packet); // If it is a position packet, perhaps set our clock handleIncomingPosition(&r.variant.packet); // If it is a position packet, perhaps set our clock
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)
// Send the packet into the mesh // Send the packet into the mesh
sendToMesh(packetPool.allocCopy(r.variant.packet)); sendToMesh(packetPool.allocCopy(r.variant.packet));
bool loopback = false; // if true send any packet the phone sends back itself (for testing) bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback) if (loopback) {
{
MeshPacket *mp = packetPool.allocCopy(r.variant.packet); MeshPacket *mp = packetPool.allocCopy(r.variant.packet);
handleFromRadio(mp); handleFromRadio(mp);
bluetoothNotifyFromNum(fromNum); // tell the phone a new packet arrived bluetoothNotifyFromNum(fromNum); // tell the phone a new packet arrived
@@ -254,6 +244,8 @@ void MeshService::handleToRadio(std::string s)
DEBUG_MSG("Error: unexpected ToRadio variant\n"); DEBUG_MSG("Error: unexpected ToRadio variant\n");
break; break;
} }
} else {
DEBUG_MSG("Error: ignoring malformed toradio\n");
} }
} }
@@ -261,10 +253,10 @@ void MeshService::sendToMesh(MeshPacket *p)
{ {
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...) nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other 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
// Note: for now, we allow a device with a local GPS to include the time, so that gpsless devices can get time. // nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless
if (p->has_payload && p->payload.which_variant == SubPacket_position_tag) // devices can get time.
{ if (p->has_payload && p->payload.which_variant == SubPacket_position_tag) {
if (!myNodeInfo.has_gps) if (!myNodeInfo.has_gps)
p->payload.variant.position.time = 0; p->payload.variant.position.time = 0;
else else
@@ -274,8 +266,7 @@ void MeshService::sendToMesh(MeshPacket *p)
// 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())
DEBUG_MSG("Dropping locally processed message\n"); DEBUG_MSG("Dropping locally processed message\n");
else else {
{
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
if (radio.send(p) != ERRNO_OK) if (radio.send(p) != ERRNO_OK)
DEBUG_MSG("Dropped packet because send queue was full!\n"); DEBUG_MSG("Dropped packet because send queue was full!\n");
@@ -316,7 +307,8 @@ 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;
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 =
gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
sendToMesh(p); sendToMesh(p);
} }
@@ -325,27 +317,26 @@ 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 0 // !zero or !zero lat/long means valid
if (gps.altitude.isValid()) if (gps.latitude != 0 || gps.longitude != 0) {
pos.altitude = gps.altitude.meters(); if (gps.altitude != 0)
pos.latitude = gps.location.lat(); pos.altitude = gps.altitude;
pos.longitude = gps.location.lng(); pos.latitude = gps.latitude;
pos.longitude = gps.longitude;
pos.time = gps.getValidTime(); pos.time = gps.getValidTime();
#endif }
// We limit our GPS broadcasts to a max rate // We limit our GPS broadcasts to a max rate
static uint32_t lastGpsSend; static uint32_t lastGpsSend;
uint32_t now = millis(); uint32_t now = millis();
if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000) if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000) {
{
lastGpsSend = now; lastGpsSend = now;
DEBUG_MSG("Sending position to mesh\n"); DEBUG_MSG("Sending position to mesh\n");
sendToMesh(p); sendToMesh(p);
} } else {
else
{
// We don't need to send this packet to anyone else, but it still serves as a nice uniform way to update our local state // We don't need to send this packet to anyone else, but it still serves as a nice uniform way to update our local state
nodeDB.updateFrom(*p); nodeDB.updateFrom(*p);

View File

@@ -3,11 +3,11 @@
#include <Arduino.h> #include <Arduino.h>
#include <assert.h> #include <assert.h>
#include "mesh.pb.h"
#include "MeshRadio.h"
#include "PointerQueue.h"
#include "MemoryPool.h" #include "MemoryPool.h"
#include "MeshRadio.h"
#include "Observer.h" #include "Observer.h"
#include "PointerQueue.h"
#include "mesh.pb.h"
/** /**
* Top level app for this service. keeps the mesh, the radio config and the queue of received packets. * Top level app for this service. keeps the mesh, the radio config and the queue of received packets.
@@ -31,7 +31,6 @@ class MeshService: private Observer
uint32_t fromNum; uint32_t fromNum;
public: public:
MeshRadio radio; MeshRadio radio;
MeshService(); MeshService();
@@ -60,17 +59,20 @@ public:
/// Allocate and return a meshpacket which defaults as send to broadcast from the current node. /// Allocate and return a meshpacket which defaults as send to broadcast from the current node.
MeshPacket *allocForSending(); MeshPacket *allocForSending();
/// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least sends our owner /// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least
/// sends our owner
void sendNetworkPing(NodeNum dest = NODENUM_BROADCAST); void sendNetworkPing(NodeNum dest = NODENUM_BROADCAST);
/// Send our owner info to a particular node /// Send our owner info to a particular node
void sendOurOwner(NodeNum dest = NODENUM_BROADCAST); void sendOurOwner(NodeNum dest = NODENUM_BROADCAST);
private: private:
/// Broadcasts our last known position /// Broadcasts our last known position
void sendOurPosition(NodeNum dest = NODENUM_BROADCAST); void sendOurPosition(NodeNum dest = NODENUM_BROADCAST);
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after sending. /// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after
/// This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb cache /// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb
/// cache
void sendToMesh(MeshPacket *p); void sendToMesh(MeshPacket *p);
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
@@ -92,4 +94,3 @@ private:
}; };
extern MeshService service; extern MeshService service;

View File

@@ -5,13 +5,14 @@
#include "FS.h" #include "FS.h"
#include "SPIFFS.h" #include "SPIFFS.h"
#include <pb_encode.h>
#include <pb_decode.h>
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "NodeDB.h"
#include "GPS.h" #include "GPS.h"
#include "NodeDB.h"
#include "PowerFSM.h" #include "PowerFSM.h"
#include "configuration.h"
#include "error.h"
#include "mesh-pb-constants.h"
#include <pb_decode.h>
#include <pb_encode.h>
NodeDB nodeDB; NodeDB nodeDB;
@@ -26,7 +27,7 @@ DeviceState versions used to be defined in the .proto file but really only this
#define here. #define here.
*/ */
#define DEVICESTATE_CUR_VER 2 #define DEVICESTATE_CUR_VER 6
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER #define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
#define FS SPIFFS #define FS SPIFFS
@@ -41,9 +42,7 @@ User &owner = devicestate.owner;
static uint8_t ourMacAddr[6]; static uint8_t ourMacAddr[6];
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {}
{
}
void NodeDB::init() void NodeDB::init()
{ {
@@ -52,7 +51,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;
@@ -78,8 +77,8 @@ void NodeDB::init()
// 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);
sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4],
ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]); ourMacAddr[5]);
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
// make each node start with ad different random seed (but okay that the sequence is the same each boot) // make each node start with ad different random seed (but okay that the sequence is the same each boot)
@@ -122,8 +121,7 @@ void NodeDB::pickNewNodeNum()
r = NUM_RESERVED; // don't pick a reserved node number r = NUM_RESERVED; // don't pick a reserved node number
NodeInfo *found; NodeInfo *found;
while ((found = getNode(r)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr))) while ((found = getNode(r)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr))) {
{
NodeNum n = random(NUM_RESERVED, NODENUM_BROADCAST); // try a new random choice NodeNum n = random(NUM_RESERVED, NODENUM_BROADCAST); // try a new random choice
DEBUG_MSG("NOTE! Our desired nodenum 0x%x is in use, so trying for 0x%x\n", r, n); DEBUG_MSG("NOTE! Our desired nodenum 0x%x is in use, so trying for 0x%x\n", r, n);
r = n; r = n;
@@ -140,25 +138,21 @@ void NodeDB::loadFromDisk()
static DeviceState scratch; static DeviceState scratch;
File f = FS.open(preffile); File f = FS.open(preffile);
if (f) if (f) {
{
DEBUG_MSG("Loading saved preferences\n"); DEBUG_MSG("Loading saved preferences\n");
pb_istream_t stream = {&readcb, &f, DeviceState_size}; pb_istream_t stream = {&readcb, &f, DeviceState_size};
// DEBUG_MSG("Preload channel name=%s\n", channelSettings.name); // DEBUG_MSG("Preload channel name=%s\n", channelSettings.name);
memset(&scratch, 0, sizeof(scratch)); memset(&scratch, 0, sizeof(scratch));
if (!pb_decode(&stream, DeviceState_fields, &scratch)) if (!pb_decode(&stream, DeviceState_fields, &scratch)) {
{
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream)); DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
// FIXME - report failure to phone // FIXME - report failure to phone
} } else {
else
{
if (scratch.version < DEVICESTATE_MIN_VER) 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;
} }
@@ -166,9 +160,7 @@ void NodeDB::loadFromDisk()
} }
f.close(); f.close();
} } else {
else
{
DEBUG_MSG("No saved preferences found\n"); DEBUG_MSG("No saved preferences found\n");
} }
} }
@@ -176,8 +168,7 @@ void NodeDB::loadFromDisk()
void NodeDB::saveToDisk() void NodeDB::saveToDisk()
{ {
File f = FS.open(preftmp, "w"); File f = FS.open(preftmp, "w");
if (f) if (f) {
{
DEBUG_MSG("Writing preferences\n"); DEBUG_MSG("Writing preferences\n");
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0}; pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
@@ -185,8 +176,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_CUR_VER; 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));
// FIXME - report failure to phone // FIXME - report failure to phone
} }
@@ -198,9 +188,7 @@ void NodeDB::saveToDisk()
DEBUG_MSG("Warning: Can't remove old pref file\n"); DEBUG_MSG("Warning: Can't remove old pref file\n");
if (!FS.rename(preftmp, preffile)) if (!FS.rename(preftmp, preffile))
DEBUG_MSG("Error: can't rename new pref file\n"); DEBUG_MSG("Error: can't rename new pref file\n");
} } else {
else
{
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
} }
} }
@@ -244,8 +232,7 @@ size_t NodeDB::getNumOnlineNodes()
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void NodeDB::updateFrom(const MeshPacket &mp) void NodeDB::updateFrom(const MeshPacket &mp)
{ {
if (mp.has_payload) if (mp.has_payload) {
{
const SubPacket &p = mp.payload; const SubPacket &p = mp.payload;
DEBUG_MSG("Update DB node 0x%x for variant %d, rx_time=%u\n", mp.from, p.which_variant, mp.rx_time); DEBUG_MSG("Update DB node 0x%x for variant %d, rx_time=%u\n", mp.from, p.which_variant, mp.rx_time);
@@ -255,16 +242,13 @@ void NodeDB::updateFrom(const MeshPacket &mp)
if (oldNumNodes != *numNodes) if (oldNumNodes != *numNodes)
updateGUI = true; // we just created a nodeinfo updateGUI = true; // we just created a nodeinfo
if (mp.rx_time) if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
{ // if the packet has a valid timestamp use it to update our last_seen
info->has_position = true; // at least the time is valid info->has_position = true; // at least the time is valid
info->position.time = mp.rx_time; info->position.time = mp.rx_time;
} }
switch (p.which_variant) switch (p.which_variant) {
{ case SubPacket_position_tag: {
case SubPacket_position_tag:
{
// we carefully preserve the old time, because we always trust our local timestamps more // we carefully preserve the old time, because we always trust our local timestamps more
uint32_t oldtime = info->position.time; uint32_t oldtime = info->position.time;
info->position = p.variant.position; info->position = p.variant.position;
@@ -274,14 +258,12 @@ void NodeDB::updateFrom(const MeshPacket &mp)
break; break;
} }
case SubPacket_data_tag: case SubPacket_data_tag: {
{
// Keep a copy of the most recent text message. // Keep a copy of the most recent text message.
if (p.variant.data.typ == Data_Type_CLEAR_TEXT) if (p.variant.data.typ == Data_Type_CLEAR_TEXT) {
{ DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.variant.data.payload.size,
DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.variant.data.payload.size, p.variant.data.payload.bytes); p.variant.data.payload.bytes);
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) {
{
// We only store/display messages destined for us. // We only store/display messages destined for us.
devicestate.rx_text_message = mp; devicestate.rx_text_message = mp;
devicestate.has_rx_text_message = true; devicestate.has_rx_text_message = true;
@@ -292,18 +274,17 @@ void NodeDB::updateFrom(const MeshPacket &mp)
break; break;
} }
case SubPacket_user_tag: case SubPacket_user_tag: {
{
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name); DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
bool changed = memcmp(&info->user, &p.variant.user, sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay bool changed = memcmp(&info->user, &p.variant.user,
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
info->user = p.variant.user; info->user = p.variant.user;
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name); DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
info->has_user = true; info->has_user = true;
if (changed) if (changed) {
{
updateGUIforNode = info; updateGUIforNode = info;
powerFSM.trigger(EVENT_NODEDB_UPDATED); powerFSM.trigger(EVENT_NODEDB_UPDATED);
@@ -336,8 +317,7 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
{ {
NodeInfo *info = getNode(n); NodeInfo *info = getNode(n);
if (!info) if (!info) {
{
// add the node // add the node
assert(*numNodes < MAX_NUM_NODES); assert(*numNodes < MAX_NUM_NODES);
info = &nodes[(*numNodes)++]; info = &nodes[(*numNodes)++];
@@ -349,3 +329,11 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
return info; return info;
} }
/// Record an error that should be reported via analytics
void recordCriticalError(CriticalErrorCode code, uint32_t address)
{
myNodeInfo.error_code = code;
myNodeInfo.error_address = address;
myNodeInfo.error_count++;
}

View File

@@ -3,8 +3,8 @@
#include <Arduino.h> #include <Arduino.h>
#include <assert.h> #include <assert.h>
#include "mesh-pb-constants.h"
#include "MeshTypes.h" #include "MeshTypes.h"
#include "mesh-pb-constants.h"
extern DeviceState devicestate; extern DeviceState devicestate;
extern MyNodeInfo &myNodeInfo; extern MyNodeInfo &myNodeInfo;
@@ -56,10 +56,11 @@ public:
// bool handleWantNodeNum(NodeNum n); // bool handleWantNodeNum(NodeNum n);
/* void handleDenyNodeNum(NodeNum FIXME read mesh proto docs, perhaps picking a random node num is not a great idea /* void handleDenyNodeNum(NodeNum FIXME read mesh proto docs, perhaps picking a random node num is not a great idea
and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message. the and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message.
unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we randomly select the unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we
from a small number of nodenums which can be used temporarily for this operation). figure out what the lower level randomly select from a small number of nodenums which can be used temporarily for this operation). figure out what the lower
mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast their denial?) level mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast
their denial?)
*/ */
/// Called from bluetooth when the user wants to start reading the node DB from scratch. /// Called from bluetooth when the user wants to start reading the node DB from scratch.
@@ -74,13 +75,16 @@ public:
/// Find a node in our DB, return null for missing /// Find a node in our DB, return null for missing
NodeInfo *getNode(NodeNum n); NodeInfo *getNode(NodeNum n);
NodeInfo *getNodeByIndex(size_t x) { assert(x < *numNodes); return &nodes[x]; } NodeInfo *getNodeByIndex(size_t x)
{
assert(x < *numNodes);
return &nodes[x];
}
/// Return the number of nodes we've heard from recently (within the last 2 hrs?) /// Return the number of nodes we've heard from recently (within the last 2 hrs?)
size_t getNumOnlineNodes(); size_t getNumOnlineNodes();
private: private:
/// Find a node in our DB, create an empty NodeInfo if missing /// Find a node in our DB, create an empty NodeInfo if missing
NodeInfo *getOrCreateNode(NodeNum n); NodeInfo *getOrCreateNode(NodeNum n);
@@ -89,4 +93,3 @@ private:
}; };
extern NodeDB nodeDB; extern NodeDB nodeDB;

View File

@@ -30,20 +30,12 @@ class Observable
public: public:
void notifyObservers() void notifyObservers()
{ {
for (std::list<Observer *>::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator) for (std::list<Observer *>::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator) {
{
(*iterator)->onNotify(this); (*iterator)->onNotify(this);
} }
} }
void addObserver(Observer *o) void addObserver(Observer *o) { observers.push_back(o); }
{
observers.push_back(o);
}
void removeObserver(Observer *o) void removeObserver(Observer *o) { observers.remove(o); }
{
observers.remove(o);
}
}; };

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include <Arduino.h>
#include "PeriodicTask.h" #include "PeriodicTask.h"
#include <Arduino.h>
/** /**
* Periodically invoke a callback. * Periodically invoke a callback.
@@ -17,6 +17,5 @@ public:
Periodic(uint32_t (*_callback)()) : callback(_callback) {} Periodic(uint32_t (*_callback)()) : callback(_callback) {}
protected: protected:
void doTask(); void doTask();
}; };

View File

@@ -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()

View File

@@ -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,7 +28,11 @@ 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;

View File

@@ -5,20 +5,17 @@
/** /**
* A wrapper for freertos queues that assumes each element is a pointer * A wrapper for freertos queues that assumes each element is a pointer
*/ */
template <class T> template <class T> class PointerQueue : public TypedQueue<T *>
class PointerQueue : public TypedQueue<T *>
{ {
public: public:
PointerQueue(int maxElements) : TypedQueue<T *>(maxElements) PointerQueue(int maxElements) : TypedQueue<T *>(maxElements) {}
{
}
// returns a ptr or null if the queue was empty // returns a ptr or null if the queue was empty
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY) T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
{ {
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 +23,6 @@ public:
{ {
T *p; T *p;
return this->dequeueFromISR(&p, higherPriWoken) == pdTRUE ? p : NULL; return this->dequeueFromISR(&p, higherPriWoken) ? p : nullptr;
} }
}; };

View File

@@ -1,12 +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 "screen.h"
#include "PowerFSM.h"
#include "GPS.h"
#include "main.h" #include "main.h"
#include "screen.h"
#include "sleep.h"
static void sdsEnter() static void sdsEnter()
{ {
@@ -25,7 +25,7 @@ static void sdsEnter()
static void lsEnter() static void lsEnter()
{ {
DEBUG_MSG("lsEnter begin\n"); DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
screen.setOn(false); screen.setOn(false);
while (!service.radio.rf95.canSleep()) while (!service.radio.rf95.canSleep())
@@ -33,7 +33,8 @@ static void lsEnter()
gps.prepareSleep(); // abandon in-process parsing gps.prepareSleep(); // abandon in-process parsing
//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 // 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 // setGPSPower(false); // kill GPS power
DEBUG_MSG("lsEnter end\n"); DEBUG_MSG("lsEnter end\n");
@@ -47,8 +48,7 @@ static void lsIdle()
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED; esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
bool reached_ls_secs = false; bool reached_ls_secs = false;
while (!reached_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;
@@ -67,19 +67,21 @@ static void lsIdle()
} }
setLed(false); setLed(false);
if (reached_ls_secs) if (reached_ls_secs) {
{
// stay in LS mode but let loop check whatever it wants // stay in LS mode but let loop check whatever it wants
DEBUG_MSG("reached ls_secs, servicing loop()\n"); DEBUG_MSG("reached ls_secs, servicing loop()\n");
} } else {
else
{
DEBUG_MSG("wakeCause %d\n", wakeCause); DEBUG_MSG("wakeCause %d\n", wakeCause);
// Regardless of why we woke just transition to NB (and that state will handle stuff like IRQs etc) 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()
{ {
@@ -104,38 +106,47 @@ 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 wakeForPing() {}
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 stateBOOT(bootEnter, NULL, NULL, "BOOT");
State stateON(onEnter, NULL, NULL, "ON"); State stateON(onEnter, NULL, NULL, "ON");
Fsm powerFSM(&stateDARK); 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
@@ -162,11 +173,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"); 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 // 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.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
} }

View File

@@ -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

View File

@@ -1,15 +1,18 @@
#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:
@@ -19,40 +22,17 @@ public:
assert(h); assert(h);
} }
~TypedQueue() ~TypedQueue() { vQueueDelete(h); }
{
vQueueDelete(h);
}
int numFree() int numFree() { return uxQueueSpacesAvailable(h); }
{
return uxQueueSpacesAvailable(h);
}
bool isEmpty() bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; }
{
return uxQueueMessagesWaiting(h) == 0;
}
// pdTRUE for success else failure bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { return xQueueSendToBack(h, &x, maxWait) == pdTRUE; }
BaseType_t enqueue(T x, TickType_t maxWait = portMAX_DELAY)
{
return xQueueSendToBack(h, &x, maxWait);
}
BaseType_t enqueueFromISR(T x, BaseType_t *higherPriWoken) bool enqueueFromISR(T x, BaseType_t *higherPriWoken) { return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE; }
{
return xQueueSendToBackFromISR(h, &x, higherPriWoken);
}
// pdTRUE for success else failure bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY) { return xQueueReceive(h, p, maxWait) == pdTRUE; }
BaseType_t dequeue(T *p, TickType_t maxWait = portMAX_DELAY)
{
return xQueueReceive(h, p, maxWait);
}
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);
}
}; };

View File

@@ -44,7 +44,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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
#endif #endif
// If we are using the JTAG port for debugging, some pins must be left free for that (and things like GPS have to be disabled) // If we are using the JTAG port for debugging, some pins must be left free for that (and things like GPS have to be disabled)
@@ -77,6 +78,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define SSD1306_ADDRESS 0x3C #define SSD1306_ADDRESS 0x3C
// Flip the screen upside down by default as it makes more sense on T-BEAM
// devices. Comment this out to not rotate screen 180 degrees.
#define FLIP_SCREEN_VERTICALLY
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// GPS // GPS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -84,14 +89,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
@@ -106,8 +109,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// This string must exactly match the case used in release file names or the android updater won't work // This string must exactly match the case used in release file names or the android updater won't work
#define HW_VENDOR "TBEAM" #define HW_VENDOR "TBEAM"
#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 BUTTON_NEED_PULLUP // if set we need to turn on the internal CPU pullup during sleep
#define I2C_SDA 21 #define I2C_SDA 21

20
src/debug.cpp Normal file
View 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
View 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

7
src/error.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
/// Error codes for critical error
enum CriticalErrorCode { NoError, ErrTxWatchdog };
/// Record an error that should be reported via analytics
void recordCriticalError(CriticalErrorCode code, uint32_t address = 0);

View File

@@ -1,11 +1,8 @@
#define SATELLITE_IMAGE_WIDTH 16 #define SATELLITE_IMAGE_WIDTH 16
#define SATELLITE_IMAGE_HEIGHT 15 #define SATELLITE_IMAGE_HEIGHT 15
const uint8_t SATELLITE_IMAGE[] PROGMEM = { const uint8_t SATELLITE_IMAGE[] PROGMEM = {0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0x20, 0x07, 0x70, 0x02,
0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0x20, 0x07, 0x70, 0x02, 0xF8, 0x00, 0xF8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54,
0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54, 0x0E, 0x52, 0x07, 0x48, 0x0E, 0x52, 0x07, 0x48, 0x02, 0x26, 0x00, 0x10, 0x00, 0x0E};
0x02, 0x26, 0x00, 0x10, 0x00, 0x0E
};
const const
#include "icon.xbm" #include "icon.xbm"

35
src/lock.cpp Normal file
View 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
View 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

View File

@@ -21,23 +21,22 @@
*/ */
#include "configuration.h"
#include "rom/rtc.h"
#include <driver/rtc_io.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include "BluetoothUtil.h" #include "BluetoothUtil.h"
#include "MeshBluetoothService.h"
#include "MeshService.h"
#include "GPS.h" #include "GPS.h"
#include "screen.h" #include "MeshBluetoothService.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "Periodic.h" #include "Periodic.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "esp32/pm.h" #include "esp32/pm.h"
#include "esp_pm.h" #include "esp_pm.h"
#include "MeshRadio.h" #include "rom/rtc.h"
#include "screen.h"
#include "sleep.h" #include "sleep.h"
#include "PowerFSM.h" #include <Wire.h>
#include <driver/rtc_io.h>
#ifdef T_BEAM_V10 #ifdef T_BEAM_V10
#include "axp20x.h" #include "axp20x.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;
@@ -62,31 +69,25 @@ void scanI2Cdevice(void)
{ {
byte err, addr; byte err, addr;
int nDevices = 0; int nDevices = 0;
for (addr = 1; addr < 127; addr++) for (addr = 1; addr < 127; addr++) {
{
Wire.beginTransmission(addr); Wire.beginTransmission(addr);
err = Wire.endTransmission(); err = Wire.endTransmission();
if (err == 0) if (err == 0) {
{
DEBUG_MSG("I2C device found at address 0x%x\n", addr); DEBUG_MSG("I2C device found at address 0x%x\n", addr);
nDevices++; nDevices++;
if (addr == SSD1306_ADDRESS) if (addr == SSD1306_ADDRESS) {
{
ssd1306_found = true; ssd1306_found = true;
DEBUG_MSG("ssd1306 display found\n"); DEBUG_MSG("ssd1306 display found\n");
} }
#ifdef T_BEAM_V10 #ifdef T_BEAM_V10
if (addr == AXP192_SLAVE_ADDRESS) if (addr == AXP192_SLAVE_ADDRESS) {
{
axp192_found = true; axp192_found = true;
DEBUG_MSG("axp192 PMU found\n"); DEBUG_MSG("axp192 PMU found\n");
} }
#endif #endif
} } else if (err == 4) {
else if (err == 4)
{
DEBUG_MSG("Unknow error at address 0x%x\n", addr); DEBUG_MSG("Unknow error at address 0x%x\n", addr);
} }
} }
@@ -100,20 +101,16 @@ void scanI2Cdevice(void)
* Init the power manager chip * Init the power manager chip
* *
* axp192 power * axp192 power
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
DCDC2 -> unused share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can not be turned off not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
LDO2 200mA -> LORA
LDO3 200mA -> GPS
*/ */
void axp192Init() void axp192Init()
{ {
#ifdef T_BEAM_V10 #ifdef T_BEAM_V10
if (axp192_found) if (axp192_found) {
{ if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS))
{
DEBUG_MSG("AXP192 Begin PASS\n"); DEBUG_MSG("AXP192 Begin PASS\n");
// axp.setChgLEDMode(LED_BLINK_4HZ); // axp.setChgLEDMode(LED_BLINK_4HZ);
@@ -162,26 +159,21 @@ void axp192Init()
#ifdef PMU_IRQ #ifdef PMU_IRQ
pinMode(PMU_IRQ, INPUT_PULLUP); pinMode(PMU_IRQ, INPUT_PULLUP);
attachInterrupt(PMU_IRQ, [] { attachInterrupt(
pmu_irq = true; PMU_IRQ, [] { pmu_irq = true; }, RISING);
},
RISING);
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1); axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
axp.enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ, 1); axp.enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ,
1);
axp.clearIRQ(); axp.clearIRQ();
#endif #endif
isCharging = axp.isChargeing() ? 1 : 0; isCharging = axp.isChargeing() ? 1 : 0;
isUSBPowered = axp.isVBUSPlug() ? 1 : 0; isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
} } else {
else
{
DEBUG_MSG("AXP192 Begin FAIL\n"); DEBUG_MSG("AXP192 Begin FAIL\n");
} }
} } else {
else
{
DEBUG_MSG("AXP192 not found\n"); DEBUG_MSG("AXP192 not found\n");
} }
#endif #endif
@@ -222,8 +214,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);
@@ -241,21 +231,24 @@ void setup()
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()
@@ -265,7 +258,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, xstr(APP_VERSION), xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr // Note: these callbacks might be coming in from a different thread.
BLEServer *serve = initBLE(
[](uint32_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
@@ -274,19 +274,15 @@ void initBluetooth()
void setBluetoothEnable(bool on) void setBluetoothEnable(bool on)
{ {
if (on != bluetoothOn) if (on != bluetoothOn) {
{
DEBUG_MSG("Setting bluetooth enable=%d\n", on); DEBUG_MSG("Setting bluetooth enable=%d\n", on);
bluetoothOn = on; bluetoothOn = on;
if (on) if (on) {
{
Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap()); Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap());
// ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) ); // ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
initBluetooth(); initBluetooth();
} } 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 stopMeshBluetoothService(); // Must do before shutting down bluetooth
deinitBLE(); deinitBLE();
@@ -345,11 +341,9 @@ void loop()
// service.radio.rf95.canSleep(); // service.radio.rf95.canSleep();
#ifdef T_BEAM_V10 #ifdef T_BEAM_V10
if (axp192_found) if (axp192_found) {
{
#ifdef PMU_IRQ #ifdef PMU_IRQ
if (pmu_irq) if (pmu_irq) {
{
pmu_irq = false; pmu_irq = false;
axp.readIRQ(); axp.readIRQ();
@@ -369,52 +363,39 @@ void loop()
#endif #endif
#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)) {
{ if (!wasPressed) { // just started a new press
if (!wasPressed)
{ // just started a new press
DEBUG_MSG("pressing\n"); DEBUG_MSG("pressing\n");
// doLightSleep(); // doLightSleep();
// 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);
} }
} } else if (wasPressed) {
else if (wasPressed)
{
// 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);
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons feel slow // FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons
// feel slow
msecstosleep = 10; msecstosleep = 10;
delay(msecstosleep); delay(msecstosleep);

View File

@@ -1,6 +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 isCharging;
extern bool isUSBPowered; extern bool isUSBPowered;
// Global Screen singleton.
extern meshtastic::Screen screen;

View File

@@ -1,10 +1,10 @@
#include <Arduino.h>
#include "configuration.h"
#include "mesh-pb-constants.h" #include "mesh-pb-constants.h"
#include <pb_encode.h>
#include <pb_decode.h>
#include <assert.h>
#include "FS.h" #include "FS.h"
#include "configuration.h"
#include <Arduino.h>
#include <assert.h>
#include <pb_decode.h>
#include <pb_encode.h>
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic /// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
/// returns the encoded packet size /// returns the encoded packet size
@@ -12,42 +12,33 @@ size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc
{ {
pb_ostream_t stream = pb_ostream_from_buffer(destbuf, destbufsize); pb_ostream_t stream = pb_ostream_from_buffer(destbuf, destbufsize);
if (!pb_encode(&stream, fields, src_struct)) if (!pb_encode(&stream, fields, src_struct)) {
{
DEBUG_MSG("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream)); DEBUG_MSG("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream));
assert(0); // FIXME - panic assert(0); // FIXME - panic
} } else {
else
{
return stream.bytes_written; return stream.bytes_written;
} }
} }
/// helper function for decoding a record as a protobuf, we will return false if the decoding failed /// helper function for decoding a record as a protobuf, we will return false if the decoding failed
bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msgdesc_t *fields, void *dest_struct) bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msgdesc_t *fields, void *dest_struct)
{ {
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, pb_msgdesc 0x%p\n", PB_GET_ERROR(&stream), fields);
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
return false; return false;
} } else {
else
{
return true; return true;
} }
} }
/// Read from an Arduino File /// Read from an Arduino File
bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count) bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count)
{ {
File *file = (File *)stream->state; File *file = (File *)stream->state;
bool status; bool status;
if (buf == NULL) if (buf == NULL) {
{
while (count-- && file->read() != EOF) while (count-- && file->read() != EOF)
; ;
return count == 0; return count == 0;
@@ -61,7 +52,6 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count)
return status; return status;
} }
/// Write to an arduino file /// Write to an arduino file
bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count) bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count)
{ {

View File

@@ -13,7 +13,6 @@
/// max number of nodes allowed in the mesh /// max number of nodes allowed in the mesh
#define MAX_NUM_NODES (member_size(DeviceState, node_db) / member_size(DeviceState, node_db[0])) #define MAX_NUM_NODES (member_size(DeviceState, node_db) / member_size(DeviceState, node_db[0]))
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic /// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
/// returns the encoded packet size /// returns the encoded packet size
size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc_t *fields, const void *src_struct); size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc_t *fields, const void *src_struct);

View File

@@ -34,7 +34,6 @@ typedef enum _ChannelSettings_ModemConfig {
/* 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];
@@ -53,6 +52,9 @@ typedef struct _MyNodeInfo {
char region[12]; char region[12];
char hw_model[12]; char hw_model[12];
char firmware_version[12]; char firmware_version[12];
uint32_t error_code;
uint32_t error_address;
uint32_t error_count;
} MyNodeInfo; } MyNodeInfo;
typedef struct _Position { typedef struct _Position {
@@ -173,11 +175,11 @@ typedef struct _ToRadio {
#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, "", "", "", 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}, false, MeshPacket_init_default, 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}, 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}}
@@ -186,18 +188,17 @@ typedef struct _ToRadio {
#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, "", "", "", 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}, false, MeshPacket_init_zero, 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}, 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
@@ -209,6 +210,9 @@ typedef struct _ToRadio {
#define MyNodeInfo_region_tag 4 #define MyNodeInfo_region_tag 4
#define MyNodeInfo_hw_model_tag 5 #define MyNodeInfo_hw_model_tag 5
#define MyNodeInfo_firmware_version_tag 6 #define MyNodeInfo_firmware_version_tag 6
#define MyNodeInfo_error_code_tag 7
#define MyNodeInfo_error_address_tag 8
#define MyNodeInfo_error_count_tag 9
#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
@@ -303,7 +307,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)
@@ -352,7 +355,10 @@ 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, region, 4) \
X(a, STATIC, SINGULAR, STRING, hw_model, 5) \ X(a, STATIC, SINGULAR, STRING, hw_model, 5) \
X(a, STATIC, SINGULAR, STRING, firmware_version, 6) X(a, STATIC, SINGULAR, STRING, firmware_version, 6) \
X(a, STATIC, SINGULAR, UINT32, error_code, 7) \
X(a, STATIC, SINGULAR, UINT32, error_address, 8) \
X(a, STATIC, SINGULAR, UINT32, error_count, 9)
#define MyNodeInfo_CALLBACK NULL #define MyNodeInfo_CALLBACK NULL
#define MyNodeInfo_DEFAULT NULL #define MyNodeInfo_DEFAULT NULL
@@ -421,12 +427,12 @@ extern const pb_msgdesc_t ToRadio_msg;
#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 155 #define NodeInfo_size 155
#define MyNodeInfo_size 63 #define MyNodeInfo_size 81
#define DeviceState_size 15064 #define DeviceState_size 15076
#define FromRadio_size 301 #define FromRadio_size 301
#define ToRadio_size 295 #define ToRadio_size 295

View File

@@ -20,54 +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 "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
@@ -78,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");
@@ -98,81 +75,44 @@ 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); // 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;
} }
@@ -182,21 +122,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++;
} }
@@ -206,8 +148,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;
@@ -216,10 +159,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))
@@ -228,18 +169,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
@@ -252,7 +194,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);
@@ -262,6 +204,9 @@ 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
{ {
@@ -273,10 +218,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;
@@ -295,7 +238,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);
} }
@@ -303,15 +248,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;
@@ -330,28 +275,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
if (state->currentFrame != prevFrame)
{ {
// We only advance our nodeIndex if the frame # has changed - because
// 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();
} }
@@ -379,14 +324,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)
@@ -394,23 +339,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;
@@ -418,8 +360,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);
@@ -431,7 +372,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);
@@ -450,32 +391,17 @@ void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
static char gpsStr[20]; static char gpsStr[20];
if (myNodeInfo.has_gps) if (myNodeInfo.has_gps)
snprintf(gpsStr, sizeof(gpsStr), "GPS %d%%", 75); // FIXME, use something based on hdop snprintf(gpsStr, sizeof(gpsStr), "GPS %d%%",
75); // FIXME, use something based on hdop
else else
gpsStr[0] = '\0'; // Just show emptystring gpsStr[0] = '\0'; // Just show emptystring
const char *fields[] = { const char *fields[] = {batStr, gpsStr, usersStr, channelStr, NULL};
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()
{ {
@@ -500,20 +426,21 @@ 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"); DEBUG_MSG("Turning on screen\n");
dispdev.displayOn(); dispdev.displayOn();
setPeriod(1); // redraw ASAP } else {
}
else {
DEBUG_MSG("Turning off screen\n"); DEBUG_MSG("Turning off screen\n");
dispdev.displayOff(); dispdev.displayOff();
} }
@@ -521,166 +448,133 @@ void Screen::setOn(bool on)
} }
} }
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 dispdev.resetOrientation();
// 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. // Initialising the UI will init the display too.
ui.init(); 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();
// Scroll buffer // Add frames.
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); dispdev.setLogBuffer(3, 32);
setOn(true); // update our screenOn bool #ifdef FLIP_SCREEN_VERTICALLY
dispdev.flipScreenVertically();
#ifdef BICOLOR_DISPLAY
dispdev.flipScreenVertically(); // looks better without this on lora32
#endif #endif
// dispdev.setFont(Custom_ArialMT_Plain_10); // Turn on the display.
handleSetOn(true);
ui.disableAutoTransition(); // we now require presses // On some ssd1306 clones, the first draw command is discarded, so draw it
#endif // 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);
} }
// 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;
setFrames();
}
}
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(); setFrames();
nodeDB.updateGUI = false; nodeDB.updateGUI = false;
nodeDB.updateTextMessage = false; nodeDB.updateTextMessage = false;
} }
} }
}
// This must be after we possibly do screen_set_frames() to ensure we draw the new data
ui.update(); ui.update();
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate, ui.getUiState()->frameState); // DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
// If we are scrolling we need to be called soon, otherwise just 1 fps (to save CPU) // ui.getUiState()->frameState); If we are scrolling we need to be called
// We also ask to be called twice as fast as we really need so that any rounding errors still result // soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
// with the correct framerate // 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::setFrames() 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)
@@ -691,28 +585,49 @@ void Screen::setFrames()
// 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();
@@ -723,3 +638,5 @@ void Screen::onPress()
ui.setTargetFPS(targetFramerate); ui.setTargetFPS(targetFramerate);
} }
} }
} // namespace meshtastic

View File

@@ -1,44 +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
void screen_print(const char * text, uint8_t x, uint8_t y, uint8_t alignment); {
/// 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;
/// Rebuilt our list of screens
void setFrames();
private: 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;
};
}; };
extern Screen screen; /// 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;
};
} // namespace meshtastic

View File

@@ -1,20 +1,18 @@
#include "configuration.h" #include "sleep.h"
#include "rom/rtc.h"
#include <driver/rtc_io.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include "BluetoothUtil.h" #include "BluetoothUtil.h"
#include "MeshBluetoothService.h"
#include "MeshService.h"
#include "GPS.h" #include "GPS.h"
#include "screen.h" #include "MeshBluetoothService.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "Periodic.h" #include "Periodic.h"
#include "configuration.h"
#include "esp32/pm.h" #include "esp32/pm.h"
#include "esp_pm.h" #include "esp_pm.h"
#include "MeshRadio.h"
#include "main.h" #include "main.h"
#include "sleep.h" #include "rom/rtc.h"
#include <Wire.h>
#include <driver/rtc_io.h>
#ifdef T_BEAM_V10 #ifdef T_BEAM_V10
#include "axp20x.h" #include "axp20x.h"
@@ -51,8 +49,7 @@ void setLed(bool ledOn)
#endif #endif
#ifdef T_BEAM_V10 #ifdef T_BEAM_V10
if (axp192_found) if (axp192_found) {
{
// blink the axp led // blink the axp led
axp.setChgLEDMode(ledOn ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF); axp.setChgLEDMode(ledOn ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF);
} }
@@ -78,8 +75,8 @@ void initDeepSleep()
Not using yet because we are using wake on all buttons being low Not using yet because we are using wake on all buttons being low
wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke
if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to support busted boards, assume button one was pressed if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to
wakeButtons = ((uint64_t)1) << buttons.gpios[0]; support busted boards, assume button one was pressed wakeButtons = ((uint64_t)1) << buttons.gpios[0];
*/ */
// If we booted because our timer ran out or the user pressed reset, send those as fake events // If we booted because our timer ran out or the user pressed reset, send those as fake events
@@ -128,8 +125,7 @@ void doDeepSleep(uint64_t msecToWake)
setLed(false); setLed(false);
#ifdef T_BEAM_V10 #ifdef T_BEAM_V10
if (axp192_found) if (axp192_found) {
{
// No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would // No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would
// leave floating input for the IRQ line // leave floating input for the IRQ line
@@ -159,10 +155,18 @@ void doDeepSleep(uint64_t msecToWake)
static const uint8_t rtcGpios[] = {/* 0, */ 2, static const uint8_t rtcGpios[] = {/* 0, */ 2,
/* 4, */ /* 4, */
#ifndef USE_JTAG #ifndef USE_JTAG
12, 13, /* 14, */ /* 15, */ 12,
13,
/* 14, */ /* 15, */
#endif #endif
/* 25, */ 26, /* 27, */ /* 25, */ 26, /* 27, */
32, 33, 34, 35, 36, 37, /* 38, */ 39}; 32,
33,
34,
35,
36,
37,
/* 38, */ 39};
for (int i = 0; i < sizeof(rtcGpios); i++) for (int i = 0; i < sizeof(rtcGpios); i++)
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
@@ -182,8 +186,8 @@ void doDeepSleep(uint64_t msecToWake)
#endif #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
// gpio_pullup_en((gpio_num_t)BUTTON_PIN); // just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
#endif #endif
@@ -223,7 +227,8 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
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 b=%d, rf95=%d, pmu=%d\n", digitalRead(BUTTON_PIN), digitalRead(DIO0_GPIO), digitalRead(PMU_IRQ)); // 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();
} }