mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-20 17:52:35 +00:00
Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc6b500029 | ||
|
|
887b1b5dcd | ||
|
|
62e333b235 | ||
|
|
bf68ad7cf5 | ||
|
|
6d9bdbb628 | ||
|
|
6782c2d3d1 | ||
|
|
b6f71ca1db | ||
|
|
caeea41867 | ||
|
|
bc17dd1a0f | ||
|
|
2a3175470b | ||
|
|
8ca6bbfb78 | ||
|
|
9777762052 | ||
|
|
da01f0ab7f | ||
|
|
1cd81208c0 | ||
|
|
2394075d94 | ||
|
|
de7b9877f9 | ||
|
|
ee27c15c2c | ||
|
|
076f8bd77b | ||
|
|
288363b3a6 | ||
|
|
48256d6e9e | ||
|
|
c007302564 | ||
|
|
0a9f7147f3 | ||
|
|
170f0693c6 | ||
|
|
d900509fbc | ||
|
|
8018c27dcd | ||
|
|
8df5ac9d3f | ||
|
|
c3196f47ef | ||
|
|
81a49d4e3c | ||
|
|
4f32c36db8 | ||
|
|
5db0bb3368 | ||
|
|
cbea36a151 | ||
|
|
49dea6d6bd | ||
|
|
7d4c6c7086 | ||
|
|
f41a77c46d | ||
|
|
5423f4e06c | ||
|
|
88f8bbe21e | ||
|
|
eda4862f0d | ||
|
|
7ca752cd32 | ||
|
|
c5ffebc498 | ||
|
|
baa12aa5b3 | ||
|
|
ccf3522ada | ||
|
|
d14cf5aa94 | ||
|
|
810429b54f | ||
|
|
915427c964 | ||
|
|
347484baaf | ||
|
|
bb6913a56a | ||
|
|
6cdaf8c600 | ||
|
|
c370eb4a88 | ||
|
|
8dc4492ba3 | ||
|
|
901cc536ef | ||
|
|
41c2732e4f | ||
|
|
2d12a363db | ||
|
|
28455f0056 | ||
|
|
5125126aec | ||
|
|
f1ca1ee3c0 | ||
|
|
dffb6c2f06 | ||
|
|
7f214ffbb0 | ||
|
|
e049eac38a | ||
|
|
338445d175 | ||
|
|
8eb492d356 | ||
|
|
a5341d766e | ||
|
|
c78142b235 | ||
|
|
9ebaa2b962 | ||
|
|
79498580b1 | ||
|
|
41901aed97 | ||
|
|
2729a513ab | ||
|
|
af046e7dbd | ||
|
|
0caf534b65 | ||
|
|
f650222e94 | ||
|
|
5c40378805 | ||
|
|
780b7e3628 | ||
|
|
83ae3c7714 | ||
|
|
25ebb9adb8 | ||
|
|
7f6a0e7ddc | ||
|
|
f62e6793c5 | ||
|
|
babd57ecde | ||
|
|
de196810a2 | ||
|
|
82fe55471d | ||
|
|
83726086a9 | ||
|
|
60d90c4533 | ||
|
|
9145945efa | ||
|
|
7b09fbe049 | ||
|
|
a90bab5455 | ||
|
|
3d9cc8a056 | ||
|
|
ff885ef215 | ||
|
|
eb4286b560 | ||
|
|
9c90de0f6f | ||
|
|
d7a1cef046 | ||
|
|
6a359e2124 | ||
|
|
ca75dcd64d | ||
|
|
aba05ba5ce | ||
|
|
99882a675b | ||
|
|
9c9347df23 | ||
|
|
b66856c53f | ||
|
|
285069703c | ||
|
|
d91ab5480f | ||
|
|
e3b74ece74 | ||
|
|
66557241f3 | ||
|
|
3c09c3e520 | ||
|
|
781077e799 | ||
|
|
22946b5e51 | ||
|
|
c0307cbcb0 | ||
|
|
6b568ab2fb | ||
|
|
67bad9a689 | ||
|
|
559a790286 | ||
|
|
08e5bd728b | ||
|
|
0cfeeba2e2 | ||
|
|
5007624ba5 | ||
|
|
bba4677915 | ||
|
|
ac969cdb26 | ||
|
|
1c3eff0ee5 | ||
|
|
cba9546a4d | ||
|
|
ceae60cf13 | ||
|
|
3de1607cea | ||
|
|
029b2f3139 | ||
|
|
ab6c97bfef | ||
|
|
a61b15e861 | ||
|
|
8c7aa07c70 | ||
|
|
6a402b13fa | ||
|
|
c30b570e16 | ||
|
|
9b25818a50 | ||
|
|
5311e44660 | ||
|
|
55dafcbecb | ||
|
|
178958c165 | ||
|
|
d7cf7e2eb4 | ||
|
|
fce8c16d52 | ||
|
|
b690868bb1 | ||
|
|
dec88a368b | ||
|
|
17394d8c1c | ||
|
|
a7da7cd32e | ||
|
|
f37dc9c776 | ||
|
|
d6658dbb2e | ||
|
|
05531b2684 | ||
|
|
8b1fb39ce1 | ||
|
|
da46d4ca0e | ||
|
|
047141eb34 | ||
|
|
cb1053850d | ||
|
|
7652331e8c | ||
|
|
12bf3795ea | ||
|
|
7f45184d90 | ||
|
|
829763af2c | ||
|
|
75806ee666 | ||
|
|
91ec29db03 | ||
|
|
d191b12801 | ||
|
|
e0d6456618 | ||
|
|
70eda2ee06 | ||
|
|
7c4eb3eddd | ||
|
|
661a75d796 | ||
|
|
b617010a46 | ||
|
|
5a70c45a3e | ||
|
|
cb2b36811a | ||
|
|
92edcb97ed | ||
|
|
7f1ec15cab | ||
|
|
1aa7451866 | ||
|
|
d5c46dc114 | ||
|
|
5bab16636d | ||
|
|
204f2c1a68 | ||
|
|
a5b7501a4e | ||
|
|
b0e2c81666 | ||
|
|
00ca351169 | ||
|
|
0415a3c369 | ||
|
|
4eb27b637d | ||
|
|
107b56a346 | ||
|
|
abdc4dfae8 | ||
|
|
014eea2f56 | ||
|
|
9b4ca95660 | ||
|
|
78ff9a8116 | ||
|
|
66b147fb31 | ||
|
|
c5df1bc885 | ||
|
|
00cf3a768e | ||
|
|
b6a3deb341 | ||
|
|
531f488fe8 | ||
|
|
d674aaaa29 | ||
|
|
7f6dc104f0 | ||
|
|
102085808f | ||
|
|
2645730329 | ||
|
|
6aa28f55dd | ||
|
|
1e86365167 | ||
|
|
62c20f8ab9 | ||
|
|
7706f65921 | ||
|
|
68490336b8 | ||
|
|
20ac8d71fd | ||
|
|
ec082b7c9a | ||
|
|
ece75d1d7f | ||
|
|
9e10ce487c | ||
|
|
d248c6be4b | ||
|
|
39b0a89821 | ||
|
|
d9f43d3e2f | ||
|
|
08c77caaa9 | ||
|
|
cfedc97cd0 | ||
|
|
cfad226b2b | ||
|
|
91b4cadb1b | ||
|
|
a23f327461 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -23,4 +23,4 @@ jobs:
|
||||
run: |
|
||||
pip install -U adafruit-nrfutil
|
||||
- name: Build
|
||||
run: platformio run -e tbeam -e heltec -e nrf52840dk -e rak815
|
||||
run: platformio run -e tbeam -e heltec -e lora-relay-v1
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,3 +17,5 @@ Thumbs.db
|
||||
.cproject
|
||||
.idea/*
|
||||
.vagrant
|
||||
|
||||
flash.uf2
|
||||
|
||||
@@ -25,11 +25,16 @@ We currently support three models of radios.
|
||||
- TTGO T-Beam (usually the recommended choice)
|
||||
- [T-Beam V1.1 w/ NEO-6M - special Meshtastic version](https://www.aliexpress.com/item/4001178678568.html) (Includes built-in OLED display and they have **preinstalled** the meshtastic software)
|
||||
- [T-Beam V1.1 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (slightly better GPS)
|
||||
- [T-Beam V1.1 w/ NEO-M8N /w SX1262](https://de.aliexpress.com/item/4001287221970.html) (slightly better GPS + LoRa)
|
||||
- board labels "TTGO T22_V1.1 20191212"
|
||||
- [T-Beam V0.7 w/ NEO-6M](https://www.aliexpress.com/item/4000574335430.html) (will work but **you must use the tbeam0.7 firmware ** - but the T-Beam V1.0 or later are better!)
|
||||
- board labels "TTGO T22_V07 20180711"
|
||||
- 3D printable cases
|
||||
- [T-Beam V0](https://www.thingiverse.com/thing:3773717)
|
||||
- [T-Beam V1](https://www.thingiverse.com/thing:3830711)
|
||||
- [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:3830711)
|
||||
- [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4587297)
|
||||
- Laser-cut cases
|
||||
- [T-Beam V1](https://www.thingiverse.com/thing:4552771)
|
||||
|
||||
- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS
|
||||
- version 2.1
|
||||
|
||||
@@ -8,7 +8,11 @@ COUNTRIES="US EU433 EU865 CN JP"
|
||||
#COUNTRIES=US
|
||||
#COUNTRIES=CN
|
||||
|
||||
BOARDS="ttgo-lora32-v2 ttgo-lora32-v1 tbeam heltec tbeam0.7"
|
||||
BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
|
||||
|
||||
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
|
||||
BOARDS_NRF52="lora-relay-v1"
|
||||
BOARDS="$BOARDS_ESP32 $BOARDS_NRF52"
|
||||
#BOARDS=tbeam
|
||||
|
||||
OUTDIR=release/latest
|
||||
@@ -23,20 +27,17 @@ rm -f $OUTDIR/bins/*
|
||||
|
||||
# build the named environment and copy the bins to the release directory
|
||||
function do_build {
|
||||
ENV_NAME=$1
|
||||
echo "Building for $ENV_NAME with $PLATFORMIO_BUILD_FLAGS"
|
||||
SRCBIN=.pio/build/$ENV_NAME/firmware.bin
|
||||
SRCELF=.pio/build/$ENV_NAME/firmware.elf
|
||||
rm -f $SRCBIN
|
||||
echo "Building for $BOARD with $PLATFORMIO_BUILD_FLAGS"
|
||||
rm -f .pio/build/$BOARD/firmware.*
|
||||
|
||||
# The shell vars the build tool expects to find
|
||||
export HW_VERSION="1.0-$COUNTRY"
|
||||
export APP_VERSION=$VERSION
|
||||
export COUNTRY
|
||||
|
||||
pio run --jobs 4 --environment $ENV_NAME # -v
|
||||
cp $SRCBIN $OUTDIR/bins/firmware-$ENV_NAME-$COUNTRY-$VERSION.bin
|
||||
cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf
|
||||
pio run --jobs 4 --environment $BOARD # -v
|
||||
SRCELF=.pio/build/$BOARD/firmware.elf
|
||||
cp $SRCELF $OUTDIR/elfs/firmware-$BOARD-$COUNTRY-$VERSION.elf
|
||||
}
|
||||
|
||||
# Make sure our submodules are current
|
||||
@@ -49,6 +50,18 @@ for COUNTRY in $COUNTRIES; do
|
||||
for BOARD in $BOARDS; do
|
||||
do_build $BOARD
|
||||
done
|
||||
|
||||
echo "Copying ESP32 bin files"
|
||||
for BOARD in $BOARDS_ESP32; do
|
||||
SRCBIN=.pio/build/$BOARD/firmware.bin
|
||||
cp $SRCBIN $OUTDIR/bins/firmware-$BOARD-$COUNTRY-$VERSION.bin
|
||||
done
|
||||
|
||||
echo "Generating NRF52 uf2 files"
|
||||
for BOARD in $BOARDS_NRF52; do
|
||||
SRCHEX=.pio/build/$BOARD/firmware.hex
|
||||
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/bins/firmware-$BOARD-$COUNTRY-$VERSION.uf2 -f 0xADA52840
|
||||
done
|
||||
done
|
||||
|
||||
# keep the bins in archive also
|
||||
|
||||
@@ -1,11 +1,45 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
# Usage info
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Usage: ${0##*/} [-h] [-p ESPTOOL_PORT] [-f FILENAME]
|
||||
Flash image file to device, but first erasing and writing system information"
|
||||
|
||||
FILENAME=$1
|
||||
-h Display this help and exit
|
||||
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
|
||||
-f FILENAME The .bin file to flash. Custom to your device type and region.
|
||||
EOF
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
while getopts ":h:p:f:" opt; do
|
||||
case "${opt}" in
|
||||
h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
p) export ESPTOOL_PORT=${OPTARG}
|
||||
;;
|
||||
f) FILENAME=${OPTARG}
|
||||
;;
|
||||
*)
|
||||
echo "Invalid flag."
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift "$((OPTIND-1))"
|
||||
|
||||
if [ -f "${FILENAME}" ]; then
|
||||
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}
|
||||
else
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
show_help
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,8 +1,43 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
# Usage info
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Usage: ${0##*/} [-h] [-p ESPTOOL_PORT] -f FILENAME
|
||||
Flash image file to device, leave existing system intact."
|
||||
|
||||
FILENAME=$1
|
||||
-h Display this help and exit
|
||||
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
|
||||
-f FILENAME The .bin file to flash. Custom to your device type and region.
|
||||
EOF
|
||||
}
|
||||
|
||||
echo "Trying to update $FILENAME"
|
||||
esptool.py --baud 921600 write_flash 0x10000 $FILENAME
|
||||
|
||||
while getopts ":h:p:f:" opt; do
|
||||
case "${opt}" in
|
||||
h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
p) export ESPTOOL_PORT=${OPTARG}
|
||||
;;
|
||||
f) FILENAME=${OPTARG}
|
||||
;;
|
||||
*)
|
||||
echo "Invalid flag."
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift "$((OPTIND-1))"
|
||||
|
||||
if [ -f "${FILENAME}" ]; then
|
||||
echo "Trying to flash update ${FILENAME}."
|
||||
esptool.py --baud 921600 write_flash 0x10000 ${FILENAME}
|
||||
else
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
show_help
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
314
bin/uf2conv.py
Executable file
314
bin/uf2conv.py
Executable file
@@ -0,0 +1,314 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import struct
|
||||
import subprocess
|
||||
import re
|
||||
import os
|
||||
import os.path
|
||||
import argparse
|
||||
|
||||
|
||||
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
|
||||
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
|
||||
UF2_MAGIC_END = 0x0AB16F30 # Ditto
|
||||
|
||||
families = {
|
||||
'SAMD21': 0x68ed2b88,
|
||||
'SAML21': 0x1851780a,
|
||||
'SAMD51': 0x55114460,
|
||||
'NRF52': 0x1b57745f,
|
||||
'STM32F0': 0x647824b6,
|
||||
'STM32F1': 0x5ee21072,
|
||||
'STM32F2': 0x5d1a0a2e,
|
||||
'STM32F3': 0x6b846188,
|
||||
'STM32F4': 0x57755a57,
|
||||
'STM32F7': 0x53b80f00,
|
||||
'STM32G0': 0x300f5633,
|
||||
'STM32G4': 0x4c71240a,
|
||||
'STM32H7': 0x6db66082,
|
||||
'STM32L0': 0x202e3a91,
|
||||
'STM32L1': 0x1e1f432d,
|
||||
'STM32L4': 0x00ff6919,
|
||||
'STM32L5': 0x04240bdf,
|
||||
'STM32WB': 0x70d16653,
|
||||
'STM32WL': 0x21460ff0,
|
||||
'ATMEGA32': 0x16573617,
|
||||
'MIMXRT10XX': 0x4FB2D5BD
|
||||
}
|
||||
|
||||
INFO_FILE = "/INFO_UF2.TXT"
|
||||
|
||||
appstartaddr = 0x2000
|
||||
familyid = 0x0
|
||||
|
||||
|
||||
def is_uf2(buf):
|
||||
w = struct.unpack("<II", buf[0:8])
|
||||
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
|
||||
|
||||
def is_hex(buf):
|
||||
try:
|
||||
w = buf[0:30].decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
if w[0] == ':' and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
|
||||
return True
|
||||
return False
|
||||
|
||||
def convert_from_uf2(buf):
|
||||
global appstartaddr
|
||||
numblocks = len(buf) // 512
|
||||
curraddr = None
|
||||
outp = b""
|
||||
for blockno in range(numblocks):
|
||||
ptr = blockno * 512
|
||||
block = buf[ptr:ptr + 512]
|
||||
hd = struct.unpack(b"<IIIIIIII", block[0:32])
|
||||
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
|
||||
print("Skipping block at " + ptr + "; bad magic")
|
||||
continue
|
||||
if hd[2] & 1:
|
||||
# NO-flash flag set; skip block
|
||||
continue
|
||||
datalen = hd[4]
|
||||
if datalen > 476:
|
||||
assert False, "Invalid UF2 data size at " + ptr
|
||||
newaddr = hd[3]
|
||||
if curraddr == None:
|
||||
appstartaddr = newaddr
|
||||
curraddr = newaddr
|
||||
padding = newaddr - curraddr
|
||||
if padding < 0:
|
||||
assert False, "Block out of order at " + ptr
|
||||
if padding > 10*1024*1024:
|
||||
assert False, "More than 10M of padding needed at " + ptr
|
||||
if padding % 4 != 0:
|
||||
assert False, "Non-word padding size at " + ptr
|
||||
while padding > 0:
|
||||
padding -= 4
|
||||
outp += b"\x00\x00\x00\x00"
|
||||
outp += block[32 : 32 + datalen]
|
||||
curraddr = newaddr + datalen
|
||||
return outp
|
||||
|
||||
def convert_to_carray(file_content):
|
||||
outp = "const unsigned char bindata[] __attribute__((aligned(16))) = {"
|
||||
for i in range(len(file_content)):
|
||||
if i % 16 == 0:
|
||||
outp += "\n"
|
||||
outp += "0x%02x, " % ord(file_content[i])
|
||||
outp += "\n};\n"
|
||||
return outp
|
||||
|
||||
def convert_to_uf2(file_content):
|
||||
global familyid
|
||||
datapadding = b""
|
||||
while len(datapadding) < 512 - 256 - 32 - 4:
|
||||
datapadding += b"\x00\x00\x00\x00"
|
||||
numblocks = (len(file_content) + 255) // 256
|
||||
outp = b""
|
||||
for blockno in range(numblocks):
|
||||
ptr = 256 * blockno
|
||||
chunk = file_content[ptr:ptr + 256]
|
||||
flags = 0x0
|
||||
if familyid:
|
||||
flags |= 0x2000
|
||||
hd = struct.pack(b"<IIIIIIII",
|
||||
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
||||
flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
|
||||
while len(chunk) < 256:
|
||||
chunk += b"\x00"
|
||||
block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
|
||||
assert len(block) == 512
|
||||
outp += block
|
||||
return outp
|
||||
|
||||
class Block:
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
self.bytes = bytearray(256)
|
||||
|
||||
def encode(self, blockno, numblocks):
|
||||
global familyid
|
||||
flags = 0x0
|
||||
if familyid:
|
||||
flags |= 0x2000
|
||||
hd = struct.pack("<IIIIIIII",
|
||||
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
||||
flags, self.addr, 256, blockno, numblocks, familyid)
|
||||
hd += self.bytes[0:256]
|
||||
while len(hd) < 512 - 4:
|
||||
hd += b"\x00"
|
||||
hd += struct.pack("<I", UF2_MAGIC_END)
|
||||
return hd
|
||||
|
||||
def convert_from_hex_to_uf2(buf):
|
||||
global appstartaddr
|
||||
appstartaddr = None
|
||||
upper = 0
|
||||
currblock = None
|
||||
blocks = []
|
||||
for line in buf.split('\n'):
|
||||
if line[0] != ":":
|
||||
continue
|
||||
i = 1
|
||||
rec = []
|
||||
while i < len(line) - 1:
|
||||
rec.append(int(line[i:i+2], 16))
|
||||
i += 2
|
||||
tp = rec[3]
|
||||
if tp == 4:
|
||||
upper = ((rec[4] << 8) | rec[5]) << 16
|
||||
elif tp == 2:
|
||||
upper = ((rec[4] << 8) | rec[5]) << 4
|
||||
assert (upper & 0xffff) == 0
|
||||
elif tp == 1:
|
||||
break
|
||||
elif tp == 0:
|
||||
addr = upper | (rec[1] << 8) | rec[2]
|
||||
if appstartaddr == None:
|
||||
appstartaddr = addr
|
||||
i = 4
|
||||
while i < len(rec) - 1:
|
||||
if not currblock or currblock.addr & ~0xff != addr & ~0xff:
|
||||
currblock = Block(addr & ~0xff)
|
||||
blocks.append(currblock)
|
||||
currblock.bytes[addr & 0xff] = rec[i]
|
||||
addr += 1
|
||||
i += 1
|
||||
numblocks = len(blocks)
|
||||
resfile = b""
|
||||
for i in range(0, numblocks):
|
||||
resfile += blocks[i].encode(i, numblocks)
|
||||
return resfile
|
||||
|
||||
def to_str(b):
|
||||
return b.decode("utf-8")
|
||||
|
||||
def get_drives():
|
||||
drives = []
|
||||
if sys.platform == "win32":
|
||||
r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
|
||||
"get", "DeviceID,", "VolumeName,",
|
||||
"FileSystem,", "DriveType"])
|
||||
for line in to_str(r).split('\n'):
|
||||
words = re.split('\s+', line)
|
||||
if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
|
||||
drives.append(words[0])
|
||||
else:
|
||||
rootpath = "/media"
|
||||
if sys.platform == "darwin":
|
||||
rootpath = "/Volumes"
|
||||
elif sys.platform == "linux":
|
||||
tmp = rootpath + "/" + os.environ["USER"]
|
||||
if os.path.isdir(tmp):
|
||||
rootpath = tmp
|
||||
for d in os.listdir(rootpath):
|
||||
drives.append(os.path.join(rootpath, d))
|
||||
|
||||
|
||||
def has_info(d):
|
||||
try:
|
||||
return os.path.isfile(d + INFO_FILE)
|
||||
except:
|
||||
return False
|
||||
|
||||
return list(filter(has_info, drives))
|
||||
|
||||
|
||||
def board_id(path):
|
||||
with open(path + INFO_FILE, mode='r') as file:
|
||||
file_content = file.read()
|
||||
return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)
|
||||
|
||||
|
||||
def list_drives():
|
||||
for d in get_drives():
|
||||
print(d, board_id(d))
|
||||
|
||||
|
||||
def write_file(name, buf):
|
||||
with open(name, "wb") as f:
|
||||
f.write(buf)
|
||||
print("Wrote %d bytes to %s" % (len(buf), name))
|
||||
|
||||
|
||||
def main():
|
||||
global appstartaddr, familyid
|
||||
def error(msg):
|
||||
print(msg)
|
||||
sys.exit(1)
|
||||
parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
|
||||
parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
|
||||
help='input file (HEX, BIN or UF2)')
|
||||
parser.add_argument('-b' , '--base', dest='base', type=str,
|
||||
default="0x2000",
|
||||
help='set base address of application for BIN format (default: 0x2000)')
|
||||
parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str,
|
||||
help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
|
||||
parser.add_argument('-d' , '--device', dest="device_path",
|
||||
help='select a device path to flash')
|
||||
parser.add_argument('-l' , '--list', action='store_true',
|
||||
help='list connected devices')
|
||||
parser.add_argument('-c' , '--convert', action='store_true',
|
||||
help='do not flash, just convert')
|
||||
parser.add_argument('-D' , '--deploy', action='store_true',
|
||||
help='just flash, do not convert')
|
||||
parser.add_argument('-f' , '--family', dest='family', type=str,
|
||||
default="0x0",
|
||||
help='specify familyID - number or name (default: 0x0)')
|
||||
parser.add_argument('-C' , '--carray', action='store_true',
|
||||
help='convert binary file to a C array, not UF2')
|
||||
args = parser.parse_args()
|
||||
appstartaddr = int(args.base, 0)
|
||||
|
||||
if args.family.upper() in families:
|
||||
familyid = families[args.family.upper()]
|
||||
else:
|
||||
try:
|
||||
familyid = int(args.family, 0)
|
||||
except ValueError:
|
||||
error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
|
||||
|
||||
if args.list:
|
||||
list_drives()
|
||||
else:
|
||||
if not args.input:
|
||||
error("Need input file")
|
||||
with open(args.input, mode='rb') as f:
|
||||
inpbuf = f.read()
|
||||
from_uf2 = is_uf2(inpbuf)
|
||||
ext = "uf2"
|
||||
if args.deploy:
|
||||
outbuf = inpbuf
|
||||
elif from_uf2:
|
||||
outbuf = convert_from_uf2(inpbuf)
|
||||
ext = "bin"
|
||||
elif is_hex(inpbuf):
|
||||
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
|
||||
elif args.carray:
|
||||
outbuf = convert_to_carray(inpbuf)
|
||||
ext = "h"
|
||||
else:
|
||||
outbuf = convert_to_uf2(inpbuf)
|
||||
print("Converting to %s, output size: %d, start address: 0x%x" %
|
||||
(ext, len(outbuf), appstartaddr))
|
||||
if args.convert or ext != "uf2":
|
||||
drives = []
|
||||
if args.output == None:
|
||||
args.output = "flash." + ext
|
||||
else:
|
||||
drives = get_drives()
|
||||
|
||||
if args.output:
|
||||
write_file(args.output, outbuf)
|
||||
else:
|
||||
if len(drives) == 0:
|
||||
error("No drive to deploy.")
|
||||
for d in drives:
|
||||
print("Flashing %s (%s)" % (d, board_id(d)))
|
||||
write_file(d + "/NEW.UF2", outbuf)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
4
bin/upload-to-bootloader.sh
Executable file
4
bin/upload-to-bootloader.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
|
||||
echo "Converting to uf2 for NRF52 Adafruit bootloader"
|
||||
bin/uf2conv.py .pio/build/lora-relay-v1/firmware.hex -f 0xADA52840
|
||||
# cp flash.uf2 /media/kevinh/FTH*BOOT/
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
export VERSION=0.7.11
|
||||
export VERSION=1.0.0
|
||||
46
boards/lora-relay-v1.json
Normal file
46
boards/lora-relay-v1.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V1 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0x4404"]],
|
||||
"usb_product": "LORA_RELAY",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "lora_relay_v1",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Meshtastic Lora Relay V1 (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink", "nrfjprog", "stlink"]
|
||||
},
|
||||
"url": "https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay",
|
||||
"vendor": "BigCorvus"
|
||||
}
|
||||
@@ -66,6 +66,8 @@ The link above will return older more stable releases. We would prefer if you jo
|
||||
|
||||
If you'd like to help with development, the source code is [on github](https://github.com/meshtastic/Meshtastic-Android).
|
||||
|
||||
The app is also distributed for Amazon Fire devices via the Amazon appstore: [](https://www.amazon.com/Geeksville-Industries-Meshtastic/dp/B08CY9394Q)
|
||||
|
||||
## Supported hardware
|
||||
|
||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4001178678568.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most people should buy the T-Beam and a 18650 battery (total cost less than \$35). Also, the version of the T-Beam we link to is shipped with Meshtastic **preinstalled** by TTGO, so you don't have to install it yourself.
|
||||
@@ -74,11 +76,14 @@ Make sure to buy the frequency range which is legal for your country. For the US
|
||||
|
||||
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
|
||||
|
||||
For a nice printable cases:
|
||||
|
||||
1. TTGO T-Beam V0 see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
|
||||
2. TTGO T_Beam V1 see this [design](https://www.thingiverse.com/thing:3830711) by [rwanrooy](https://www.thingiverse.com/rwanrooy) or this [remix](https://www.thingiverse.com/thing:3949330) by [8ung](https://www.thingiverse.com/8ung)
|
||||
3. Heltec Lora32 see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
|
||||
For a nice looking cases:
|
||||
- 3D printable cases
|
||||
1. TTGO T-Beam V0 see this [design](https://www.thingiverse.com/thing:3773717) by [bsiege](https://www.thingiverse.com/bsiege).
|
||||
2. TTGO T_Beam V1 (SMA) see this [design](https://www.thingiverse.com/thing:3830711) by [rwanrooy](https://www.thingiverse.com/rwanrooy) or this [remix](https://www.thingiverse.com/thing:3949330) by [8ung](https://www.thingiverse.com/8ung)
|
||||
3. TTGO T_Beam V1 (IPEX) see this [design](https://www.thingiverse.com/thing:4587297) by [drewsed](https://www.thingiverse.com/drewsed)
|
||||
4. Heltec Lora32 see this [design](https://www.thingiverse.com/thing:3125854) by [ornotermes](https://www.thingiverse.com/ornotermes).
|
||||
- Laser-cut cases
|
||||
1. TTGO T_Beam V1 (SMA) see this [design](https://www.thingiverse.com/thing:4552771) by [jefish](https://www.thingiverse.com/jefish)
|
||||
|
||||
# IMPORTANT DISCLAIMERS AND FAQ
|
||||
|
||||
|
||||
BIN
docs/hardware/DS_SX1261-2_V1.2.pdf
Normal file
BIN
docs/hardware/DS_SX1261-2_V1.2.pdf
Normal file
Binary file not shown.
BIN
docs/hardware/T-SX1262.pdf
Normal file
BIN
docs/hardware/T-SX1262.pdf
Normal file
Binary file not shown.
35
docs/hardware/corvus.md
Normal file
35
docs/hardware/corvus.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Notes on @BigCorvus boards
|
||||
|
||||
## Board version 1.1
|
||||
|
||||
variant name lora_relay_v1
|
||||
|
||||
### Remaining TODOs
|
||||
|
||||
- power hold for the ST7735
|
||||
- look at example sketch
|
||||
- turn on xmit boost
|
||||
|
||||
## Recommendations for future boards
|
||||
|
||||
@BigCorvus your board is **really** nice. Here's some ideas for the future:
|
||||
|
||||
- make the SWDIO header more standard (the small ARM 2x5 micro footprint?) or at least througholes so it is easy to solder a header
|
||||
|
||||
## How to program bootloader
|
||||
|
||||
Download from here: https://github.com/adafruit/Adafruit_nRF52_Bootloader/releases
|
||||
|
||||
```
|
||||
nrfjprog -f nrf52 --eraseall
|
||||
Erasing user available code and UICR flash areas.
|
||||
Applying system reset.
|
||||
|
||||
nrfjprog -f nrf52 --program feather_nrf52840_express_bootloader-0.3.2_s140_6.1.1.hex
|
||||
Parsing hex file.
|
||||
Reading flash area to program to guarantee it is erased.
|
||||
Checking that the area to write is not protected.
|
||||
Programming device.
|
||||
```
|
||||
|
||||
Then reboot the board, if all went well it now shows up as a mountable filesystem on your USB bus.
|
||||
@@ -2,14 +2,20 @@
|
||||
|
||||
You probably don't care about this section - skip to the next one.
|
||||
|
||||
- brf52 ble
|
||||
- update protocol description per cyclomies
|
||||
- esp32 pairing
|
||||
- update faq with antennas https://meshtastic.discourse.group/t/range-test-ideas-requested/738/2
|
||||
- update faq on recommended android version and phones
|
||||
- add help link inside the app, reference a page on the wiki
|
||||
- turn on amazon reviews support
|
||||
- add a tablet layout (with map next to messages) in the android app
|
||||
Nimble tasks:
|
||||
|
||||
- readerror.txt stress test bug
|
||||
- started RPA long test, jul 22 6pm
|
||||
- implement nimble software update api
|
||||
- update to latest bins, test OTA again (measure times) and then checkin bins
|
||||
- do alpha release
|
||||
|
||||
* update protocol description per cyclomies email thread
|
||||
* update faq with antennas https://meshtastic.discourse.group/t/range-test-ideas-requested/738/2
|
||||
* update faq on recommended android version and phones
|
||||
* add help link inside the app, reference a page on the wiki
|
||||
* turn on amazon reviews support
|
||||
* add a tablet layout (with map next to messages) in the android app
|
||||
|
||||
# Medium priority
|
||||
|
||||
@@ -28,6 +34,10 @@ Items to complete before 1.0.
|
||||
|
||||
Items after the first final candidate release.
|
||||
|
||||
- implement nimble battery level service
|
||||
- Nimble implement device info service remaining fields (hw version etc)
|
||||
- Turn on RPA addresses for the device side in Nimble
|
||||
- Try to teardown less of the Nimble protocol stack across sleep
|
||||
- dynamic frequency scaling could save a lot of power on ESP32, but it seems to corrupt uart (even with ref_tick set correctly)
|
||||
- Change back to using a fixed sized MemoryPool rather than MemoryDynamic (see bug #149)
|
||||
- scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels)
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
# Build instructions
|
||||
|
||||
This project uses the simple PlatformIO build system. You can use the IDE, but for brevity
|
||||
in these instructions I describe use of their command line tool.
|
||||
This project uses the simple PlatformIO build system. PlatformIO is an extension to Microsoft VSCode.
|
||||
|
||||
1. Purchase a suitable radio (see above)
|
||||
## GUI
|
||||
1. Purchase a suitable [radio](https://github.com/meshtastic/Meshtastic-device/wiki/Hardware-Information).
|
||||
2. Install [Python](https://www.python.org/downloads/).
|
||||
3. Install [Git](https://git-scm.com/downloads).
|
||||
4. Reboot your computer.
|
||||
5. Install [PlatformIO](https://platformio.org/platformio-ide).
|
||||
6. Click the PlatformIO icon on the side bar. 
|
||||
7. Under `Quick Access, Miscellaneous, Clone Git Project` enter the URL of the Meshtastic repo found [here](https://github.com/meshtastic/Meshtastic-device). 
|
||||
8. Select a file location to save the repo.
|
||||
9. Once loaded, open the `platformio.ini` file.
|
||||
10. At the line `default_envs` you can change it to the board type you are building for ie. `tlora-v2, tlora-v1, tlora-v2-1-1.6, tbeam, heltec, tbeam0.7` (boards are listed further down in the file).
|
||||
11. The hardware can be configured for different countries by adding a definition to the `configuration.h` file. `#define HW_VERSION_US` or `HW_VERSION_EU433, HW_VERSION_EU865, HW_VERSION_CN, HW_VERSION_JP`. Other country settings can be found in `MeshRadio.h`. The default is `HW_VERSION_US`.
|
||||
12. Click the PlatformIO icon on the side bar. Under `Project Tasks` you can now build or upload.
|
||||
|
||||
Note - To get a clean build you may have to delete the auto-generated file `./.vscode/c_cpp_properties.json`, close and re-open Visual Studio and WAIT until the file is auto-generated before compiling again.
|
||||
|
||||
## Command Line
|
||||
1. Purchase a suitable [radio](https://github.com/meshtastic/Meshtastic-device/wiki/Hardware-Information).
|
||||
2. Install [PlatformIO](https://platformio.org/platformio-ide)
|
||||
3. Download this git repo and cd into it
|
||||
3. Download this git repo and cd into it:
|
||||
|
||||
```
|
||||
git clone https://github.com/meshtastic/Meshtastic-device.git
|
||||
cd Meshtastic-device
|
||||
```
|
||||
4. Run `git submodule update --init --recursive` to pull in dependencies this project needs.
|
||||
5. If you are outside the USA, run "export COUNTRY=EU865" (or whatever) to set the correct frequency range for your country. Options are provided for `EU433`, `EU865`, `CN`, `JP` and `US` (default). Pull-requests eagerly accepted for other countries.
|
||||
6. Plug the radio into your USB port
|
||||
7. Type `pio run --environment XXX -t upload` (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either `tbeam`, `heltec`, `ttgo-lora32-v1`, `ttgo-lora32-v2`).
|
||||
7. Type `pio run --environment XXX -t upload` (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either `tlora-v2, tlora-v1, tlora-v2-1-1.6, tbeam, heltec, tbeam0.7`).
|
||||
8. 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
|
||||
|
||||
@@ -12,5 +12,12 @@ you'll automatically get our fixed libraries.
|
||||
https://docs.espressif.com/projects/esp-idf/en/release-v3.3/get-started/linux-setup.html
|
||||
kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder\$ python /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/esp-idf/components/esptool*py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/build/bootloader/bootloader.bin
|
||||
cp -a out/tools/sdk/* components/arduino/tools/sdk
|
||||
cp -ar components/arduino/* ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/
|
||||
cp -ar components/arduino/* ~/.platformio/packages/framework-arduinoespressif32
|
||||
|
||||
/// @src-fba9d33740f719f712e9f8b07da6ea13/
|
||||
|
||||
or
|
||||
|
||||
cp -ar out/tools/sdk/* ~/.platformio/packages/framework-arduinoespressif32/tools/sdk
|
||||
|
||||
```
|
||||
|
||||
@@ -1,5 +1,75 @@
|
||||
# Mesh broadcast algorithm
|
||||
|
||||
## Current algorithm
|
||||
|
||||
The routing protocol for Meshtastic is really quite simple (and suboptimal). It is heavily influenced by the mesh routing algorithm used in [Radiohead](https://www.airspayce.com/mikem/arduino/RadioHead/) (which was used in very early versions of this project). It has four conceptual layers.
|
||||
|
||||
### A note about protocol buffers
|
||||
|
||||
Because we want our devices to work across various vendors and implementations, we use [Protocol Buffers](https://github.com/meshtastic/Meshtastic-protobufs) pervasively. For information on how the protocol buffers are used wrt API clients see [sw-design](sw-design.md), for purposes of this document you mostly only
|
||||
need to consider the MeshPacket and Subpacket message types.
|
||||
|
||||
### Layer 1: Non reliable zero hop messaging
|
||||
|
||||
This layer is conventional non-reliable lora packet transmission. The transmitted packet has the following representation on the ether:
|
||||
|
||||
- A 32 bit LORA preamble (to allow receiving radios to synchronize clocks and start framing). We use a longer than minimum (8 bit) preamble to maximize the amount of time the LORA receivers can stay asleep, which dramatically lowers power consumption.
|
||||
|
||||
After the preamble the 16 byte packet header is transmitted. This header is described directly by the PacketHeader class in the C++ source code. But indirectly it matches the first portion of the "MeshPacket" protobuf definition. But notably: this portion of the packet is sent directly as the following 16 bytes (rather than using the protobuf encoding). We do this to both save airtime and to allow receiving radio hardware the option of filtering packets before even waking the main CPU.
|
||||
|
||||
- to (4 bytes): the unique NodeId of the destination (or 0xffffffff for NodeNum_BROADCAST)
|
||||
- from (4 bytes): the unique NodeId of the sender)
|
||||
- id (4 bytes): the unique (wrt the sending node only) packet ID number for this packet. We use a large (32 bit) packet ID to ensure there is enough unique state to protect any encrypted payload from attack.
|
||||
- flags (4 bytes): Only a few bits are are currently used - 3 bits for for the "HopLimit" (see below) and 1 bit for "WantAck"
|
||||
|
||||
After the packet header the actual packet is placed onto the the wire. These bytes are merely the encrypted packed protobuf encoding of the SubPacket protobuf. A full description of our encryption is available in [crypto](crypto.md). It is worth noting that only this SubPacket is encrypted, headers are not. Which leaves open the option of eventually allowing nodes to route packets without knowing the keys used to encrypt.
|
||||
|
||||
NodeIds are constructed from the bottom four bytes of the macaddr of the bluetooth address. Because the OUI is assigned by the IEEE and we currently only support a few CPU manufacturers, the upper byte is defacto guaranteed unique for each vendor. The bottom 3 bytes are guaranteed unique by that vendor.
|
||||
|
||||
To prevent collisions all transmitters will listen before attempting to send. If they hear some other node transmitting, they will reattempt transmission in x milliseconds. This retransmission delay is random between FIXME and FIXME (these two numbers are currently hardwired, but really should be scaled based on expected packet transmission time at current channel settings).
|
||||
|
||||
### Layer 2: Reliable zero hop messaging
|
||||
|
||||
This layer adds reliable messaging between the node and its immediate neighbors (only).
|
||||
|
||||
The default messaging provided by layer-1 is extended by setting the "want-ack" flag in the MeshPacket protobuf. If want-ack is set the following documentation from mesh.proto applies:
|
||||
|
||||
"""This packet is being sent as a reliable message, we would prefer it to arrive
|
||||
at the destination. We would like to receive a ack packet in response.
|
||||
|
||||
Broadcasts messages treat this flag specially: Since acks for broadcasts would
|
||||
rapidly flood the channel, the normal ack behavior is suppressed. Instead,
|
||||
the original sender listens to see if at least one node is rebroadcasting this
|
||||
packet (because naive flooding algorithm). If it hears that the odds (given
|
||||
typical LoRa topologies) the odds are very high that every node should
|
||||
eventually receive the message. So FloodingRouter.cpp generates an implicit
|
||||
ack which is delivered to the original sender. If after some time we don't
|
||||
hear anyone rebroadcast our packet, we will timeout and retransmit, using the
|
||||
regular resend logic."""
|
||||
|
||||
If a transmitting node does not receive an ACK (or a NAK) packet within FIXME milliseconds, it will use layer-1 to attempt a retransmission of the sent packet. A reliable packet (at this 'zero hop' level) will be resent a maximum of three times. If no ack or nak has been received by then the local node will internally generate a nak (either for local consumption or use by higher layers of the protocol).
|
||||
|
||||
### Layer 3: (Naive) flooding for multi-hop messaging
|
||||
|
||||
Given our use-case for the initial release, most of our protocol is built around [flooding](<https://en.wikipedia.org/wiki/Flooding_(computer_networking)>). The implementation is currently 'naive' - i.e. it doesn't try to optimize flooding other than abandoning retransmission once we've seen a nearby receiver has acked the packet. Therefore, for each source packet up to N retransmissions might occur (if there are N nodes in the mesh).
|
||||
|
||||
Each node in the mesh, if it sees a packet on the ether with HopLimit set to a value other than zero, it will decrement that HopLimit and attempt retransmission on behalf of the original sending node.
|
||||
|
||||
### Layer 4: DSR for multi-hop unicast messaging
|
||||
|
||||
This layer is not yet fully implemented (and not yet used). But eventually (if we stay with our own transport rather than switching to QMesh or Reticulum)
|
||||
we will use conventional DSR for unicast messaging. Currently (even when not requiring 'broadcasts') we send any multi-hop unicasts as 'broadcasts' so that we can
|
||||
leverage our (functional) flooding implementation. This is suboptimal but it is a very rare use-case, because the odds are high that most nodes (given our small networks and 'hiking' use case) are within a very small number of hops. When any node witnesses an ack for a packet, it will realize that it can abandon its own
|
||||
broadcast attempt for that packet.
|
||||
|
||||
## Misc notes on remaining tasks
|
||||
|
||||
This section is currently poorly formatted, it is mostly a mere set of todo lists and notes for @geeksville during his initial development. After release 1.0 ideas for future optimization include:
|
||||
|
||||
- Make flood-routing less naive (because we have GPS and radio signal strength as heuristics to avoid redundant retransmissions)
|
||||
- If nodes have been user marked as 'routers', preferentially do flooding via those nodes
|
||||
- Fully implement DSR to improve unicast efficiency (or switch to QMesh/Reticulum as these projects mature)
|
||||
|
||||
great source of papers and class notes: http://www.cs.jhu.edu/~cs647/
|
||||
|
||||
flood routing improvements
|
||||
@@ -146,23 +216,3 @@ look into the literature for this idea specifically.
|
||||
build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db.
|
||||
- construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here?
|
||||
- possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database.
|
||||
|
||||
# Old notes
|
||||
|
||||
FIXME, merge into the above:
|
||||
|
||||
good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept
|
||||
|
||||
interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf
|
||||
It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728
|
||||
https://en.wikipedia.org/wiki/Dynamic_Source_Routing
|
||||
|
||||
broadcast solution:
|
||||
Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent)
|
||||
Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set
|
||||
|
||||
compare to db sync algorithm?
|
||||
|
||||
what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies.
|
||||
|
||||
If Y were to die, at least the neighbor nodes of Y would have their last known position of Y.
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# NRF52 TODO
|
||||
|
||||
- Possibly switch from softdevice to Apachy Newt: https://github.com/espressif/esp-nimble
|
||||
https://github.com/apache/mynewt-core - use nimble BLE on both ESP32 and NRF52
|
||||
|
||||
## RAK815
|
||||
|
||||
TODO:
|
||||
|
||||
- shrink soft device RAM usage
|
||||
- get nrf52832 working again (currently OOM)
|
||||
- i2c gps comms not quite right
|
||||
- ble: AdafruitBluefruit::begin - adafruit_ble_task was assigned an invalid stack pointer. out of memory?
|
||||
- measure power draw
|
||||
|
||||
148
docs/software/read-error.txt
Normal file
148
docs/software/read-error.txt
Normal file
@@ -0,0 +1,148 @@
|
||||
nimble stress test error (private notes for @geeksville)
|
||||
|
||||
findings:
|
||||
only happens when stress testing multiple sleepwake cycles?
|
||||
failed packets all have initial mbuflen of zero (should be 1)
|
||||
restarting the connection on phone sometimes (but not always) fixes it (is the larger config nonce pushing packet size up too large?)
|
||||
- packets >= 79 bytes (FromRadio) cause INVALID_OFFSET (7) gatt errors to be sent to the app
|
||||
- some packets are missing
|
||||
|
||||
theory:
|
||||
some sort of leak in mbuf storage during unfortunately timed sleep shutdowns
|
||||
|
||||
|
||||
device side
|
||||
|
||||
|
||||
connection updated; status=0 handle=0 our_ota_addr_type=0 our_ota_addr=00:24:62:ab:dd:df
|
||||
our_id_addr_type=0 our_id_addr=00:24:62:ab:dd:df
|
||||
peer_ota_addr_type=0 peer_ota_addr=00:7c:d9:5c:ba:2e
|
||||
peer_id_addr_type=0 peer_id_addr=00:7c:d9:5c:ba:2e
|
||||
conn_itvl=36 conn_latency=0 supervision_timeout=500 encrypted=1 authenticated=1 bonded=1
|
||||
|
||||
BLE fromRadio called
|
||||
getFromRadio, !available
|
||||
toRadioWriteCb data 0x3ffc3d72, len 4
|
||||
Trigger powerFSM 9
|
||||
Transition powerFSM transition=Contact from phone, from=DARK to=DARK
|
||||
Client wants config, nonce=6864
|
||||
Reset nodeinfo read pointer
|
||||
toRadioWriteCb data 0x3ffc3d72, len 4
|
||||
Trigger powerFSM 9
|
||||
Transition powerFSM transition=Contact from phone, from=DARK to=DARK
|
||||
Client wants config, nonce=6863
|
||||
Reset nodeinfo read pointer
|
||||
BLE fromRadio called
|
||||
getFromRadio, state=2
|
||||
encoding toPhone packet to phone variant=3, 50 bytes
|
||||
BLE fromRadio called
|
||||
getFromRadio, state=3
|
||||
encoding toPhone packet to phone variant=6, 83 bytes
|
||||
BLE fromRadio called
|
||||
getFromRadio, state=4
|
||||
Sending nodeinfo: num=0xabdddf38, lastseen=1595606850, id=!2462abdddf38, name=Bob b
|
||||
encoding toPhone packet to phone variant=4, 67 bytes
|
||||
BLE fromRadio called
|
||||
getFromRadio, state=4
|
||||
Sending nodeinfo: num=0x28b200b4, lastseen=1595606804, id=!246f28b200b4, name=Unknown 00b4
|
||||
encoding toPhone packet to phone variant=4, 80 bytes
|
||||
BLE fromRadio called
|
||||
getFromRadio, state=4
|
||||
Sending nodeinfo: num=0xabf84098, lastseen=1593680756, id=!2462abf84098, name=bx n
|
||||
encoding toPhone packet to phone variant=4, 72 bytes
|
||||
BLE fromRadio called
|
||||
getFromRadio, state=4
|
||||
Sending nodeinfo: num=0x83f0d8e5, lastseen=1594686931, id=!e8e383f0d8e5, name=Unknown d8e5
|
||||
encoding toPhone packet to phone variant=4, 64 bytes
|
||||
BLE fromRadio called
|
||||
getFromRadio, state=4
|
||||
Sending nodeinfo: num=0xd1dc7764, lastseen=1595602082, id=!f008d1dc7764, name=dg
|
||||
encoding toPhone packet to phone variant=4, 52 bytes
|
||||
BLE fromRadio called
|
||||
getFromRadio, state=4
|
||||
Sending nodeinfo: num=0xd1dc7828, lastseen=1595598298, id=!f008d1dc7828, name=ryan
|
||||
encoding toPhone packet to phone variant=4, 54 bytes
|
||||
BLE fromRadio called
|
||||
getFromRadio, state=4
|
||||
Done sending nodeinfos
|
||||
getFromRadio, state=5
|
||||
|
||||
|
||||
|
||||
phone side
|
||||
|
||||
2020-07-24 09:11:00.642 6478-6545/com.geeksville.mesh W/com.geeksville.mesh.service.BluetoothInterface: Attempting reconnect
|
||||
2020-07-24 09:11:00.642 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Enqueuing work: connect
|
||||
2020-07-24 09:11:00.642 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth$BluetoothContinuation: Starting work: connect
|
||||
2020-07-24 09:11:00.643 6478-6545/com.geeksville.mesh D/BluetoothGatt: connect() - device: 24:62:AB:DD:DF:3A, auto: false
|
||||
2020-07-24 09:11:00.643 6478-6545/com.geeksville.mesh D/BluetoothGatt: registerApp()
|
||||
2020-07-24 09:11:00.643 6478-6545/com.geeksville.mesh D/BluetoothGatt: registerApp() - UUID=026baf7f-d2de-43f1-961f-4e00e04c6fbb
|
||||
2020-07-24 09:11:00.645 6478-27868/com.geeksville.mesh D/BluetoothGatt: onClientRegistered() - status=0 clientIf=4
|
||||
2020-07-24 09:11:01.022 6478-27868/com.geeksville.mesh D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=4 device=24:62:AB:DD:DF:3A
|
||||
2020-07-24 09:11:01.022 6478-27868/com.geeksville.mesh I/com.geeksville.mesh.service.SafeBluetooth: new bluetooth connection state 2, status 0
|
||||
2020-07-24 09:11:01.023 6478-27868/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: work connect is completed, resuming status=0, res=kotlin.Unit
|
||||
2020-07-24 09:11:01.023 6478-6545/com.geeksville.mesh I/com.geeksville.mesh.service.BluetoothInterface: Connected to radio!
|
||||
2020-07-24 09:11:01.023 6478-6545/com.geeksville.mesh D/BluetoothGatt: refresh() - device: 24:62:AB:DD:DF:3A
|
||||
2020-07-24 09:11:01.526 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Enqueuing work: discover
|
||||
2020-07-24 09:11:01.526 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth$BluetoothContinuation: Starting work: discover
|
||||
2020-07-24 09:11:01.526 6478-6545/com.geeksville.mesh D/BluetoothGatt: discoverServices() - device: 24:62:AB:DD:DF:3A
|
||||
2020-07-24 09:11:01.829 6478-27868/com.geeksville.mesh D/BluetoothGatt: onConnectionUpdated() - Device=24:62:AB:DD:DF:3A interval=6 latency=0 timeout=500 status=0
|
||||
2020-07-24 09:11:02.008 6478-27868/com.geeksville.mesh D/BluetoothGatt: onSearchComplete() = Device=24:62:AB:DD:DF:3A Status=0
|
||||
2020-07-24 09:11:02.009 6478-27868/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: work discover is completed, resuming status=0, res=kotlin.Unit
|
||||
2020-07-24 09:11:02.009 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.BluetoothInterface: Discovered services!
|
||||
2020-07-24 09:11:02.095 6478-27868/com.geeksville.mesh D/BluetoothGatt: onConnectionUpdated() - Device=24:62:AB:DD:DF:3A interval=36 latency=0 timeout=500 status=0
|
||||
2020-07-24 09:11:03.010 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.RadioInterfaceService: Broadcasting connection=true
|
||||
2020-07-24 09:11:03.012 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Enqueuing work: readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
2020-07-24 09:11:03.012 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth$BluetoothContinuation: Starting work: readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
2020-07-24 09:11:03.012 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: Received broadcast com.geeksville.mesh.CONNECT_CHANGED
|
||||
2020-07-24 09:11:03.012 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: onConnectionChanged=CONNECTED
|
||||
2020-07-24 09:11:03.012 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: Starting config nonce=6878
|
||||
2020-07-24 09:11:03.013 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.BluetoothInterface: queuing 4 bytes to f75c76d2-129e-4dad-a1dd-7866124401e7 *** sending start config
|
||||
2020-07-24 09:11:03.013 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Enqueuing work: writeC f75c76d2-129e-4dad-a1dd-7866124401e7
|
||||
2020-07-24 09:11:03.015 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: Received broadcast com.geeksville.mesh.CONNECT_CHANGED
|
||||
2020-07-24 09:11:03.015 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: onConnectionChanged=CONNECTED
|
||||
2020-07-24 09:11:03.015 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: Starting config nonce=6877
|
||||
2020-07-24 09:11:03.016 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: device sleep timeout cancelled
|
||||
2020-07-24 09:11:03.016 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.BluetoothInterface: queuing 4 bytes to f75c76d2-129e-4dad-a1dd-7866124401e7
|
||||
2020-07-24 09:11:03.016 6478-6545/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Enqueuing work: writeC f75c76d2-129e-4dad-a1dd-7866124401e7
|
||||
2020-07-24 09:11:03.130 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth$BluetoothContinuation: Starting work: writeC f75c76d2-129e-4dad-a1dd-7866124401e7
|
||||
2020-07-24 09:11:03.132 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: work readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 is completed, resuming status=0, res=android.bluetooth.BluetoothGattCharacteristic@556a315
|
||||
2020-07-24 09:11:03.132 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.BluetoothInterface: Done reading from radio, fromradio is empty
|
||||
2020-07-24 09:11:03.132 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: starting setNotify(ed9da18c-a800-4f66-a670-aa7547e34453, true) *** start notify
|
||||
2020-07-24 09:11:03.132 6478-19966/com.geeksville.mesh D/BluetoothGatt: setCharacteristicNotification() - uuid: ed9da18c-a800-4f66-a670-aa7547e34453 enable: true
|
||||
2020-07-24 09:11:03.133 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Enqueuing work: writeD
|
||||
2020-07-24 09:11:03.220 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth$BluetoothContinuation: Starting work: writeC f75c76d2-129e-4dad-a1dd-7866124401e7
|
||||
2020-07-24 09:11:03.221 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: work writeC f75c76d2-129e-4dad-a1dd-7866124401e7 is completed, resuming status=0, res=android.bluetooth.BluetoothGattCharacteristic@2d2062a
|
||||
2020-07-24 09:11:03.221 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.BluetoothInterface: write of 4 bytes completed
|
||||
2020-07-24 09:11:03.221 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Enqueuing work: readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
2020-07-24 09:11:03.310 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth$BluetoothContinuation: Starting work: writeD
|
||||
2020-07-24 09:11:03.311 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: work writeC f75c76d2-129e-4dad-a1dd-7866124401e7 is completed, resuming status=0, res=android.bluetooth.BluetoothGattCharacteristic@2d2062a
|
||||
2020-07-24 09:11:03.311 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.BluetoothInterface: write of 4 bytes completed
|
||||
2020-07-24 09:11:03.400 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth$BluetoothContinuation: Starting work: readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
2020-07-24 09:11:03.402 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: work writeD is completed, resuming status=0, res=android.bluetooth.BluetoothGattDescriptor@fc99c1b
|
||||
2020-07-24 09:11:03.402 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Notify enable=true completed
|
||||
2020-07-24 09:11:03.769 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: work readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 is completed, resuming status=0, res=android.bluetooth.BluetoothGattCharacteristic@556a315
|
||||
2020-07-24 09:11:03.769 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.BluetoothInterface: Received 80 bytes from radio **** received an 80 byte fromradio. Why did we miss three previous reads?
|
||||
2020-07-24 09:11:03.774 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Enqueuing work: readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
2020-07-24 09:11:03.774 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth$BluetoothContinuation: Starting work: readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
2020-07-24 09:11:03.774 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: Received broadcast com.geeksville.mesh.RECEIVE_FROMRADIO
|
||||
2020-07-24 09:11:03.776 6478-6478/com.geeksville.mesh E/com.geeksville.mesh.service.MeshService: Invalid Protobuf from radio, len=80 (exception Protocol message had invalid UTF-8.)
|
||||
2020-07-24 09:11:03.776 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: Received broadcast com.geeksville.mesh.RECEIVE_FROMRADIO
|
||||
2020-07-24 09:11:03.776 6478-6478/com.geeksville.mesh E/com.geeksville.mesh.service.MeshService: Invalid Protobuf from radio, len=80 (exception Protocol message had invalid UTF-8.)
|
||||
2020-07-24 09:11:04.031 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: work readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 is completed, resuming status=0, res=android.bluetooth.BluetoothGattCharacteristic@556a315
|
||||
2020-07-24 09:11:04.031 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.BluetoothInterface: Received 52 bytes from radio *** received 52 bytes - where did the previous two read results go?
|
||||
2020-07-24 09:11:04.033 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: Enqueuing work: readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
2020-07-24 09:11:04.033 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth$BluetoothContinuation: Starting work: readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
2020-07-24 09:11:04.034 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: Received broadcast com.geeksville.mesh.RECEIVE_FROMRADIO
|
||||
2020-07-24 09:11:04.035 6478-6478/com.geeksville.mesh E/com.geeksville.mesh.service.MeshService: Invalid Protobuf from radio, len=52 (exception While parsing a protocol message, the input ended unexpectedly in the middle of a field. This could mean either that the input has been truncated or that an embedded message misreported its own length.)
|
||||
2020-07-24 09:11:04.036 6478-6478/com.geeksville.mesh D/com.geeksville.mesh.service.MeshService: Received broadcast com.geeksville.mesh.RECEIVE_FROMRADIO
|
||||
2020-07-24 09:11:04.036 6478-6478/com.geeksville.mesh E/com.geeksville.mesh.service.MeshService: Invalid Protobuf from radio, len=52 (exception While parsing a protocol message, the input ended unexpectedly in the middle of a field. This could mean either that the input has been truncated or that an embedded message misreported its own length.)
|
||||
2020-07-24 09:11:04.210 6478-19966/com.geeksville.mesh D/com.geeksville.mesh.service.SafeBluetooth: work readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 is completed, resuming status=7, res=android.bluetooth.BluetoothGattCharacteristic@556a315 *** read failed
|
||||
2020-07-24 09:11:04.211 6478-19966/com.geeksville.mesh W/com.geeksville.mesh.service.BluetoothInterface: Scheduling reconnect because error during doReadFromRadio - disconnecting, Bluetooth status=7 while doing readC 8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
2020-07-24 09:11:04.211 6478-6545/com.geeksville.mesh W/com.geeksville.mesh.service.BluetoothInterface: Forcing disconnect and hopefully device will comeback (disabling forced refresh)
|
||||
2020-07-24 09:11:04.211 6478-6545/com.geeksville.mesh I/com.geeksville.mesh.service.SafeBluetooth: Closing our GATT connection
|
||||
2020-07-24 09:11:04.211 6478-6545/com.geeksville.mesh D/BluetoothGatt: cancelOpen() - device: 24:62:AB:DD:DF:3A
|
||||
2020-07-24 09:11:04.214 6478-19966/com.geeksville.mesh D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=4 device=24:62:AB:DD:DF:3A
|
||||
2020-07-24 09:11:04.215 6478-19966/com.geeksville.mesh I/com.geeksville.mesh.service.SafeBluetooth: new bluetooth connection state 0, status 0
|
||||
2020-07-24 09:11:04.215 6478-19966/com.geeksville.mesh I/com.geeksville.mesh.service.SafeBluetooth: Got disconnect because we are shutting down, closing gatt
|
||||
2020-07-24 09:11:04.215 6478-19966/com.geeksville.mesh D/BluetoothGatt: close()
|
||||
BIN
images/amazon-fire-button.png
Normal file
BIN
images/amazon-fire-button.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -9,7 +9,7 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets
|
||||
default_envs = tbeam # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
|
||||
[common]
|
||||
; common is not currently used
|
||||
@@ -32,18 +32,10 @@ board_build.partitions = partition-table.csv
|
||||
; note: we add src to our include search path so that lmic_project_config can override
|
||||
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
||||
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
-DHW_VERSION_${sysenv.COUNTRY}
|
||||
-DAPP_VERSION=${sysenv.APP_VERSION}
|
||||
-DHW_VERSION=${sysenv.HW_VERSION}
|
||||
|
||||
; not needed included in ttgo-t-beam board file
|
||||
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
|
||||
; -DBOARD_HAS_PSRAM
|
||||
; -mfix-esp32-psram-cache-issue
|
||||
|
||||
; -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||
|
||||
; leave this commented out to avoid breaking Windows
|
||||
;upload_port = /dev/ttyUSB0
|
||||
;monitor_port = /dev/ttyUSB0
|
||||
@@ -74,9 +66,10 @@ lib_deps =
|
||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||
https://github.com/meshtastic/arduino-fsm.git
|
||||
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
|
||||
https://github.com/meshtastic/RadioLib.git#d6b12f7eb0a06bd2414c79b437b25d377e3f603f
|
||||
https://github.com/meshtastic/RadioLib.git#7989a269be590a5d4914ac04069b58f4930c45c1
|
||||
https://github.com/meshtastic/TinyGPSPlus.git
|
||||
|
||||
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
|
||||
|
||||
; Common settings for ESP targes, mixin with extends = esp32_base
|
||||
[esp32_base]
|
||||
platform = espressif32
|
||||
@@ -85,20 +78,28 @@ src_filter =
|
||||
upload_speed = 921600
|
||||
debug_init_break = tbreak setup
|
||||
build_flags =
|
||||
${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue
|
||||
${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue -lnimble -std=c++11
|
||||
-DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
# Hmm - this doesn't work yet
|
||||
# board_build.ldscript = linker/esp32.extram.bss.ld
|
||||
lib_ignore = segger_rtt
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git#71ed4002c953d8c87f44ed27e34fe0735f99013e
|
||||
framework-arduinoespressif32@https://github.com/meshtastic/arduino-esp32.git#2814f110aa618429bdd9a0a2d6a93c55f29f87a6
|
||||
|
||||
; The 1.0 release of the TBEAM board
|
||||
; not needed included in ttgo-t-beam board file
|
||||
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
|
||||
; -DBOARD_HAS_PSRAM
|
||||
; -mfix-esp32-psram-cache-issue
|
||||
|
||||
; -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||
|
||||
; The 1.0 release of the TBEAM board
|
||||
[env:tbeam]
|
||||
extends = esp32_base
|
||||
board = ttgo-t-beam
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
https://github.com/meshtastic/AXP202X_Library.git
|
||||
build_flags =
|
||||
${esp32_base.build_flags} -D TBEAM_V10
|
||||
|
||||
@@ -149,15 +150,17 @@ src_filter =
|
||||
[nrf52_base]
|
||||
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
|
||||
; platform = nordicnrf52
|
||||
platform = https://github.com/meshtastic/platform-nordicnrf52.git#62d185fe61b6c84c554046106529b4fd8f155e2c
|
||||
platform = https://github.com/meshtastic/platform-nordicnrf52.git#1a2639a6b0f79b5df66bea3e3089f0d5285fdc63
|
||||
debug_tool = jlink
|
||||
build_type = debug ; I'm debugging with ICE a lot now
|
||||
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
|
||||
build_flags =
|
||||
${env.build_flags} -Wno-unused-variable -Isrc/nrf52 -Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
|
||||
${env.build_flags} -Wno-unused-variable
|
||||
-Isrc/nrf52
|
||||
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
|
||||
;-DCFG_DEBUG=3
|
||||
src_filter =
|
||||
${env.src_filter} -<esp32/>
|
||||
${env.src_filter} -<esp32/> -<nimble/>
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
monitor_port = /dev/ttyACM1
|
||||
@@ -183,6 +186,9 @@ board = nrf52840_dk
|
||||
[env:nrf52840dk-geeksville]
|
||||
extends = nrf52_base
|
||||
board = nrf52840_dk_modified
|
||||
# add our variants files to the include and src paths
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/pca10056-rc-clock
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/pca10056-rc-clock>
|
||||
|
||||
; Note: By default no lora device is created for this build - it uses a simulated interface
|
||||
[env:feather_nrf52832]
|
||||
@@ -209,6 +215,29 @@ lib_deps =
|
||||
${env.lib_deps}
|
||||
UC1701
|
||||
|
||||
; The https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay board by @BigCorvus
|
||||
[env:lora-relay-v1]
|
||||
extends = nrf52_base
|
||||
board = lora-relay-v1
|
||||
# add our variants files to the include and src paths
|
||||
# define build flags for the TFT_eSPI library
|
||||
build_flags = ${nrf52_base.build_flags} -Ivariants/lora_relay_v1
|
||||
-DUSER_SETUP_LOADED
|
||||
-DTFT_WIDTH=80
|
||||
-DTFT_HEIGHT=160
|
||||
-DST7735_GREENTAB160x80
|
||||
-DST7735_DRIVER
|
||||
-DTFT_CS=ST7735_CS
|
||||
-DTFT_DC=ST7735_RS
|
||||
-DTFT_RST=ST7735_RESET
|
||||
-DSPI_FREQUENCY=27000000
|
||||
src_filter = ${nrf52_base.src_filter} +<../variants/lora_relay_v1>
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
|
||||
TFT_eSPI
|
||||
# Adafruit ST7735 and ST7789 Library
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
2
proto
2
proto
Submodule proto updated: ab281311c4...ce422b7c44
3
release/.gitignore
vendored
3
release/.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
*.elf
|
||||
*.bin
|
||||
*.map
|
||||
*.zip
|
||||
*.zip
|
||||
*.uf2
|
||||
|
||||
12
src/BluetoothCommon.cpp
Normal file
12
src/BluetoothCommon.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "BluetoothCommon.h"
|
||||
|
||||
// NRF52 wants these constants as byte arrays
|
||||
// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER
|
||||
const uint8_t MESH_SERVICE_UUID_16[16u] = {0xfd, 0xea, 0x73, 0xe2, 0xca, 0x5d, 0xa8, 0x9f,
|
||||
0x1f, 0x46, 0xa8, 0x15, 0x18, 0xb2, 0xa1, 0x6b};
|
||||
const uint8_t TORADIO_UUID_16[16u] = {0xe7, 0x01, 0x44, 0x12, 0x66, 0x78, 0xdd, 0xa1,
|
||||
0xad, 0x4d, 0x9e, 0x12, 0xd2, 0x76, 0x5c, 0xf7};
|
||||
const uint8_t FROMRADIO_UUID_16[16u] = {0xd5, 0x54, 0xe4, 0xc5, 0x25, 0xc5, 0x31, 0xa5,
|
||||
0x55, 0x4a, 0x02, 0xee, 0xc2, 0xbc, 0xa2, 0x8b};
|
||||
const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6,
|
||||
0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed};
|
||||
@@ -12,5 +12,9 @@
|
||||
#define FROMRADIO_UUID "8ba2bcc2-ee02-4a55-a531-c525c5e454d5"
|
||||
#define FROMNUM_UUID "ed9da18c-a800-4f66-a670-aa7547e34453"
|
||||
|
||||
// NRF52 wants these constants as byte arrays
|
||||
// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER
|
||||
extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[];
|
||||
|
||||
/// Given a level between 0-100, update the BLE attribute
|
||||
void updateBatteryLevel(uint8_t level);
|
||||
160
src/Power.cpp
160
src/Power.cpp
@@ -1,27 +1,94 @@
|
||||
#include "power.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "main.h"
|
||||
#include "utils.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#ifdef TBEAM_V10
|
||||
#include "utils.h"
|
||||
|
||||
// FIXME. nasty hack cleanup how we load axp192
|
||||
#undef AXP192_SLAVE_ADDRESS
|
||||
#include "axp20x.h"
|
||||
|
||||
#ifdef TBEAM_V10
|
||||
AXP20X_Class axp;
|
||||
#endif
|
||||
|
||||
bool pmu_irq = false;
|
||||
|
||||
Power *power;
|
||||
|
||||
bool Power::setup()
|
||||
using namespace meshtastic;
|
||||
|
||||
/**
|
||||
* If this board has a battery level sensor, set this to a valid implementation
|
||||
*/
|
||||
static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor
|
||||
|
||||
/**
|
||||
* A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input
|
||||
*/
|
||||
class AnalogBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
/**
|
||||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||
*
|
||||
* FIXME - use a lipo lookup table, the current % full is super wrong
|
||||
*/
|
||||
virtual int getBattPercentage()
|
||||
{
|
||||
float v = getBattVoltage() / 1000;
|
||||
|
||||
axp192Init();
|
||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
||||
setPeriod(1);
|
||||
if (v < 2.1)
|
||||
return -1; // If voltage is super low assume no battery installed
|
||||
|
||||
return axp192_found;
|
||||
return 100 * (v - 3.27) / (4.2 - 3.27);
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw voltage of the batteryin millivolts or NAN if unknown
|
||||
*/
|
||||
virtual float getBattVoltage()
|
||||
{
|
||||
return
|
||||
#ifdef BATTERY_PIN
|
||||
1000.0 * analogRead(BATTERY_PIN) * 2.0 * (3.3 / 1024.0);
|
||||
#else
|
||||
NAN;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if there is a battery installed in this unit
|
||||
*/
|
||||
virtual bool isBatteryConnect() { return getBattVoltage() != -1; }
|
||||
} analogLevel;
|
||||
|
||||
bool Power::analogInit()
|
||||
{
|
||||
#ifdef BATTERY_PIN
|
||||
DEBUG_MSG("Using analog input for battery level\n");
|
||||
adcAttachPin(BATTERY_PIN);
|
||||
// adcStart(BATTERY_PIN);
|
||||
analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
|
||||
batteryLevel = &analogLevel;
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Power::setup()
|
||||
{
|
||||
bool found = axp192Init();
|
||||
|
||||
if (!found) {
|
||||
found = analogInit();
|
||||
}
|
||||
if (found) {
|
||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
||||
setPeriod(1);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// Reads power status to powerStatus singleton.
|
||||
@@ -29,42 +96,50 @@ bool Power::setup()
|
||||
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
||||
void Power::readPowerStatus()
|
||||
{
|
||||
bool hasBattery = axp.isBatteryConnect();
|
||||
int batteryVoltageMv = 0;
|
||||
uint8_t batteryChargePercent = 0;
|
||||
if (hasBattery) {
|
||||
batteryVoltageMv = axp.getBattVoltage();
|
||||
// If the AXP192 returns a valid battery percentage, use it
|
||||
if (axp.getBattPercentage() >= 0) {
|
||||
batteryChargePercent = axp.getBattPercentage();
|
||||
} else {
|
||||
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
||||
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in power.h
|
||||
batteryChargePercent = clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
|
||||
if (batteryLevel) {
|
||||
bool hasBattery = batteryLevel->isBatteryConnect();
|
||||
int batteryVoltageMv = 0;
|
||||
int8_t batteryChargePercent = 0;
|
||||
if (hasBattery) {
|
||||
batteryVoltageMv = batteryLevel->getBattVoltage();
|
||||
// If the AXP192 returns a valid battery percentage, use it
|
||||
if (batteryLevel->getBattPercentage() >= 0) {
|
||||
batteryChargePercent = batteryLevel->getBattPercentage();
|
||||
} else {
|
||||
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
||||
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in
|
||||
// power.h
|
||||
batteryChargePercent =
|
||||
clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)),
|
||||
0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const PowerStatus powerStatus =
|
||||
PowerStatus(hasBattery ? OptTrue : OptFalse, batteryLevel->isVBUSPlug() ? OptTrue : OptFalse,
|
||||
batteryLevel->isChargeing() ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
|
||||
newStatus.notifyObservers(&powerStatus);
|
||||
|
||||
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() && batteryLevel->getBattVoltage() < MIN_BAT_MILLIVOLTS)
|
||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||
} else {
|
||||
// No power sensing on this board - tell everyone else we have no idea what is happening
|
||||
const PowerStatus powerStatus = PowerStatus(OptUnknown, OptUnknown, OptUnknown, -1, -1);
|
||||
newStatus.notifyObservers(&powerStatus);
|
||||
}
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::PowerStatus powerStatus = meshtastic::PowerStatus(hasBattery, axp.isVBUSPlug(), axp.isChargeing(), batteryVoltageMv, batteryChargePercent);
|
||||
newStatus.notifyObservers(&powerStatus);
|
||||
|
||||
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() &&
|
||||
axp.getBattVoltage() < MIN_BAT_MILLIVOLTS)
|
||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||
}
|
||||
|
||||
void Power::doTask()
|
||||
void Power::doTask()
|
||||
{
|
||||
readPowerStatus();
|
||||
|
||||
|
||||
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||
if(statusHandler && statusHandler->isInitialized())
|
||||
if (statusHandler && statusHandler->isInitialized())
|
||||
setPeriod(1000 * 20);
|
||||
}
|
||||
#endif // TBEAM_V10
|
||||
|
||||
#ifdef AXP192_SLAVE_ADDRESS
|
||||
/**
|
||||
* Init the power manager chip
|
||||
*
|
||||
@@ -74,10 +149,13 @@ void Power::doTask()
|
||||
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 LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||||
*/
|
||||
void Power::axp192Init()
|
||||
bool Power::axp192Init()
|
||||
{
|
||||
#ifdef TBEAM_V10
|
||||
if (axp192_found) {
|
||||
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
|
||||
batteryLevel = &axp;
|
||||
|
||||
DEBUG_MSG("AXP192 Begin PASS\n");
|
||||
|
||||
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
||||
@@ -135,12 +213,15 @@ void Power::axp192Init()
|
||||
} else {
|
||||
DEBUG_MSG("AXP192 not found\n");
|
||||
}
|
||||
}
|
||||
|
||||
return axp192_found;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Power::loop()
|
||||
void Power::loop()
|
||||
{
|
||||
|
||||
#ifdef PMU_IRQ
|
||||
if (pmu_irq) {
|
||||
pmu_irq = false;
|
||||
@@ -174,6 +255,5 @@ void Power::loop()
|
||||
axp.clearIRQ();
|
||||
}
|
||||
|
||||
#endif // T_BEAM_V10
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,103 +1,94 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "Status.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace meshtastic {
|
||||
namespace meshtastic
|
||||
{
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
class PowerStatus : public Status
|
||||
/**
|
||||
* A boolean where we have a third state of Unknown
|
||||
*/
|
||||
enum OptionalBool { OptFalse = 0, OptTrue = 1, OptUnknown = 2 };
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
class PowerStatus : public Status
|
||||
{
|
||||
|
||||
private:
|
||||
CallbackObserver<PowerStatus, const PowerStatus *> statusObserver =
|
||||
CallbackObserver<PowerStatus, const PowerStatus *>(this, &PowerStatus::updateStatus);
|
||||
|
||||
/// Whether we have a battery connected
|
||||
OptionalBool hasBattery = OptUnknown;
|
||||
/// Battery voltage in mV, valid if haveBattery is true
|
||||
int batteryVoltageMv = 0;
|
||||
/// Battery charge percentage, either read directly or estimated
|
||||
int8_t batteryChargePercent = 0;
|
||||
/// Whether USB is connected
|
||||
OptionalBool hasUSB = OptUnknown;
|
||||
/// Whether we are charging the battery
|
||||
OptionalBool isCharging = OptUnknown;
|
||||
|
||||
public:
|
||||
PowerStatus() { statusType = STATUS_TYPE_POWER; }
|
||||
PowerStatus(OptionalBool hasBattery, OptionalBool hasUSB, OptionalBool isCharging, int batteryVoltageMv = -1,
|
||||
int8_t batteryChargePercent = 0)
|
||||
: Status()
|
||||
{
|
||||
this->hasBattery = hasBattery;
|
||||
this->hasUSB = hasUSB;
|
||||
this->isCharging = isCharging;
|
||||
this->batteryVoltageMv = batteryVoltageMv;
|
||||
this->batteryChargePercent = batteryChargePercent;
|
||||
}
|
||||
PowerStatus(const PowerStatus &);
|
||||
PowerStatus &operator=(const PowerStatus &);
|
||||
|
||||
private:
|
||||
CallbackObserver<PowerStatus, const PowerStatus *> statusObserver = CallbackObserver<PowerStatus, const PowerStatus *>(this, &PowerStatus::updateStatus);
|
||||
void observe(Observable<const PowerStatus *> *source) { statusObserver.observe(source); }
|
||||
|
||||
/// Whether we have a battery connected
|
||||
bool hasBattery;
|
||||
/// Battery voltage in mV, valid if haveBattery is true
|
||||
int batteryVoltageMv;
|
||||
/// Battery charge percentage, either read directly or estimated
|
||||
uint8_t batteryChargePercent;
|
||||
/// Whether USB is connected
|
||||
bool hasUSB;
|
||||
/// Whether we are charging the battery
|
||||
bool isCharging;
|
||||
bool getHasBattery() const { return hasBattery == OptTrue; }
|
||||
|
||||
public:
|
||||
bool getHasUSB() const { return hasUSB == OptTrue; }
|
||||
|
||||
PowerStatus() {
|
||||
statusType = STATUS_TYPE_POWER;
|
||||
}
|
||||
PowerStatus( bool hasBattery, bool hasUSB, bool isCharging, int batteryVoltageMv, uint8_t batteryChargePercent ) : Status()
|
||||
/// Can we even know if this board has USB power or not
|
||||
bool knowsUSB() const { return hasUSB != OptUnknown; }
|
||||
|
||||
bool getIsCharging() const { return isCharging == OptTrue; }
|
||||
|
||||
int getBatteryVoltageMv() const { return batteryVoltageMv; }
|
||||
|
||||
/**
|
||||
* Note: 0% battery means 'unknown/this board doesn't have a battery installed'
|
||||
*/
|
||||
uint8_t getBatteryChargePercent() const { return getHasBattery() ? batteryChargePercent : 0; }
|
||||
|
||||
bool matches(const PowerStatus *newStatus) const
|
||||
{
|
||||
return (newStatus->getHasBattery() != hasBattery || newStatus->getHasUSB() != hasUSB ||
|
||||
newStatus->getBatteryVoltageMv() != batteryVoltageMv);
|
||||
}
|
||||
int updateStatus(const PowerStatus *newStatus)
|
||||
{
|
||||
// Only update the status if values have actually changed
|
||||
bool isDirty;
|
||||
{
|
||||
this->hasBattery = hasBattery;
|
||||
this->hasUSB = hasUSB;
|
||||
this->isCharging = isCharging;
|
||||
this->batteryVoltageMv = batteryVoltageMv;
|
||||
this->batteryChargePercent = batteryChargePercent;
|
||||
isDirty = matches(newStatus);
|
||||
initialized = true;
|
||||
hasBattery = newStatus->hasBattery;
|
||||
batteryVoltageMv = newStatus->getBatteryVoltageMv();
|
||||
batteryChargePercent = newStatus->getBatteryChargePercent();
|
||||
hasUSB = newStatus->hasUSB;
|
||||
isCharging = newStatus->isCharging;
|
||||
}
|
||||
PowerStatus(const PowerStatus &);
|
||||
PowerStatus &operator=(const PowerStatus &);
|
||||
|
||||
void observe(Observable<const PowerStatus *> *source)
|
||||
{
|
||||
statusObserver.observe(source);
|
||||
if (isDirty) {
|
||||
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
bool getHasBattery() const
|
||||
{
|
||||
return hasBattery;
|
||||
}
|
||||
|
||||
bool getHasUSB() const
|
||||
{
|
||||
return hasUSB;
|
||||
}
|
||||
|
||||
bool getIsCharging() const
|
||||
{
|
||||
return isCharging;
|
||||
}
|
||||
|
||||
int getBatteryVoltageMv() const
|
||||
{
|
||||
return batteryVoltageMv;
|
||||
}
|
||||
|
||||
uint8_t getBatteryChargePercent() const
|
||||
{
|
||||
return batteryChargePercent;
|
||||
}
|
||||
|
||||
bool matches(const PowerStatus *newStatus) const
|
||||
{
|
||||
return (
|
||||
newStatus->getHasBattery() != hasBattery ||
|
||||
newStatus->getHasUSB() != hasUSB ||
|
||||
newStatus->getBatteryVoltageMv() != batteryVoltageMv
|
||||
);
|
||||
}
|
||||
int updateStatus(const PowerStatus *newStatus) {
|
||||
// Only update the status if values have actually changed
|
||||
bool isDirty;
|
||||
{
|
||||
isDirty = matches(newStatus);
|
||||
initialized = true;
|
||||
hasBattery = newStatus->getHasBattery();
|
||||
batteryVoltageMv = newStatus->getBatteryVoltageMv();
|
||||
batteryChargePercent = newStatus->getBatteryChargePercent();
|
||||
hasUSB = newStatus->getHasUSB();
|
||||
isCharging = newStatus->getIsCharging();
|
||||
}
|
||||
if(isDirty) {
|
||||
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace meshtastic
|
||||
|
||||
extern meshtastic::PowerStatus *powerStatus;
|
||||
|
||||
11
src/SPILock.cpp
Normal file
11
src/SPILock.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "SPILock.h"
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
|
||||
concurrency::Lock *spiLock;
|
||||
|
||||
void initSPI()
|
||||
{
|
||||
assert(!spiLock);
|
||||
spiLock = new concurrency::Lock();
|
||||
}
|
||||
12
src/SPILock.h
Normal file
12
src/SPILock.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/LockGuard.h"
|
||||
|
||||
/**
|
||||
* Used to provide mutual exclusion for access to the SPI bus. Usage:
|
||||
* concurrency::LockGuard g(spiLock);
|
||||
*/
|
||||
extern concurrency::Lock *spiLock;
|
||||
|
||||
/** Setup SPI access and create the spiLock lock. */
|
||||
void initSPI();
|
||||
@@ -20,7 +20,7 @@ namespace meshtastic
|
||||
CallbackObserver<Status, const Status *> statusObserver = CallbackObserver<Status, const Status *>(this, &Status::updateStatus);
|
||||
bool initialized = false;
|
||||
// Workaround for no typeid support
|
||||
int statusType;
|
||||
int statusType = 0;
|
||||
|
||||
public:
|
||||
// Allows us to generate observable events
|
||||
|
||||
@@ -158,16 +158,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BUTTON_PIN 38 // The middle button GPIO on the T-Beam
|
||||
#define BUTTON_PIN_ALT 13 // Alternate GPIO for an external button if needed
|
||||
|
||||
#ifndef USE_JTAG
|
||||
#define RF95_RESET 14
|
||||
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
|
||||
// not found then probe for SX1262
|
||||
#define USE_RF95
|
||||
#define USE_SX1262
|
||||
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 23
|
||||
#define LORA_DIO1 33 // SX1262 IRQ
|
||||
#define LORA_DIO2 32 // SX1262 BUSY
|
||||
#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
|
||||
|
||||
#ifdef USE_SX1262
|
||||
#define SX1262_CS RF95_NSS // FIXME - we really should define LORA_CS instead
|
||||
#define SX1262_DIO1 LORA_DIO1
|
||||
#define SX1262_BUSY LORA_DIO2
|
||||
#define SX1262_RESET LORA_RESET
|
||||
#define SX1262_E22 // Not really an E22 but TTGO seems to be trying to clone that
|
||||
// Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface
|
||||
// code)
|
||||
#endif
|
||||
#define RF95_IRQ 26
|
||||
#define RF95_DIO1 33 // Note: not really used on this board
|
||||
#define RF95_DIO2 32 // Note: not really used on this board
|
||||
|
||||
// Leave undefined to disable our PMU IRQ handler
|
||||
#define PMU_IRQ 35
|
||||
|
||||
#define AXP192_SLAVE_ADDRESS 0x34
|
||||
|
||||
#elif defined(TBEAM_V07)
|
||||
@@ -180,13 +193,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define I2C_SCL 22
|
||||
|
||||
#define BUTTON_PIN 39
|
||||
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
|
||||
#ifndef USE_JTAG
|
||||
#define RF95_RESET 23
|
||||
#endif
|
||||
#define RF95_IRQ 26
|
||||
#define RF95_DIO1 33 // Note: not really used on this board
|
||||
#define RF95_DIO2 32 // Note: not really used on this board
|
||||
#define USE_RF95
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 23
|
||||
#define LORA_DIO1 33 // Not really used
|
||||
#define LORA_DIO2 32 // Not really used
|
||||
|
||||
// This board has different GPS pins than all other boards
|
||||
#undef GPS_RX_PIN
|
||||
@@ -216,12 +231,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define LED_PIN 25 // If defined we will blink this LED
|
||||
#define BUTTON_PIN 0 // If defined, this will be used for user button presses
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#ifndef USE_JTAG
|
||||
#define RF95_RESET 14 // If defined, this pin will be used to reset the LORA radio
|
||||
#define LORA_RESET 14
|
||||
#endif
|
||||
#define RF95_IRQ 26
|
||||
#define RF95_DIO1 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define RF95_DIO2 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
#elif defined(TLORA_V1)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR "tlora-v1"
|
||||
@@ -238,11 +255,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
|
||||
#define LED_PIN 2 // If defined we will blink this LED
|
||||
#define BUTTON_PIN 0 // If defined, this will be used for user button presses
|
||||
#define BUTTON_NEED_PULLUP
|
||||
|
||||
#define RF95_RESET 14 // If defined, this pin will be used to reset the LORA radio
|
||||
#define RF95_IRQ 26 // IRQ line for the LORA radio
|
||||
#define RF95_DIO1 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define RF95_DIO2 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 14
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
#elif defined(TLORA_V2)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
@@ -263,11 +282,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BUTTON_PIN \
|
||||
0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one
|
||||
// between this pin and ground
|
||||
#define BUTTON_NEED_PULLUP
|
||||
|
||||
#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio
|
||||
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
|
||||
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 14
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
#elif defined(TLORA_V2_1_16)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
@@ -278,6 +299,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define GPS_RX_PIN 36
|
||||
#define GPS_TX_PIN 39
|
||||
|
||||
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
|
||||
#define I2C_SDA 21 // I2C pins for this board
|
||||
#define I2C_SCL 22
|
||||
|
||||
@@ -288,11 +311,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BUTTON_PIN \
|
||||
12 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one
|
||||
// between this pin and ground
|
||||
#define BUTTON_NEED_PULLUP
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 14
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
#define RF95_RESET 14 // If defined, this pin will be used to reset the LORA radio
|
||||
#define RF95_IRQ 26 // IRQ line for the LORA radio
|
||||
#define RF95_DIO1 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#define RF95_DIO2 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_NRF52840_PCA10056
|
||||
@@ -314,6 +340,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_RF95
|
||||
#define RF95_RESET LORA_RESET
|
||||
#define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0
|
||||
#define RF95_DIO1 LORA_DIO1 // Note: not really used for RF95
|
||||
#define RF95_DIO2 LORA_DIO2 // Note: not really used for RF95
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// DEBUG
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -337,8 +370,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// Debug printing to segger console
|
||||
#define SEGGER_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
|
||||
|
||||
// nrf52 gets its settings via variant files
|
||||
#ifndef PIN_SERIAL_RX
|
||||
// If we are not on a NRF52840 (which has built in USB-ACM serial support) and we don't have serial pins hooked up, then we MUST
|
||||
// use SEGGER for debug output
|
||||
#if !defined(PIN_SERIAL_RX) && !defined(NRF52840_XXAA)
|
||||
// No serial ports on this board - ONLY use segger in memory console
|
||||
#define USE_SEGGER
|
||||
#endif
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
/// Error codes for critical error
|
||||
enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio, ErrUnspecified };
|
||||
enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio, ErrUnspecified, UBloxInitFailed };
|
||||
|
||||
/// Record an error that should be reported via analytics
|
||||
void recordCriticalError(CriticalErrorCode code, uint32_t address = 0);
|
||||
|
||||
@@ -1,48 +1,43 @@
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include "BluetoothUtil.h"
|
||||
#include "CallbackCharacteristic.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "../concurrency/LockGuard.h"
|
||||
#include "../timing.h"
|
||||
#include <Arduino.h>
|
||||
#include <BLE2902.h>
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "configuration.h"
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
|
||||
#include <CRC32.h>
|
||||
#include <Update.h>
|
||||
#include <esp_gatt_defs.h>
|
||||
|
||||
//using namespace meshtastic;
|
||||
int16_t updateResultHandle = -1;
|
||||
|
||||
CRC32 crc;
|
||||
uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
|
||||
static CRC32 crc;
|
||||
static uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
|
||||
|
||||
uint32_t updateExpectedSize, updateActualSize;
|
||||
static uint32_t updateExpectedSize, updateActualSize;
|
||||
|
||||
concurrency::Lock *updateLock;
|
||||
static concurrency::Lock *updateLock;
|
||||
|
||||
class TotalSizeCharacteristic : public CallbackCharacteristic
|
||||
/// Handle writes & reads to total size
|
||||
int update_size_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
public:
|
||||
TotalSizeCharacteristic()
|
||||
: CallbackCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e",
|
||||
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
|
||||
{
|
||||
}
|
||||
concurrency::LockGuard g(updateLock);
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
{
|
||||
concurrency::LockGuard g(updateLock);
|
||||
// Check if there is enough to OTA Update
|
||||
uint32_t len = getValue32(c, 0);
|
||||
updateExpectedSize = len;
|
||||
// Check if there is enough to OTA Update
|
||||
chr_readwrite32le(&updateExpectedSize, ctxt);
|
||||
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR && updateExpectedSize != 0) {
|
||||
updateActualSize = 0;
|
||||
crc.reset();
|
||||
bool canBegin = Update.begin(len);
|
||||
DEBUG_MSG("Setting update size %u, result %d\n", len, canBegin);
|
||||
if (Update.isRunning())
|
||||
Update.abort();
|
||||
bool canBegin = Update.begin(updateExpectedSize);
|
||||
DEBUG_MSG("Setting update size %u, result %d\n", updateExpectedSize, canBegin);
|
||||
if (!canBegin) {
|
||||
// Indicate failure by forcing the size to 0
|
||||
uint32_t zero = 0;
|
||||
c->setValue(zero);
|
||||
// Indicate failure by forcing the size to 0 (client will read it back)
|
||||
updateExpectedSize = 0;
|
||||
} else {
|
||||
// This totally breaks abstraction to up up into the app layer for this, but quick hack to make sure we only
|
||||
// talk to one service during the sw update.
|
||||
@@ -55,73 +50,81 @@ class TotalSizeCharacteristic : public CallbackCharacteristic
|
||||
// writing flash - shut the radio off during updates
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_BLOCKSIZE 512
|
||||
|
||||
class DataCharacteristic : public CallbackCharacteristic
|
||||
/// Handle writes to data
|
||||
int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
public:
|
||||
DataCharacteristic() : CallbackCharacteristic("e272ebac-d463-4b98-bc84-5cc1a39ee517", BLECharacteristic::PROPERTY_WRITE) {}
|
||||
concurrency::LockGuard g(updateLock);
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
{
|
||||
concurrency::LockGuard g(updateLock);
|
||||
std::string value = c->getValue();
|
||||
uint32_t len = value.length();
|
||||
assert(len <= MAX_BLOCKSIZE);
|
||||
static uint8_t
|
||||
data[MAX_BLOCKSIZE]; // we temporarily copy here because I'm worried that a fast sender might be able overwrite srcbuf
|
||||
memcpy(data, c->getData(), len);
|
||||
// DEBUG_MSG("Writing %u\n", len);
|
||||
crc.update(data, len);
|
||||
Update.write(data, len);
|
||||
updateActualSize += len;
|
||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); // Not exactly correct, but we want to force the device to not sleep now
|
||||
}
|
||||
};
|
||||
static uint8_t
|
||||
data[MAX_BLOCKSIZE]; // we temporarily copy here because I'm worried that a fast sender might be able overwrite srcbuf
|
||||
|
||||
static BLECharacteristic *resultC;
|
||||
uint16_t len = 0;
|
||||
|
||||
class CRC32Characteristic : public CallbackCharacteristic
|
||||
auto rc = ble_hs_mbuf_to_flat(ctxt->om, data, sizeof(data), &len);
|
||||
assert(rc == 0);
|
||||
|
||||
// DEBUG_MSG("Writing %u\n", len);
|
||||
crc.update(data, len);
|
||||
Update.write(data, len);
|
||||
updateActualSize += len;
|
||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); // Not exactly correct, but we want to force the device to not sleep now
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t update_result;
|
||||
|
||||
/// Handle writes to crc32
|
||||
int update_crc32_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
public:
|
||||
CRC32Characteristic() : CallbackCharacteristic("4826129c-c22a-43a3-b066-ce8f0d5bacc6", BLECharacteristic::PROPERTY_WRITE) {}
|
||||
concurrency::LockGuard g(updateLock);
|
||||
uint32_t expectedCRC = 0;
|
||||
chr_readwrite32le(&expectedCRC, ctxt);
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
uint32_t actualCRC = crc.finalize();
|
||||
DEBUG_MSG("expected CRC %u\n", expectedCRC);
|
||||
|
||||
uint8_t result = 0xff;
|
||||
|
||||
if (updateActualSize != updateExpectedSize) {
|
||||
DEBUG_MSG("Expected %u bytes, but received %u bytes!\n", updateExpectedSize, updateActualSize);
|
||||
result = 0xe1; // FIXME, use real error codes
|
||||
} else if (actualCRC != expectedCRC) // Check the CRC before asking the update to happen.
|
||||
{
|
||||
concurrency::LockGuard g(updateLock);
|
||||
uint32_t expectedCRC = getValue32(c, 0);
|
||||
uint32_t actualCRC = crc.finalize();
|
||||
DEBUG_MSG("expected CRC %u\n", expectedCRC);
|
||||
|
||||
uint8_t result = 0xff;
|
||||
|
||||
if (updateActualSize != updateExpectedSize) {
|
||||
DEBUG_MSG("Expected %u bytes, but received %u bytes!\n", updateExpectedSize, updateActualSize);
|
||||
result = 0xe1; // FIXME, use real error codes
|
||||
} else if (actualCRC != expectedCRC) // Check the CRC before asking the update to happen.
|
||||
{
|
||||
DEBUG_MSG("Invalid CRC! expected=%u, actual=%u\n", expectedCRC, actualCRC);
|
||||
result = 0xe0; // FIXME, use real error codes
|
||||
DEBUG_MSG("Invalid CRC! expected=%u, actual=%u\n", expectedCRC, actualCRC);
|
||||
result = 0xe0; // FIXME, use real error codes
|
||||
} else {
|
||||
if (Update.end()) {
|
||||
DEBUG_MSG("OTA done, rebooting in 5 seconds!\n");
|
||||
rebootAtMsec = timing::millis() + 5000;
|
||||
} else {
|
||||
if (Update.end()) {
|
||||
DEBUG_MSG("OTA done, rebooting in 5 seconds!\n");
|
||||
rebootAtMsec = timing::millis() + 5000;
|
||||
} else {
|
||||
DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError());
|
||||
}
|
||||
result = Update.getError();
|
||||
DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError());
|
||||
}
|
||||
|
||||
if (RadioLibInterface::instance)
|
||||
RadioLibInterface::instance->startReceive(); // Resume radio
|
||||
|
||||
assert(resultC);
|
||||
resultC->setValue(&result, 1);
|
||||
resultC->notify();
|
||||
result = Update.getError();
|
||||
}
|
||||
};
|
||||
|
||||
if (RadioLibInterface::instance)
|
||||
RadioLibInterface::instance->startReceive(); // Resume radio
|
||||
|
||||
assert(updateResultHandle >= 0);
|
||||
update_result = result;
|
||||
DEBUG_MSG("BLE notify update result\n");
|
||||
auto res = ble_gattc_notify(curConnectionHandle, updateResultHandle);
|
||||
assert(res == 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int update_result_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
return chr_readwrite8(&update_result, sizeof(update_result), ctxt);
|
||||
}
|
||||
|
||||
void bluetoothRebootCheck()
|
||||
{
|
||||
@@ -135,45 +138,15 @@ void bluetoothRebootCheck()
|
||||
See bluetooth-api.md
|
||||
|
||||
*/
|
||||
BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion)
|
||||
void reinitUpdateService()
|
||||
{
|
||||
if (!updateLock)
|
||||
updateLock = new concurrency::Lock();
|
||||
|
||||
// Create the BLE Service
|
||||
BLEService *service = server->createService(BLEUUID("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30"), 25, 0);
|
||||
auto res = ble_gatts_count_cfg(gatt_update_svcs); // assigns handles? see docstring for note about clearing the handle list
|
||||
// before calling SLEEP SUPPORT
|
||||
assert(res == 0);
|
||||
|
||||
assert(!resultC);
|
||||
resultC = new BLECharacteristic("5e134862-7411-4424-ac4a-210937432c77",
|
||||
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||
|
||||
addWithDesc(service, new TotalSizeCharacteristic, "total image size");
|
||||
addWithDesc(service, new DataCharacteristic, "data");
|
||||
addWithDesc(service, new CRC32Characteristic, "crc32");
|
||||
addWithDesc(service, resultC, "result code");
|
||||
|
||||
resultC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
||||
|
||||
BLECharacteristic *swC =
|
||||
new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||
swC->setValue(swVersion);
|
||||
service->addCharacteristic(addBLECharacteristic(swC));
|
||||
|
||||
BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ);
|
||||
mfC->setValue(hwVendor);
|
||||
service->addCharacteristic(addBLECharacteristic(mfC));
|
||||
|
||||
BLECharacteristic *hwvC =
|
||||
new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||
hwvC->setValue(hwVersion);
|
||||
service->addCharacteristic(addBLECharacteristic(hwvC));
|
||||
|
||||
return service;
|
||||
res = ble_gatts_add_svcs(gatt_update_svcs);
|
||||
assert(res == 0);
|
||||
}
|
||||
|
||||
void destroyUpdateService()
|
||||
{
|
||||
assert(resultC);
|
||||
|
||||
resultC = NULL;
|
||||
}
|
||||
@@ -1,11 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include "nimble/NimbleDefs.h"
|
||||
|
||||
BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion);
|
||||
void reinitUpdateService();
|
||||
|
||||
void destroyUpdateService();
|
||||
void bluetoothRebootCheck();
|
||||
void bluetoothRebootCheck();
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int update_size_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
int update_result_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
int update_crc32_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
|
||||
extern const struct ble_gatt_svc_def gatt_update_svcs[];
|
||||
|
||||
extern const ble_uuid128_t update_result_uuid;
|
||||
|
||||
extern int16_t updateResultHandle;
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
@@ -1,323 +0,0 @@
|
||||
#include "BluetoothUtil.h"
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
#include <BLE2902.h>
|
||||
#include <Update.h>
|
||||
#include <esp_gatt_defs.h>
|
||||
|
||||
SimpleAllocator btPool;
|
||||
|
||||
bool _BLEClientConnected = false;
|
||||
|
||||
class MyServerCallbacks : public BLEServerCallbacks
|
||||
{
|
||||
void onConnect(BLEServer *pServer) { _BLEClientConnected = true; };
|
||||
|
||||
void onDisconnect(BLEServer *pServer) { _BLEClientConnected = false; }
|
||||
};
|
||||
|
||||
#define MAX_DESCRIPTORS 32
|
||||
#define MAX_CHARACTERISTICS 32
|
||||
|
||||
static BLECharacteristic *chars[MAX_CHARACTERISTICS];
|
||||
static size_t numChars;
|
||||
static BLEDescriptor *descs[MAX_DESCRIPTORS];
|
||||
static size_t numDescs;
|
||||
|
||||
/// Add a characteristic that we will delete when we restart
|
||||
BLECharacteristic *addBLECharacteristic(BLECharacteristic *c)
|
||||
{
|
||||
assert(numChars < MAX_CHARACTERISTICS);
|
||||
chars[numChars++] = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
/// Add a characteristic that we will delete when we restart
|
||||
BLEDescriptor *addBLEDescriptor(BLEDescriptor *c)
|
||||
{
|
||||
assert(numDescs < MAX_DESCRIPTORS);
|
||||
descs[numDescs++] = c;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
// Help routine to add a description to any BLECharacteristic and add it to the service
|
||||
// We default to require an encrypted BOND for all these these characterstics
|
||||
void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description)
|
||||
{
|
||||
c->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
|
||||
|
||||
BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t)ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1);
|
||||
assert(desc);
|
||||
desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
|
||||
desc->setValue(description);
|
||||
c->addDescriptor(desc);
|
||||
service->addCharacteristic(c);
|
||||
addBLECharacteristic(c);
|
||||
addBLEDescriptor(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create standard device info service
|
||||
**/
|
||||
BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion,
|
||||
std::string hwVersion = "")
|
||||
{
|
||||
BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC));
|
||||
|
||||
BLECharacteristic *swC =
|
||||
new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||
BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ);
|
||||
// BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR),
|
||||
// BLECharacteristic::PROPERTY_READ);
|
||||
|
||||
/*
|
||||
* Mandatory characteristic for device info service?
|
||||
|
||||
BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID,
|
||||
BLECharacteristic::PROPERTY_READ);
|
||||
|
||||
uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version;
|
||||
uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >>
|
||||
8), (uint8_t) version }; m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
|
||||
*/
|
||||
swC->setValue(swVersion);
|
||||
deviceInfoService->addCharacteristic(addBLECharacteristic(swC));
|
||||
mfC->setValue(hwVendor);
|
||||
deviceInfoService->addCharacteristic(addBLECharacteristic(mfC));
|
||||
if (!hwVersion.empty()) {
|
||||
BLECharacteristic *hwvC =
|
||||
new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||
hwvC->setValue(hwVersion);
|
||||
deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC));
|
||||
}
|
||||
// SerialNumberCharacteristic.setValue("FIXME");
|
||||
// deviceInfoService->addCharacteristic(&SerialNumberCharacteristic);
|
||||
|
||||
// m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29,
|
||||
// BLECharacteristic::PROPERTY_READ); m_manufacturerCharacteristic->setValue(name);
|
||||
|
||||
/* add these later?
|
||||
ESP_GATT_UUID_SYSTEM_ID
|
||||
*/
|
||||
|
||||
// caller must call service->start();
|
||||
return deviceInfoService;
|
||||
}
|
||||
|
||||
static BLECharacteristic *batteryLevelC;
|
||||
|
||||
/**
|
||||
* Create a battery level service
|
||||
*/
|
||||
BLEService *createBatteryService(BLEServer *server)
|
||||
{
|
||||
// Create the BLE Service
|
||||
BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F));
|
||||
|
||||
batteryLevelC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL),
|
||||
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||
|
||||
addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100");
|
||||
batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
||||
|
||||
// I don't think we need to advertise this? and some phones only see the first thing advertised anyways...
|
||||
// server->getAdvertising()->addServiceUUID(pBattery->getUUID());
|
||||
pBattery->start();
|
||||
|
||||
return pBattery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the battery level we are currently telling clients.
|
||||
* level should be a pct between 0 and 100
|
||||
*/
|
||||
void updateBatteryLevel(uint8_t level)
|
||||
{
|
||||
if (batteryLevelC) {
|
||||
DEBUG_MSG("set BLE battery level %u\n", level);
|
||||
batteryLevelC->setValue(&level, 1);
|
||||
batteryLevelC->notify();
|
||||
}
|
||||
}
|
||||
|
||||
void dumpCharacteristic(BLECharacteristic *c)
|
||||
{
|
||||
std::string value = c->getValue();
|
||||
|
||||
if (value.length() > 0) {
|
||||
DEBUG_MSG("New value: ");
|
||||
for (int i = 0; i < value.length(); i++)
|
||||
DEBUG_MSG("%c", value[i]);
|
||||
|
||||
DEBUG_MSG("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/** converting endianness pull out a 32 bit value */
|
||||
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue)
|
||||
{
|
||||
std::string value = c->getValue();
|
||||
uint32_t r = defaultValue;
|
||||
|
||||
if (value.length() == 4)
|
||||
r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
class MySecurity : public BLESecurityCallbacks
|
||||
{
|
||||
protected:
|
||||
bool onConfirmPIN(uint32_t pin)
|
||||
{
|
||||
Serial.printf("onConfirmPIN %u\n", pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t onPassKeyRequest()
|
||||
{
|
||||
Serial.println("onPassKeyRequest");
|
||||
return 123511; // not used
|
||||
}
|
||||
|
||||
void onPassKeyNotify(uint32_t pass_key)
|
||||
{
|
||||
Serial.printf("onPassKeyNotify %06u\n", pass_key);
|
||||
startCb(pass_key);
|
||||
}
|
||||
|
||||
bool onSecurityRequest()
|
||||
{
|
||||
Serial.println("onSecurityRequest");
|
||||
return true;
|
||||
}
|
||||
|
||||
void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl)
|
||||
{
|
||||
if (cmpl.success) {
|
||||
uint16_t length;
|
||||
esp_ble_gap_get_whitelist_size(&length);
|
||||
Serial.printf(" authenticated and connected to phone\n");
|
||||
} else {
|
||||
Serial.printf("phone authenticate failed %d\n", cmpl.fail_reason);
|
||||
}
|
||||
|
||||
// Remove our custom PIN request screen.
|
||||
stopCb();
|
||||
}
|
||||
|
||||
public:
|
||||
StartBluetoothPinScreenCallback startCb;
|
||||
StopBluetoothPinScreenCallback stopCb;
|
||||
};
|
||||
|
||||
BLEServer *pServer;
|
||||
|
||||
BLEService *pDevInfo, *pUpdate, *pBattery;
|
||||
|
||||
void deinitBLE()
|
||||
{
|
||||
assert(pServer);
|
||||
|
||||
pServer->getAdvertising()->stop();
|
||||
|
||||
if (pUpdate != NULL) {
|
||||
destroyUpdateService();
|
||||
|
||||
pUpdate->stop(); // we delete them below
|
||||
pUpdate->executeDelete();
|
||||
}
|
||||
|
||||
pBattery->stop();
|
||||
pBattery->executeDelete();
|
||||
|
||||
pDevInfo->stop();
|
||||
pDevInfo->executeDelete();
|
||||
|
||||
// First shutdown bluetooth
|
||||
BLEDevice::deinit(false);
|
||||
|
||||
// do not delete this - it is dynamically allocated, but only once - statically in BLEDevice
|
||||
// delete pServer->getAdvertising();
|
||||
|
||||
if (pUpdate != NULL)
|
||||
delete pUpdate;
|
||||
delete pDevInfo;
|
||||
delete pBattery;
|
||||
delete pServer;
|
||||
|
||||
batteryLevelC = NULL; // Don't let anyone generate bogus notifies
|
||||
|
||||
for (int i = 0; i < numChars; i++) {
|
||||
delete chars[i];
|
||||
}
|
||||
numChars = 0;
|
||||
|
||||
for (int i = 0; i < numDescs; i++)
|
||||
delete descs[i];
|
||||
numDescs = 0;
|
||||
|
||||
btPool.reset();
|
||||
}
|
||||
|
||||
BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoothPinScreenCallback stopBtPinScreen,
|
||||
std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion)
|
||||
{
|
||||
BLEDevice::init(deviceName);
|
||||
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
|
||||
|
||||
/*
|
||||
* Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation
|
||||
*/
|
||||
static MySecurity mySecurity;
|
||||
mySecurity.startCb = startBtPinScreen;
|
||||
mySecurity.stopCb = stopBtPinScreen;
|
||||
BLEDevice::setSecurityCallbacks(&mySecurity);
|
||||
|
||||
// Create the BLE Server
|
||||
pServer = BLEDevice::createServer();
|
||||
static MyServerCallbacks myCallbacks;
|
||||
pServer->setCallbacks(&myCallbacks);
|
||||
|
||||
pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion);
|
||||
|
||||
pBattery = createBatteryService(pServer);
|
||||
|
||||
#define BLE_SOFTWARE_UPDATE
|
||||
#ifdef BLE_SOFTWARE_UPDATE
|
||||
pUpdate = createUpdateService(pServer, hwVendor, swVersion,
|
||||
hwVersion); // We need to advertise this so our android ble scan operation can see it
|
||||
|
||||
pUpdate->start();
|
||||
#endif
|
||||
|
||||
// It seems only one service can be advertised - so for now don't advertise our updater
|
||||
// pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID());
|
||||
|
||||
// start all our services (do this after creating all of them)
|
||||
pDevInfo->start();
|
||||
|
||||
// FIXME turn on this restriction only after the device is paired with a phone
|
||||
// advert->setScanFilter(false, true); // We let anyone scan for us (FIXME, perhaps only allow that until we are paired with a
|
||||
// phone and configured) but only let whitelist phones connect
|
||||
|
||||
static BLESecurity security; // static to avoid allocs
|
||||
BLESecurity *pSecurity = &security;
|
||||
pSecurity->setCapability(ESP_IO_CAP_OUT);
|
||||
|
||||
// FIXME - really should be ESP_LE_AUTH_REQ_SC_BOND but it seems there is a bug right now causing that bonding info to be lost
|
||||
// occasionally?
|
||||
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
|
||||
|
||||
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
|
||||
|
||||
return pServer;
|
||||
}
|
||||
|
||||
// Called from loop
|
||||
void loopBLE()
|
||||
{
|
||||
bluetoothRebootCheck();
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include "SimpleAllocator.h"
|
||||
|
||||
// Now handled by BluetoothUtil.cpp
|
||||
// BLEService *createDeviceInfomationService(BLEServer* server, uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version);
|
||||
|
||||
// Help routine to add a description to any BLECharacteristic and add it to the service
|
||||
void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description);
|
||||
|
||||
void dumpCharacteristic(BLECharacteristic *c);
|
||||
|
||||
/** converting endianness pull out a 32 bit value */
|
||||
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();
|
||||
BLEServer *initBLE(
|
||||
StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoothPinScreenCallback stopBtPinScreen,
|
||||
std::string devName, std::string hwVendor, std::string swVersion, std::string hwVersion = "");
|
||||
void deinitBLE();
|
||||
|
||||
/// Add a characteristic that we will delete when we restart
|
||||
BLECharacteristic *addBLECharacteristic(BLECharacteristic *c);
|
||||
|
||||
/// Add a characteristic that we will delete when we restart
|
||||
BLEDescriptor *addBLEDescriptor(BLEDescriptor *c);
|
||||
|
||||
/// Any bluetooth objects you allocate _must_ come from this pool if you want to be able to call deinitBLE()
|
||||
extern SimpleAllocator btPool;
|
||||
@@ -1,137 +0,0 @@
|
||||
#include "MeshBluetoothService.h"
|
||||
#include "BluetoothUtil.h"
|
||||
#include <Arduino.h>
|
||||
#include <BLE2902.h>
|
||||
#include <assert.h>
|
||||
#include <esp_gatt_defs.h>
|
||||
|
||||
#include "BluetoothCommon.h"
|
||||
#include "CallbackCharacteristic.h"
|
||||
#include "GPS.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PhoneAPI.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
|
||||
static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)];
|
||||
|
||||
static CallbackCharacteristic *meshFromNumCharacteristic;
|
||||
|
||||
BLEService *meshService;
|
||||
|
||||
class BluetoothPhoneAPI : public PhoneAPI
|
||||
{
|
||||
/**
|
||||
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
|
||||
*/
|
||||
virtual void onNowHasData(uint32_t fromRadioNum)
|
||||
{
|
||||
PhoneAPI::onNowHasData(fromRadioNum);
|
||||
|
||||
if (meshFromNumCharacteristic) { // this ptr might change from sleep to sleep, or even be null
|
||||
meshFromNumCharacteristic->setValue(fromRadioNum);
|
||||
meshFromNumCharacteristic->notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static BluetoothPhoneAPI *bluetoothPhoneAPI;
|
||||
|
||||
class ToRadioCharacteristic : public CallbackCharacteristic
|
||||
{
|
||||
public:
|
||||
ToRadioCharacteristic() : CallbackCharacteristic(TORADIO_UUID, BLECharacteristic::PROPERTY_WRITE) {}
|
||||
|
||||
void onWrite(BLECharacteristic *c) { bluetoothPhoneAPI->handleToRadio(c->getData(), c->getValue().length()); }
|
||||
};
|
||||
|
||||
class FromRadioCharacteristic : public CallbackCharacteristic
|
||||
{
|
||||
public:
|
||||
FromRadioCharacteristic() : CallbackCharacteristic(FROMRADIO_UUID, BLECharacteristic::PROPERTY_READ) {}
|
||||
|
||||
void onRead(BLECharacteristic *c)
|
||||
{
|
||||
size_t numBytes = bluetoothPhoneAPI->getFromRadio(trBytes);
|
||||
|
||||
// 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
|
||||
if (numBytes) {
|
||||
c->setValue(trBytes, numBytes);
|
||||
} else {
|
||||
c->setValue((uint8_t *)"", 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class FromNumCharacteristic : public CallbackCharacteristic
|
||||
{
|
||||
public:
|
||||
FromNumCharacteristic()
|
||||
: CallbackCharacteristic(FROMNUM_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ |
|
||||
BLECharacteristic::PROPERTY_NOTIFY)
|
||||
{
|
||||
// observe(&service.fromNumChanged);
|
||||
}
|
||||
|
||||
void onRead(BLECharacteristic *c) { DEBUG_MSG("FIXME implement fromnum read\n"); }
|
||||
};
|
||||
|
||||
/*
|
||||
See bluetooth-api.md for documentation.
|
||||
*/
|
||||
BLEService *createMeshBluetoothService(BLEServer *server)
|
||||
{
|
||||
// Only create our phone API object once
|
||||
if (!bluetoothPhoneAPI) {
|
||||
bluetoothPhoneAPI = new BluetoothPhoneAPI();
|
||||
bluetoothPhoneAPI->init();
|
||||
}
|
||||
|
||||
// Create the BLE Service, we need more than the default of 15 handles
|
||||
BLEService *service = server->createService(BLEUUID(MESH_SERVICE_UUID), 30, 0);
|
||||
|
||||
assert(!meshFromNumCharacteristic);
|
||||
meshFromNumCharacteristic = new FromNumCharacteristic;
|
||||
|
||||
addWithDesc(service, meshFromNumCharacteristic, "fromRadio");
|
||||
addWithDesc(service, new ToRadioCharacteristic, "toRadio");
|
||||
addWithDesc(service, new FromRadioCharacteristic, "fromNum");
|
||||
|
||||
meshFromNumCharacteristic->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
||||
|
||||
service->start();
|
||||
|
||||
// We only add to advertisting once, because the ESP32 arduino code is dumb and that object never dies
|
||||
static bool firstTime = true;
|
||||
if (firstTime) {
|
||||
firstTime = false;
|
||||
server->getAdvertising()->addServiceUUID(service->getUUID());
|
||||
}
|
||||
|
||||
DEBUG_MSG("*** Mesh service:\n");
|
||||
service->dump();
|
||||
|
||||
meshService = service;
|
||||
return service;
|
||||
}
|
||||
|
||||
void stopMeshBluetoothService()
|
||||
{
|
||||
assert(meshService);
|
||||
meshService->stop();
|
||||
meshService->executeDelete();
|
||||
}
|
||||
|
||||
void destroyMeshBluetoothService()
|
||||
{
|
||||
assert(meshService);
|
||||
delete meshService;
|
||||
|
||||
meshFromNumCharacteristic = NULL;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEService.h>
|
||||
|
||||
BLEService *createMeshBluetoothService(BLEServer *server);
|
||||
void destroyMeshBluetoothService();
|
||||
|
||||
/**
|
||||
* Tell any bluetooth clients that the number of rx packets has changed
|
||||
*/
|
||||
void bluetoothNotifyFromNum(uint32_t newValue);
|
||||
|
||||
void stopMeshBluetoothService();
|
||||
61
src/esp32/NimbleSoftwareUpdate.c
Normal file
61
src/esp32/NimbleSoftwareUpdate.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
|
||||
// NRF52 wants these constants as byte arrays
|
||||
// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER
|
||||
|
||||
// "cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30"
|
||||
const ble_uuid128_t update_service_uuid =
|
||||
BLE_UUID128_INIT(0x30, 0xee, 0x44, 0x31, 0x2e, 0x44, 0xbb, 0xbd, 0x0d, 0x4c, 0x4c, 0xa8, 0x0b, 0x9a, 0x0b, 0xcb);
|
||||
|
||||
// "e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e" write|read
|
||||
const ble_uuid128_t update_size_uuid =
|
||||
BLE_UUID128_INIT(0x1e, 0x8e, 0xea, 0xdb, 0xe1, 0xf0, 0xa1, 0x95, 0x6f, 0x4a, 0x01, 0xa3, 0xc0, 0xd9, 0x4d, 0xe7);
|
||||
|
||||
// "e272ebac-d463-4b98-bc84-5cc1a39ee517" write
|
||||
const ble_uuid128_t update_data_uuid =
|
||||
BLE_UUID128_INIT(0x17, 0xe5, 0x9e, 0xa3, 0xc1, 0x5c, 0x84, 0xbc, 0x98, 0x4b, 0x63, 0xd4, 0xac, 0xeb, 0x72, 0xe2);
|
||||
|
||||
// "4826129c-c22a-43a3-b066-ce8f0d5bacc6" write
|
||||
const ble_uuid128_t update_crc32_uuid =
|
||||
BLE_UUID128_INIT(0xc6, 0xac, 0x5b, 0x0d, 0x8f, 0xce, 0x66, 0xb0, 0xa3, 0x43, 0x2a, 0xc2, 0x9c, 0x12, 0x26, 0x48);
|
||||
|
||||
// "5e134862-7411-4424-ac4a-210937432c77" read|notify
|
||||
const ble_uuid128_t update_result_uuid =
|
||||
BLE_UUID128_INIT(0x77, 0x2c, 0x43, 0x37, 0x09, 0x21, 0x4a, 0xac, 0x24, 0x44, 0x11, 0x74, 0x62, 0x48, 0x13, 0x5e);
|
||||
|
||||
const struct ble_gatt_svc_def gatt_update_svcs[] = {
|
||||
{
|
||||
/*** Service: Security test. */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = &update_service_uuid.u,
|
||||
.characteristics =
|
||||
(struct ble_gatt_chr_def[]){{
|
||||
.uuid = &update_size_uuid.u,
|
||||
.access_cb = update_size_callback,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_AUTHEN | BLE_GATT_CHR_F_READ |
|
||||
BLE_GATT_CHR_F_READ_AUTHEN,
|
||||
},
|
||||
{
|
||||
.uuid = &update_data_uuid.u,
|
||||
.access_cb = update_data_callback,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_AUTHEN,
|
||||
},
|
||||
{
|
||||
.uuid = &update_crc32_uuid.u,
|
||||
.access_cb = update_crc32_callback,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_AUTHEN,
|
||||
},
|
||||
{
|
||||
.uuid = &update_result_uuid.u,
|
||||
.access_cb = update_size_callback,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_NOTIFY,
|
||||
},
|
||||
{
|
||||
0, /* No more characteristics in this service. */
|
||||
}},
|
||||
},
|
||||
|
||||
{
|
||||
0, /* No more services. */
|
||||
},
|
||||
};
|
||||
@@ -1,59 +1,14 @@
|
||||
#include "BluetoothUtil.h"
|
||||
#include "MeshBluetoothService.h"
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "main.h"
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include "utils.h"
|
||||
|
||||
bool bluetoothOn;
|
||||
|
||||
// This routine is called multiple times, once each time we come back from sleep
|
||||
void reinitBluetooth()
|
||||
{
|
||||
DEBUG_MSG("Starting bluetooth\n");
|
||||
|
||||
// FIXME - we are leaking like crazy
|
||||
// AllocatorScope scope(btPool);
|
||||
|
||||
// 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, optstr(APP_VERSION),
|
||||
optstr(HW_VERSION)); // FIXME, use a real name based on the macaddr
|
||||
createMeshBluetoothService(serve);
|
||||
|
||||
// Start advertising - this must be done _after_ creating all services
|
||||
serve->getAdvertising()->start();
|
||||
}
|
||||
|
||||
// Enable/disable bluetooth.
|
||||
void setBluetoothEnable(bool on)
|
||||
{
|
||||
if (on != bluetoothOn) {
|
||||
DEBUG_MSG("Setting bluetooth enable=%d\n", on);
|
||||
|
||||
bluetoothOn = on;
|
||||
if (on) {
|
||||
Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap());
|
||||
// ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
|
||||
reinitBluetooth();
|
||||
} else {
|
||||
// We have to totally teardown our bluetooth objects to prevent leaks
|
||||
stopMeshBluetoothService(); // Must do before shutting down bluetooth
|
||||
deinitBLE();
|
||||
destroyMeshBluetoothService(); // must do after deinit, because it frees our service
|
||||
Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap());
|
||||
// ESP_ERROR_CHECK( heap_trace_stop() );
|
||||
// heap_trace_dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
#include <nvs.h>
|
||||
#include <nvs_flash.h>
|
||||
|
||||
void getMacAddr(uint8_t *dmac)
|
||||
{
|
||||
@@ -83,13 +38,19 @@ void esp32Setup()
|
||||
DEBUG_MSG("Total PSRAM: %d\n", ESP.getPsramSize());
|
||||
DEBUG_MSG("Free PSRAM: %d\n", ESP.getFreePsram());
|
||||
|
||||
nvs_stats_t nvs_stats;
|
||||
auto res = nvs_get_stats(NULL, &nvs_stats);
|
||||
assert(res == ESP_OK);
|
||||
DEBUG_MSG("NVS: UsedEntries %d, FreeEntries %d, AllEntries %d\n", nvs_stats.used_entries, nvs_stats.free_entries,
|
||||
nvs_stats.total_entries);
|
||||
|
||||
// enableModemSleep();
|
||||
|
||||
// Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any
|
||||
// false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now.
|
||||
#define APP_WATCHDOG_SECS 45
|
||||
|
||||
auto res = esp_task_wdt_init(APP_WATCHDOG_SECS, true);
|
||||
res = esp_task_wdt_init(APP_WATCHDOG_SECS, true);
|
||||
assert(res == ESP_OK);
|
||||
|
||||
res = esp_task_wdt_add(NULL);
|
||||
@@ -121,6 +82,7 @@ void esp32Loop()
|
||||
{
|
||||
esp_task_wdt_reset(); // service our app level watchdog
|
||||
loopBLE();
|
||||
bluetoothRebootCheck();
|
||||
|
||||
// for debug printing
|
||||
// radio.radioIf.canSleep();
|
||||
|
||||
@@ -25,7 +25,7 @@ void readFromRTC();
|
||||
*
|
||||
* When new data is available it will notify observers.
|
||||
*/
|
||||
class GPS : public Observable<void *>
|
||||
class GPS
|
||||
{
|
||||
protected:
|
||||
bool hasValidLocation = false; // default to false, until we complete our first read
|
||||
@@ -48,6 +48,7 @@ class GPS : public Observable<void *>
|
||||
|
||||
virtual ~GPS() {}
|
||||
|
||||
/** We will notify this observable anytime GPS state has changed meaningfully */
|
||||
Observable<const meshtastic::GPSStatus *> newStatus;
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,10 +66,6 @@ void NEMAGPS::loop()
|
||||
|
||||
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
|
||||
DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d, hdop=%f, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5);
|
||||
|
||||
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
|
||||
if (hasValidLocation)
|
||||
notifyObservers(NULL);
|
||||
}
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "UBloxGPS.h"
|
||||
#include "error.h"
|
||||
#include "sleep.h"
|
||||
#include <assert.h>
|
||||
|
||||
@@ -45,45 +46,15 @@ bool UBloxGPS::setup()
|
||||
// ublox.enableDebugging(Serial);
|
||||
|
||||
// try a second time, the ublox lib serial parsing is buggy?
|
||||
if (!tryConnect())
|
||||
tryConnect();
|
||||
// see https://github.com/meshtastic/Meshtastic-device/issues/376
|
||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||
delay(500);
|
||||
|
||||
if (isConnected) {
|
||||
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
|
||||
|
||||
bool factoryReset = false;
|
||||
bool ok;
|
||||
if (factoryReset) {
|
||||
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have
|
||||
// GPS_TX connected)
|
||||
ublox.factoryReset();
|
||||
delay(3000);
|
||||
tryConnect();
|
||||
DEBUG_MSG("Factory reset success=%d\n", isConnected);
|
||||
ok = ublox.saveConfiguration(3000);
|
||||
assert(ok);
|
||||
return false;
|
||||
} else {
|
||||
if (_serial_gps) {
|
||||
ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API
|
||||
assert(ok);
|
||||
}
|
||||
if (i2cAddress) {
|
||||
ok = ublox.setI2COutput(COM_TYPE_UBX, 500);
|
||||
assert(ok);
|
||||
}
|
||||
|
||||
ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low
|
||||
assert(ok);
|
||||
// ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M
|
||||
// assert(ok);
|
||||
// ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds
|
||||
// assert(ok);
|
||||
ok = ublox.powerSaveMode(true, 2000); // use power save mode, the default timeout (1100ms seems a bit too tight)
|
||||
assert(ok);
|
||||
}
|
||||
ok = ublox.saveConfiguration(3000);
|
||||
assert(ok);
|
||||
if (!setUBXMode())
|
||||
recordCriticalError(UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
||||
|
||||
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
||||
|
||||
@@ -93,6 +64,62 @@ bool UBloxGPS::setup()
|
||||
}
|
||||
}
|
||||
|
||||
bool UBloxGPS::setUBXMode()
|
||||
{
|
||||
if (_serial_gps) {
|
||||
if (!ublox.setUART1Output(COM_TYPE_UBX, 1000)) // Use native API
|
||||
return false;
|
||||
}
|
||||
if (i2cAddress) {
|
||||
if (!ublox.setI2COutput(COM_TYPE_UBX, 1000))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ublox.setNavigationFrequency(1, 1000)) // Produce 4x/sec to keep the amount of time we stall in getPVT low
|
||||
return false;
|
||||
|
||||
// ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M
|
||||
// assert(ok);
|
||||
// ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds
|
||||
// assert(ok);
|
||||
|
||||
// per https://github.com/meshtastic/Meshtastic-device/issues/376 powerSaveMode might not work with the marginal
|
||||
// TTGO antennas
|
||||
// if (!ublox.powerSaveMode(true, 2000)) // use power save mode, the default timeout (1100ms seems a bit too tight)
|
||||
// return false;
|
||||
|
||||
if (!ublox.saveConfiguration(3000))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset our GPS back to factory settings
|
||||
*
|
||||
* @return true for success
|
||||
*/
|
||||
bool UBloxGPS::factoryReset()
|
||||
{
|
||||
bool ok = false;
|
||||
|
||||
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have
|
||||
// GPS_TX connected)
|
||||
ublox.factoryReset();
|
||||
delay(5000);
|
||||
tryConnect(); // sets isConnected
|
||||
|
||||
// try a second time, the ublox lib serial parsing is buggy?
|
||||
for (int i = 0; (i < 3) && !tryConnect(); i++)
|
||||
delay(500);
|
||||
|
||||
DEBUG_MSG("GPS Factory reset success=%d\n", isConnected);
|
||||
if (isConnected)
|
||||
ok = setUBXMode();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
int UBloxGPS::prepareSleep(void *unused)
|
||||
{
|
||||
@@ -104,72 +131,72 @@ int UBloxGPS::prepareSleep(void *unused)
|
||||
|
||||
void UBloxGPS::doTask()
|
||||
{
|
||||
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
|
||||
|
||||
assert(isConnected);
|
||||
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
|
||||
|
||||
// Consume all characters that have arrived
|
||||
// if using i2c or serial look too see if any chars are ready
|
||||
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
||||
|
||||
// if using i2c or serial look too see if any chars are ready
|
||||
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
||||
// If we don't have a fix (a quick check), don't try waiting for a solution)
|
||||
// Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions
|
||||
// turn off for now
|
||||
uint16_t maxWait = i2cAddress ? 300 : 0; // If using i2c we must poll with wait
|
||||
fixtype = ublox.getFixType(maxWait);
|
||||
DEBUG_MSG("GPS fix type %d\n", fixtype);
|
||||
|
||||
// If we don't have a fix (a quick check), don't try waiting for a solution)
|
||||
// Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions
|
||||
// turn off for now
|
||||
uint16_t maxWait = i2cAddress ? 300 : 0; // If using i2c we must poll with wait
|
||||
fixtype = ublox.getFixType(maxWait);
|
||||
DEBUG_MSG("GPS fix type %d\n", fixtype);
|
||||
// DEBUG_MSG("sec %d\n", ublox.getSecond());
|
||||
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
|
||||
|
||||
// DEBUG_MSG("sec %d\n", ublox.getSecond());
|
||||
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
|
||||
// any fix that has time
|
||||
|
||||
// any fix that has time
|
||||
if (ublox.getT(maxWait)) {
|
||||
/* Convert to unix time
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January
|
||||
1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
struct tm t;
|
||||
t.tm_sec = ublox.getSecond(0);
|
||||
t.tm_min = ublox.getMinute(0);
|
||||
t.tm_hour = ublox.getHour(0);
|
||||
t.tm_mday = ublox.getDay(0);
|
||||
t.tm_mon = ublox.getMonth(0) - 1;
|
||||
t.tm_year = ublox.getYear(0) - 1900;
|
||||
t.tm_isdst = false;
|
||||
perhapsSetRTC(t);
|
||||
}
|
||||
|
||||
if (ublox.getT(maxWait)) {
|
||||
/* Convert to unix time
|
||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
struct tm t;
|
||||
t.tm_sec = ublox.getSecond(0);
|
||||
t.tm_min = ublox.getMinute(0);
|
||||
t.tm_hour = ublox.getHour(0);
|
||||
t.tm_mday = ublox.getDay(0);
|
||||
t.tm_mon = ublox.getMonth(0) - 1;
|
||||
t.tm_year = ublox.getYear(0) - 1900;
|
||||
t.tm_isdst = false;
|
||||
perhapsSetRTC(t);
|
||||
latitude = ublox.getLatitude(0);
|
||||
longitude = ublox.getLongitude(0);
|
||||
altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters
|
||||
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
||||
heading = ublox.getHeading(0);
|
||||
numSatellites = ublox.getSIV(0);
|
||||
|
||||
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
||||
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
||||
hasValidLocation =
|
||||
(latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000) && (numSatellites > 0);
|
||||
|
||||
// we only notify if position has changed due to a new fix
|
||||
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(maxWait)) // rd fixes only
|
||||
{
|
||||
if (hasValidLocation) {
|
||||
wantNewLocation = false;
|
||||
// ublox.powerOff();
|
||||
}
|
||||
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
||||
wantNewLocation = true;
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::GPSStatus status =
|
||||
meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
|
||||
newStatus.notifyObservers(&status);
|
||||
}
|
||||
|
||||
latitude = ublox.getLatitude(0);
|
||||
longitude = ublox.getLongitude(0);
|
||||
altitude = ublox.getAltitude(0) / 1000; // in mm convert to meters
|
||||
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
||||
heading = ublox.getHeading(0);
|
||||
numSatellites = ublox.getSIV(0);
|
||||
|
||||
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
||||
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
||||
hasValidLocation = (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000) && (numSatellites > 0);
|
||||
|
||||
// we only notify if position has changed due to a new fix
|
||||
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(maxWait)) // rd fixes only
|
||||
{
|
||||
if (hasValidLocation) {
|
||||
wantNewLocation = false;
|
||||
notifyObservers(NULL);
|
||||
// ublox.powerOff();
|
||||
}
|
||||
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
||||
wantNewLocation = true;
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::GPSStatus status =
|
||||
meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
|
||||
newStatus.notifyObservers(&status);
|
||||
|
||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 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 10s until we have something
|
||||
// over the serial
|
||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/PeriodicTask.h"
|
||||
#include "GPS.h"
|
||||
#include "Observer.h"
|
||||
#include "../concurrency/PeriodicTask.h"
|
||||
#include "SparkFun_Ublox_Arduino_Library.h"
|
||||
|
||||
/**
|
||||
@@ -33,12 +33,22 @@ class UBloxGPS : public GPS, public concurrency::PeriodicTask
|
||||
* called after the CPU wakes from light-sleep state */
|
||||
virtual void startLock();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Reset our GPS back to factory settings
|
||||
*
|
||||
* @return true for success
|
||||
*/
|
||||
bool factoryReset();
|
||||
|
||||
private:
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
/// always returns 0 to indicate okay to sleep
|
||||
int prepareSleep(void *unused);
|
||||
|
||||
/// Attempt to connect to our GPS, returns false if no gps is present
|
||||
bool tryConnect();
|
||||
|
||||
/// Switch to our desired operating mode and save the settings to flash
|
||||
/// returns true for success
|
||||
bool setUBXMode();
|
||||
};
|
||||
|
||||
@@ -25,13 +25,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "GPS.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "Screen.h"
|
||||
#include "configs.h"
|
||||
#include "configuration.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "Screen.h"
|
||||
#include "utils.h"
|
||||
#include "configs.h"
|
||||
|
||||
using namespace meshtastic; /** @todo remove */
|
||||
|
||||
@@ -43,10 +43,14 @@ static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||
static char btPIN[16] = "888888";
|
||||
|
||||
uint8_t imgBattery[16] = { 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C };
|
||||
uint8_t imgSatellite[8] = { 0x70, 0x71, 0x22, 0xFA, 0xFA, 0x22, 0x71, 0x70 };
|
||||
// This image definition is here instead of images.h because it's modified dynamically by the drawBattery function
|
||||
uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C};
|
||||
|
||||
uint32_t dopThresholds[5] = { 2000, 1000, 500, 200, 100 };
|
||||
// Threshold values for the GPS lock accuracy bar display
|
||||
uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100};
|
||||
|
||||
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
|
||||
static char ourId[5];
|
||||
|
||||
#ifdef SHOW_REDRAWS
|
||||
static bool heartbeat = false;
|
||||
@@ -116,7 +120,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
assert(mp.decoded.which_payload == SubPacket_data_tag);
|
||||
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.data.payload.bytes);
|
||||
|
||||
display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf);
|
||||
display->drawStringMaxWidth(4 + x, 10 + y, SCREEN_WIDTH - (6 + x), tempBuf);
|
||||
}
|
||||
|
||||
/// Draw a series of fields in a column, wrapping to multiple colums if needed
|
||||
@@ -204,42 +208,54 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *no
|
||||
// Draw GPS status summary
|
||||
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||
{
|
||||
if (!gps->getIsConnected())
|
||||
{
|
||||
if (!gps->getIsConnected()) {
|
||||
display->drawString(x, y - 2, "No GPS");
|
||||
return;
|
||||
}
|
||||
display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty);
|
||||
if (!gps->getHasLock())
|
||||
{
|
||||
if (!gps->getHasLock()) {
|
||||
display->drawString(x + 8, y - 2, "No sats");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
char satsString[3];
|
||||
uint8_t bar[2] = { 0 };
|
||||
uint8_t bar[2] = {0};
|
||||
|
||||
//Draw DOP signal bars
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
// Draw DOP signal bars
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (gps->getDOP() <= dopThresholds[i])
|
||||
bar[0] = ~((1 << (5 - i)) - 1);
|
||||
else
|
||||
bar[0] = 0b10000000;
|
||||
//bar[1] = bar[0];
|
||||
// bar[1] = bar[0];
|
||||
display->drawFastImage(x + 9 + (i * 2), y, 2, 8, bar);
|
||||
}
|
||||
|
||||
//Draw satellite image
|
||||
// Draw satellite image
|
||||
display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
|
||||
|
||||
//Draw the number of satellites
|
||||
// Draw the number of satellites
|
||||
sprintf(satsString, "%d", gps->getNumSatellites());
|
||||
display->drawString(x + 34, y - 2, satsString);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw GPS status coordinates
|
||||
static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||
{
|
||||
String displayLine = "";
|
||||
if (!gps->getIsConnected()) {
|
||||
displayLine = "No GPS Module";
|
||||
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
|
||||
} else if (!gps->getHasLock()) {
|
||||
displayLine = "No GPS Lock";
|
||||
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
|
||||
} else {
|
||||
char coordinateLine[22];
|
||||
sprintf(coordinateLine, "%f %f", gps->getLatitude() * 1e-7, gps->getLongitude() * 1e-7);
|
||||
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ported from my old java code, returns distance in meters along the globe
|
||||
/// surface (by magic?)
|
||||
static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
||||
@@ -386,7 +402,6 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp
|
||||
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
||||
Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY);
|
||||
|
||||
|
||||
Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
@@ -402,13 +417,13 @@ static void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t comp
|
||||
// Draw the compass heading
|
||||
static void drawCompassHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading)
|
||||
{
|
||||
Point N1(-0.04f, -0.65f), N2( 0.04f, -0.65f);
|
||||
Point N3(-0.04f, -0.55f), N4( 0.04f, -0.55f);
|
||||
Point N1(-0.04f, -0.65f), N2(0.04f, -0.65f);
|
||||
Point N3(-0.04f, -0.55f), N4(0.04f, -0.55f);
|
||||
Point *rosePoints[] = {&N1, &N2, &N3, &N4};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
rosePoints[i]->rotate(myHeading);
|
||||
rosePoints[i]->scale(COMPASS_DIAM);
|
||||
rosePoints[i]->scale(-1 * COMPASS_DIAM);
|
||||
rosePoints[i]->translate(compassX, compassY);
|
||||
}
|
||||
drawLine(display, N1, N3);
|
||||
@@ -436,8 +451,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
displayedNodeNum = n->num;
|
||||
|
||||
// We just changed to a new node screen, ask that node for updated state if it's older than 2 minutes
|
||||
if(sinceLastSeen(n) > 120)
|
||||
{
|
||||
if (sinceLastSeen(n) > 120) {
|
||||
service.sendNetworkPing(displayedNodeNum, true);
|
||||
}
|
||||
}
|
||||
@@ -473,14 +487,12 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 5, compassY = y + SCREEN_HEIGHT / 2;
|
||||
bool hasNodeHeading = false;
|
||||
|
||||
if(ourNode && hasPosition(ourNode))
|
||||
{
|
||||
if (ourNode && hasPosition(ourNode)) {
|
||||
Position &op = ourNode->position;
|
||||
float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
drawCompassHeading(display, compassX, compassY, myHeading);
|
||||
|
||||
if(hasPosition(node))
|
||||
{
|
||||
if (hasPosition(node)) {
|
||||
// display direction toward node
|
||||
hasNodeHeading = true;
|
||||
Position &p = node->position;
|
||||
@@ -495,16 +507,15 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
headingRadian = bearingToOther - myHeading;
|
||||
drawNodeHeading(display, compassX, compassY, headingRadian);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!hasNodeHeading)
|
||||
if (!hasNodeHeading)
|
||||
// direction to node is unknown so display question mark
|
||||
// Debug info for gps lock errors
|
||||
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
||||
display->drawString(compassX - FONT_HEIGHT / 4, compassY - FONT_HEIGHT / 2, "?");
|
||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||
|
||||
|
||||
// Must be after distStr is populated
|
||||
drawColumns(display, x, y, fields);
|
||||
}
|
||||
@@ -594,6 +605,11 @@ void Screen::setup()
|
||||
dispdev.flipScreenVertically();
|
||||
#endif
|
||||
|
||||
// Get our hardware ID
|
||||
uint8_t dmac[6];
|
||||
getMacAddr(dmac);
|
||||
sprintf(ourId, "%02x%02x", dmac[4], dmac[5]);
|
||||
|
||||
// Turn on the display.
|
||||
handleSetOn(true);
|
||||
|
||||
@@ -686,6 +702,13 @@ void Screen::drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
screen->debugInfo.drawFrame(display, state, x, y);
|
||||
}
|
||||
|
||||
void Screen::drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
Screen *screen = reinterpret_cast<Screen *>(state->userData);
|
||||
screen->debugInfo.drawFrameSettings(display, state, x, y);
|
||||
}
|
||||
|
||||
|
||||
// restore our regular frame list
|
||||
void Screen::setFrames()
|
||||
{
|
||||
@@ -713,6 +736,9 @@ void Screen::setFrames()
|
||||
// call a method on debugInfo object.
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline;
|
||||
|
||||
// call a method on debugInfoScreen object (for more details)
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
|
||||
|
||||
ui.setFrames(normalFrames, numframes);
|
||||
ui.enableAllIndicators();
|
||||
|
||||
@@ -770,12 +796,12 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
char channelStr[20];
|
||||
{
|
||||
concurrency::LockGuard guard(&lock);
|
||||
snprintf(channelStr, sizeof(channelStr), "#%s", channelName.c_str());
|
||||
snprintf(channelStr, sizeof(channelStr), "%s", channelName.c_str());
|
||||
|
||||
// Display power status
|
||||
if (powerStatus->getHasBattery())
|
||||
drawBattery(display, x, y + 2, imgBattery, powerStatus);
|
||||
else
|
||||
else if (powerStatus->knowsUSB())
|
||||
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
|
||||
// Display nodes status
|
||||
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
|
||||
@@ -783,8 +809,13 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
||||
}
|
||||
|
||||
// Draw the channel name
|
||||
display->drawString(x, y + FONT_HEIGHT, channelStr);
|
||||
// Draw our hardware ID to assist with bluetooth pairing
|
||||
display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT, 8, 8, imgInfo);
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT, ourId);
|
||||
|
||||
// Draw any log messages
|
||||
display->drawLogBuffer(x, y + (FONT_HEIGHT * 2));
|
||||
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
@@ -795,6 +826,74 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
#endif
|
||||
}
|
||||
|
||||
// Jm
|
||||
void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
displayedNodeNum = 0; // Not currently showing a node pane
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
char batStr[20];
|
||||
if (powerStatus->getHasBattery())
|
||||
{
|
||||
int batV = powerStatus->getBatteryVoltageMv() / 1000;
|
||||
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
|
||||
|
||||
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c",
|
||||
batV,
|
||||
batCv,
|
||||
powerStatus->getBatteryChargePercent(),
|
||||
powerStatus->getIsCharging() ? '+' : ' ',
|
||||
powerStatus->getHasUSB() ? 'U' : ' ');
|
||||
|
||||
// Line 1
|
||||
display->drawString(x, y, batStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Line 1
|
||||
display->drawString(x, y, String("USB"));
|
||||
}
|
||||
|
||||
|
||||
//TODO: Display status of the BT radio
|
||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth("BT On"), y, "BT On");
|
||||
|
||||
// Line 2
|
||||
uint32_t currentMillis = millis();
|
||||
uint32_t seconds = currentMillis / 1000;
|
||||
uint32_t minutes = seconds / 60;
|
||||
uint32_t hours = minutes / 60;
|
||||
uint32_t days = hours / 24;
|
||||
currentMillis %= 1000;
|
||||
seconds %= 60;
|
||||
minutes %= 60;
|
||||
hours %= 24;
|
||||
|
||||
display->drawString(x, y + FONT_HEIGHT * 1, String(days) + "d "
|
||||
+ (hours < 10 ? "0" : "") + String(hours) + ":"
|
||||
+ (minutes < 10 ? "0" : "") + String(minutes) + ":"
|
||||
+ (seconds < 10 ? "0" : "") + String(seconds));
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("Mode " + String(channelSettings.modem_config)), y + FONT_HEIGHT * 1, "Mode " + String(channelSettings.modem_config));
|
||||
|
||||
// Line 3
|
||||
// TODO: Use this line for WiFi information.
|
||||
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth("WiFi: 192.168.0.100"))) / 2, y + FONT_HEIGHT * 2, "WiFi: 192.168.0.100");
|
||||
|
||||
// Line 4
|
||||
drawGPScoordinates(display, x, y + FONT_HEIGHT * 3, gpsStatus);
|
||||
|
||||
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
if (heartbeat)
|
||||
display->setPixel(0, 0);
|
||||
heartbeat = !heartbeat;
|
||||
#endif
|
||||
}
|
||||
// adjust Brightness cycle trough 1 to 254 as long as attachDuringLongPress is true
|
||||
void Screen::adjustBrightness()
|
||||
{
|
||||
@@ -810,20 +909,21 @@ void Screen::adjustBrightness()
|
||||
dispdev.setBrightness(brightness);
|
||||
}
|
||||
|
||||
int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
{
|
||||
//DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
||||
switch(arg->getStatusType())
|
||||
{
|
||||
case STATUS_TYPE_NODE:
|
||||
if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal())
|
||||
setFrames();
|
||||
prevFrame = -1;
|
||||
nodeDB.updateGUI = false;
|
||||
nodeDB.updateTextMessage = false;
|
||||
break;
|
||||
// DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
||||
switch (arg->getStatusType()) {
|
||||
case STATUS_TYPE_NODE:
|
||||
if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
|
||||
setFrames(); // Regen the list of screens
|
||||
prevFrame = -1; // Force a GUI update
|
||||
setPeriod(1); // Update the screen right away
|
||||
}
|
||||
nodeDB.updateGUI = false;
|
||||
nodeDB.updateTextMessage = false;
|
||||
break;
|
||||
}
|
||||
setPeriod(1); // Update the screen right away
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace graphics
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
#include <SSD1306Wire.h>
|
||||
#endif
|
||||
|
||||
#include "concurrency/PeriodicTask.h"
|
||||
#include "TFT.h"
|
||||
#include "TypedQueue.h"
|
||||
#include "concurrency/LockGuard.h"
|
||||
#include "power.h"
|
||||
#include "commands.h"
|
||||
#include "concurrency/LockGuard.h"
|
||||
#include "concurrency/PeriodicTask.h"
|
||||
#include "power.h"
|
||||
#include <string>
|
||||
|
||||
namespace graphics
|
||||
@@ -44,6 +45,8 @@ class DebugInfo
|
||||
|
||||
/// Renders the debug screen.
|
||||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
|
||||
std::string channelName;
|
||||
|
||||
@@ -53,16 +56,19 @@ class DebugInfo
|
||||
|
||||
/**
|
||||
* @brief This class deals with showing things on the screen of the device.
|
||||
*
|
||||
* @details Other than setup(), this class is thread-safe as long as drawFrame is not called
|
||||
* multiple times simultaneously. All state-changing calls are queued and executed
|
||||
*
|
||||
* @details Other than setup(), this class is thread-safe as long as drawFrame is not called
|
||||
* multiple times simultaneously. All state-changing calls are queued and executed
|
||||
* when the main loop calls us.
|
||||
*/
|
||||
class Screen : public concurrency::PeriodicTask
|
||||
{
|
||||
CallbackObserver<Screen, const meshtastic::Status *> powerStatusObserver = CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const meshtastic::Status *> gpsStatusObserver = CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const meshtastic::Status *> nodeStatusObserver = CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const meshtastic::Status *> powerStatusObserver =
|
||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const meshtastic::Status *> gpsStatusObserver =
|
||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||
|
||||
public:
|
||||
Screen(uint8_t address, int sda = -1, int scl = -1);
|
||||
@@ -125,11 +131,12 @@ class Screen : public concurrency::PeriodicTask
|
||||
}
|
||||
|
||||
/// Overrides the default utf8 character conversion, to replace empty space with question marks
|
||||
static char customFontTableLookup(const uint8_t ch) {
|
||||
static char customFontTableLookup(const uint8_t ch)
|
||||
{
|
||||
// UTF-8 to font table index converter
|
||||
// Code form http://playground.arduino.cc/Main/Utf8ascii
|
||||
static uint8_t LASTCHAR;
|
||||
static bool SKIPREST; // Only display a single unconvertable-character symbol per sequence of unconvertable characters
|
||||
static bool SKIPREST; // Only display a single unconvertable-character symbol per sequence of unconvertable characters
|
||||
|
||||
if (ch < 128) { // Standard ASCII-set 0..0x7F handling
|
||||
LASTCHAR = 0;
|
||||
@@ -137,28 +144,38 @@ class Screen : public concurrency::PeriodicTask
|
||||
return ch;
|
||||
}
|
||||
|
||||
uint8_t last = LASTCHAR; // get last char
|
||||
uint8_t last = LASTCHAR; // get last char
|
||||
LASTCHAR = ch;
|
||||
|
||||
switch (last) { // conversion depnding on first UTF8-character
|
||||
case 0xC2: { SKIPREST = false; return (uint8_t) ch; }
|
||||
case 0xC3: { SKIPREST = false; return (uint8_t) (ch | 0xC0); }
|
||||
switch (last) { // conversion depnding on first UTF8-character
|
||||
case 0xC2: {
|
||||
SKIPREST = false;
|
||||
return (uint8_t)ch;
|
||||
}
|
||||
case 0xC3: {
|
||||
SKIPREST = false;
|
||||
return (uint8_t)(ch | 0xC0);
|
||||
}
|
||||
}
|
||||
|
||||
// We want to strip out prefix chars for two-byte char formats
|
||||
if (ch == 0xC2 || ch == 0xC3 || ch == 0x82) return (uint8_t) 0;
|
||||
if (ch == 0xC2 || ch == 0xC3 || ch == 0x82)
|
||||
return (uint8_t)0;
|
||||
|
||||
// If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the rest of it
|
||||
if (SKIPREST) return (uint8_t) 0;
|
||||
// If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the
|
||||
// rest of it
|
||||
if (SKIPREST)
|
||||
return (uint8_t)0;
|
||||
SKIPREST = true;
|
||||
|
||||
return (uint8_t) 191; // otherwise: return ¿ if character can't be converted (note that the font map we're using doesn't stick to standard EASCII codes)
|
||||
return (uint8_t)191; // otherwise: return ¿ if character can't be converted (note that the font map we're using doesn't
|
||||
// stick to standard EASCII codes)
|
||||
}
|
||||
|
||||
/// Returns a handle to the DebugInfo screen.
|
||||
//
|
||||
// Use this handle to set things like battery status, user count, GPS status, etc.
|
||||
DebugInfo* debug_info() { return &debugInfo; }
|
||||
DebugInfo *debug_info() { return &debugInfo; }
|
||||
|
||||
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||
|
||||
@@ -201,6 +218,8 @@ class Screen : public concurrency::PeriodicTask
|
||||
/// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame.
|
||||
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
static void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
/// Queue of commands to execute in doTask.
|
||||
TypedQueue<ScreenCmd> cmdQueue;
|
||||
/// Whether we are using a display
|
||||
@@ -215,8 +234,10 @@ class Screen : public concurrency::PeriodicTask
|
||||
DebugInfo debugInfo;
|
||||
|
||||
/// Display device
|
||||
/** @todo display abstraction */
|
||||
#ifdef USE_SH1106
|
||||
/** FIXME cleanup display abstraction */
|
||||
#ifdef ST7735_CS
|
||||
TFTDisplay dispdev;
|
||||
#elif defined(USE_SH1106)
|
||||
SH1106Wire dispdev;
|
||||
#else
|
||||
SSD1306Wire dispdev;
|
||||
|
||||
65
src/graphics/TFT.cpp
Normal file
65
src/graphics/TFT.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#ifdef ST7735_CS
|
||||
#include "SPILock.h"
|
||||
#include "TFT.h"
|
||||
#include "graphics/configs.h"
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h> // Graphics and font library for ST7735 driver chip
|
||||
|
||||
static TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h
|
||||
|
||||
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl)
|
||||
{
|
||||
setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will
|
||||
// work ie GEOMETRY_RAWMODE
|
||||
}
|
||||
|
||||
// Write the buffer to the display memory
|
||||
void TFTDisplay::display(void)
|
||||
{
|
||||
concurrency::LockGuard g(spiLock);
|
||||
|
||||
#if 1
|
||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||
// tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
|
||||
for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) {
|
||||
for (uint8_t x = 0; x < SCREEN_WIDTH; x++) {
|
||||
|
||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
||||
auto b = buffer[x + (y / 8) * SCREEN_WIDTH];
|
||||
auto isset = b & (1 << (y & 7));
|
||||
tft.drawPixel(x, y, isset ? TFT_WHITE : TFT_BLACK);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Send a command to the display (low level function)
|
||||
void TFTDisplay::sendCommand(uint8_t com)
|
||||
{
|
||||
(void)com;
|
||||
// Drop all commands to device (we just update the buffer)
|
||||
}
|
||||
|
||||
// Connect to the display
|
||||
bool TFTDisplay::connect()
|
||||
{
|
||||
DEBUG_MSG("Doing TFT init\n");
|
||||
|
||||
#ifdef ST7735_BACKLIGHT_EN
|
||||
digitalWrite(ST7735_BACKLIGHT_EN, HIGH);
|
||||
pinMode(ST7735_BACKLIGHT_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
tft.init();
|
||||
tft.setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
// tft.drawRect(0, 0, 40, 10, TFT_PURPLE); // wide rectangle in upper left
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
35
src/graphics/TFT.h
Normal file
35
src/graphics/TFT.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <OLEDDisplay.h>
|
||||
|
||||
/**
|
||||
* An adapter class that allows using the TFT_eSPI library as if it was an OLEDDisplay implementation.
|
||||
*
|
||||
* Remaining TODO:
|
||||
* optimize display() to only draw changed pixels (see other OLED subclasses for examples)
|
||||
* implement displayOn/displayOff to turn off the TFT device (and backlight)
|
||||
* Use the fast NRF52 SPI API rather than the slow standard arduino version
|
||||
*
|
||||
* turn radio back on - currently with both on spi bus is fucked? or are we leaving chip select asserted?
|
||||
*/
|
||||
class TFTDisplay : public OLEDDisplay
|
||||
{
|
||||
public:
|
||||
/* constructor
|
||||
FIXME - the parameters are not used, just a temporary hack to keep working like the old displays
|
||||
*/
|
||||
TFTDisplay(uint8_t address, int sda, int scl);
|
||||
|
||||
// Write the buffer to the display memory
|
||||
virtual void display(void);
|
||||
|
||||
protected:
|
||||
// the header size of the buffer used, e.g. for the SPI command header
|
||||
virtual int getBufferOffset(void) { return 0; }
|
||||
|
||||
// Send a command to the display (low level function)
|
||||
virtual void sendCommand(uint8_t com);
|
||||
|
||||
// Connect to the display
|
||||
virtual bool connect();
|
||||
};
|
||||
@@ -6,11 +6,13 @@ const uint8_t SATELLITE_IMAGE[] PROGMEM = {0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0
|
||||
0xF8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54,
|
||||
0x0E, 0x52, 0x07, 0x48, 0x02, 0x26, 0x00, 0x10, 0x00, 0x0E};
|
||||
|
||||
const uint8_t imgSatellite[] PROGMEM = { 0x70, 0x71, 0x22, 0xFA, 0xFA, 0x22, 0x71, 0x70 };
|
||||
const uint8_t imgUSB[] PROGMEM = { 0x60, 0x60, 0x30, 0x18, 0x18, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x24, 0x24, 0x24, 0x3C };
|
||||
const uint8_t imgPower[] PROGMEM = { 0x40, 0x40, 0x40, 0x58, 0x48, 0x08, 0x08, 0x08, 0x1C, 0x22, 0x22, 0x41, 0x7F, 0x22, 0x22, 0x22 };
|
||||
const uint8_t imgUser[] PROGMEM = { 0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3C };
|
||||
const uint8_t imgPositionEmpty[] PROGMEM = { 0x20, 0x30, 0x28, 0x24, 0x42, 0xFF };
|
||||
const uint8_t imgPositionSolid[] PROGMEM = { 0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF };
|
||||
const uint8_t imgUser[] PROGMEM = { 0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3C };
|
||||
const uint8_t imgPositionEmpty[] PROGMEM = { 0x20, 0x30, 0x28, 0x24, 0x42, 0xFF };
|
||||
const uint8_t imgPositionSolid[] PROGMEM = { 0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF };
|
||||
const uint8_t imgInfo[] PROGMEM = { 0xFF, 0x81, 0x81, 0xB5, 0xB5, 0x81, 0x81, 0xFF };
|
||||
|
||||
#include "img/icon.xbm"
|
||||
|
||||
|
||||
125
src/main.cpp
125
src/main.cpp
@@ -34,6 +34,7 @@
|
||||
// #include "rom/rtc.h"
|
||||
#include "DSRRouter.h"
|
||||
// #include "debug.h"
|
||||
#include "SPILock.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "sleep.h"
|
||||
@@ -43,8 +44,7 @@
|
||||
// #include <driver/rtc_io.h>
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "BluetoothUtil.h"
|
||||
#include "WiFi.h"
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
#endif
|
||||
|
||||
#include "RF95Interface.h"
|
||||
@@ -149,35 +149,6 @@ void userButtonPressedLong()
|
||||
screen.adjustBrightness();
|
||||
}
|
||||
|
||||
#ifndef NO_ESP32
|
||||
void initWifi()
|
||||
{
|
||||
#if 0
|
||||
// strcpy(radioConfig.preferences.wifi_ssid, "xxx");
|
||||
// strcpy(radioConfig.preferences.wifi_password, "xxx");
|
||||
if (radioConfig.has_preferences) {
|
||||
const char *wifiName = radioConfig.preferences.wifi_ssid;
|
||||
|
||||
if (*wifiName) {
|
||||
const char *wifiPsw = radioConfig.preferences.wifi_password;
|
||||
if (radioConfig.preferences.wifi_ap_mode) {
|
||||
DEBUG_MSG("STARTING WIFI AP: ssid=%s, ok=%d\n", wifiName, WiFi.softAP(wifiName, wifiPsw));
|
||||
} else {
|
||||
WiFi.mode(WIFI_MODE_STA);
|
||||
DEBUG_MSG("JOINING WIFI: ssid=%s\n", wifiName);
|
||||
if (WiFi.begin(wifiName, wifiPsw) == WL_CONNECTED) {
|
||||
DEBUG_MSG("MY IP ADDRESS: %s\n", WiFi.localIP().toString().c_str());
|
||||
} else {
|
||||
DEBUG_MSG("FAILED JOINING WIFI\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
DEBUG_MSG("Not using WIFI\n");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void setup()
|
||||
{
|
||||
#ifdef USE_SEGGER
|
||||
@@ -240,21 +211,33 @@ void setup()
|
||||
esp32Setup();
|
||||
#endif
|
||||
|
||||
#ifdef TBEAM_V10
|
||||
// Currently only the tbeam has a PMU
|
||||
power = new Power();
|
||||
power->setup();
|
||||
power->setStatusHandler(powerStatus);
|
||||
powerStatus->observe(&power->newStatus);
|
||||
#endif
|
||||
|
||||
#ifdef NRF52_SERIES
|
||||
nrf52Setup();
|
||||
#endif
|
||||
|
||||
// Init our SPI controller (must be before screen and lora)
|
||||
initSPI();
|
||||
#ifdef NRF52_SERIES
|
||||
SPI.begin();
|
||||
#else
|
||||
// ESP32
|
||||
SPI.begin(RF95_SCK, RF95_MISO, RF95_MOSI, RF95_NSS);
|
||||
SPI.setFrequency(4000000);
|
||||
#endif
|
||||
|
||||
// Initialize the screen first so we can show the logo while we start up everything else.
|
||||
#ifdef ST7735_CS
|
||||
screen.setup();
|
||||
#else
|
||||
if (ssd1306_found)
|
||||
screen.setup();
|
||||
#endif
|
||||
|
||||
screen.print("Started...\n");
|
||||
|
||||
@@ -263,15 +246,18 @@ void setup()
|
||||
// If we know we have a L80 GPS, don't try UBLOX
|
||||
#ifndef L80_RESET
|
||||
// Init GPS - first try ublox
|
||||
gps = new UBloxGPS();
|
||||
auto ublox = new UBloxGPS();
|
||||
gps = ublox;
|
||||
if (!gps->setup()) {
|
||||
DEBUG_MSG("ERROR: No UBLOX GPS found\n");
|
||||
|
||||
delete ublox;
|
||||
gps = ublox = NULL;
|
||||
|
||||
if (GPS::_serial_gps) {
|
||||
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
||||
// assume NEMA at 9600 baud.
|
||||
DEBUG_MSG("Hoping that NEMA might work\n");
|
||||
delete gps;
|
||||
|
||||
// dumb NEMA access only work for serial GPSes)
|
||||
gps = new NEMAGPS();
|
||||
@@ -286,10 +272,16 @@ void setup()
|
||||
nodeStatus->observe(&nodeDB.newStatus);
|
||||
|
||||
service.init();
|
||||
#ifndef NO_ESP32
|
||||
// Must be after we init the service, because the wifi settings are loaded by NodeDB (oops)
|
||||
initWifi();
|
||||
#endif
|
||||
|
||||
// We have now loaded our saved preferences from flash
|
||||
|
||||
// ONCE we will factory reset the GPS for bug #327
|
||||
if (ublox && !devicestate.did_gps_reset) {
|
||||
if (ublox->factoryReset()) { // If we don't succeed try again next time
|
||||
devicestate.did_gps_reset = true;
|
||||
nodeDB.saveToDisk();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SX1262_ANT_SW
|
||||
// make analog PA vs not PA switch on SX1262 eval board work properly
|
||||
@@ -297,27 +289,43 @@ void setup()
|
||||
digitalWrite(SX1262_ANT_SW, 1);
|
||||
#endif
|
||||
|
||||
// Init our SPI controller
|
||||
#ifdef NRF52_SERIES
|
||||
SPI.begin();
|
||||
#else
|
||||
// ESP32
|
||||
SPI.begin(RF95_SCK, RF95_MISO, RF95_MOSI, RF95_NSS);
|
||||
SPI.setFrequency(4000000);
|
||||
#endif
|
||||
|
||||
// MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
||||
RadioInterface *rIf =
|
||||
RadioInterface *rIf = NULL;
|
||||
|
||||
#if defined(RF95_IRQ)
|
||||
// new CustomRF95(); old Radiohead based driver
|
||||
new RF95Interface(RF95_NSS, RF95_IRQ, RF95_RESET, SPI);
|
||||
#elif defined(SX1262_CS)
|
||||
new SX1262Interface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI);
|
||||
#else
|
||||
new SimRadio();
|
||||
if (!rIf) {
|
||||
rIf = new RF95Interface(RF95_NSS, RF95_IRQ, RF95_RESET, SPI);
|
||||
if (!rIf->init()) {
|
||||
DEBUG_MSG("Warning: Failed to find RF95 radio\n");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!rIf || !rIf->init())
|
||||
#if defined(SX1262_CS)
|
||||
if (!rIf) {
|
||||
rIf = new SX1262Interface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI);
|
||||
if (!rIf->init()) {
|
||||
DEBUG_MSG("Warning: Failed to find SX1262 radio\n");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SIM_RADIO
|
||||
if (!rIf) {
|
||||
rIf = new SimRadio;
|
||||
if (!rIf->init()) {
|
||||
DEBUG_MSG("Warning: Failed to find simulated radio\n");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!rIf)
|
||||
recordCriticalError(ErrNoRadio);
|
||||
else
|
||||
router.addInterface(rIf);
|
||||
@@ -398,9 +406,8 @@ void loop()
|
||||
#endif
|
||||
|
||||
// Update the screen last, after we've figured out what to show.
|
||||
screen.debug_info()->setChannelNameStatus(channelSettings.name);
|
||||
// screen.debug()->setPowerStatus(powerStatus);
|
||||
|
||||
screen.debug_info()->setChannelNameStatus(getChannelName());
|
||||
|
||||
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
||||
// i.e. don't just keep spinning in loop as fast as we can.
|
||||
// DEBUG_MSG("msecs %d\n", msecstosleep);
|
||||
|
||||
@@ -26,11 +26,16 @@
|
||||
#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510
|
||||
#define NUM_CHANNELS_CN 20
|
||||
|
||||
// JP channel settings
|
||||
#define CH0_JP 920.0f // MHz
|
||||
#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925
|
||||
// JP channel settings (AS1 bandplan)
|
||||
#define CH0_JP 920.0f // MHz
|
||||
#define CH_SPACING_JP 0.5f
|
||||
#define NUM_CHANNELS_JP 10
|
||||
|
||||
// TW channel settings (AS2 bandplan 923-925MHz)
|
||||
#define CH0_TW 923.0f // MHz
|
||||
#define CH_SPACING_TW 0.2
|
||||
#define NUM_CHANNELS_TW 10
|
||||
|
||||
// FIXME add defs for other regions and use them here
|
||||
#ifdef HW_VERSION_US
|
||||
#define CH0 CH0_US
|
||||
@@ -49,13 +54,18 @@
|
||||
#define CH_SPACING CH_SPACING_CN
|
||||
#define NUM_CHANNELS NUM_CHANNELS_CN
|
||||
#elif defined(HW_VERSION_JP)
|
||||
// Also called AS1 bandplan
|
||||
#define CH0 CH0_JP
|
||||
#define CH_SPACING CH_SPACING_JP
|
||||
#define NUM_CHANNELS NUM_CHANNELS_JP
|
||||
#elif defined(HW_VERSION_TW)
|
||||
// Also called AS2 bandplan
|
||||
#define CH0 CH0_TW
|
||||
#define CH_SPACING CH_SPACING_TW
|
||||
#define NUM_CHANNELS NUM_CHANNELS_TW
|
||||
#else
|
||||
// HW version not set - assume US
|
||||
#define CH0 CH0_US
|
||||
#define CH_SPACING CH_SPACING_US
|
||||
#define NUM_CHANNELS NUM_CHANNELS_US
|
||||
#endif
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@ void MeshService::init()
|
||||
sendOwnerPeriod.setup();
|
||||
nodeDB.init();
|
||||
|
||||
gpsObserver.observe(gps);
|
||||
assert(gps);
|
||||
gpsObserver.observe(&gps->newStatus);
|
||||
packetReceivedObserver.observe(&router.notifyPacketReceived);
|
||||
}
|
||||
|
||||
@@ -283,9 +284,8 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
|
||||
sendToMesh(p);
|
||||
}
|
||||
|
||||
int MeshService::onGPSChanged(void *unused)
|
||||
int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
||||
{
|
||||
// DEBUG_MSG("got gps notify\n");
|
||||
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
MeshPacket *p = router.allocForSending();
|
||||
@@ -305,6 +305,8 @@ int MeshService::onGPSChanged(void *unused)
|
||||
pos.battery_level = powerStatus->getBatteryChargePercent();
|
||||
updateBatteryLevel(pos.battery_level);
|
||||
|
||||
// DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.latitude_i, pos.time, pos.battery_level);
|
||||
|
||||
// We limit our GPS broadcasts to a max rate
|
||||
static uint32_t lastGpsSend;
|
||||
uint32_t now = timing::millis();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <assert.h>
|
||||
#include <string>
|
||||
|
||||
#include "GPSStatus.h"
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "MeshTypes.h"
|
||||
@@ -17,7 +18,8 @@
|
||||
*/
|
||||
class MeshService
|
||||
{
|
||||
CallbackObserver<MeshService, void *> gpsObserver = CallbackObserver<MeshService, void *>(this, &MeshService::onGPSChanged);
|
||||
CallbackObserver<MeshService, const meshtastic::GPSStatus *> gpsObserver =
|
||||
CallbackObserver<MeshService, const meshtastic::GPSStatus *>(this, &MeshService::onGPSChanged);
|
||||
CallbackObserver<MeshService, const MeshPacket *> packetReceivedObserver =
|
||||
CallbackObserver<MeshService, const MeshPacket *>(this, &MeshService::handleFromRadio);
|
||||
|
||||
@@ -85,7 +87,7 @@ class MeshService
|
||||
|
||||
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
|
||||
/// returns 0 to allow futher processing
|
||||
int onGPSChanged(void *arg);
|
||||
int onGPSChanged(const meshtastic::GPSStatus *arg);
|
||||
|
||||
/// Handle a packet that just arrived from the radio. This method does _not_ free the provided packet. If it needs
|
||||
/// to keep the packet around it makes a copy
|
||||
|
||||
@@ -29,7 +29,7 @@ DeviceState versions used to be defined in the .proto file but really only this
|
||||
#define here.
|
||||
*/
|
||||
|
||||
#define DEVICESTATE_CUR_VER 10
|
||||
#define DEVICESTATE_CUR_VER 11
|
||||
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
||||
|
||||
#ifndef NO_ESP32
|
||||
@@ -66,6 +66,33 @@ static uint8_t ourMacAddr[6];
|
||||
*/
|
||||
NodeNum displayedNodeNum;
|
||||
|
||||
/**
|
||||
* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs.
|
||||
* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they
|
||||
their nodes
|
||||
* aren't talking to each other.
|
||||
*
|
||||
* This string is of the form "#name-XY".
|
||||
*
|
||||
* Where X is a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together.
|
||||
* Y is not yet used but should eventually indicate 'speed/range' of the link
|
||||
*
|
||||
* This function will also need to be implemented in GUI apps that talk to the radio.
|
||||
*
|
||||
* https://github.com/meshtastic/Meshtastic-device/issues/269
|
||||
*/
|
||||
const char *getChannelName()
|
||||
{
|
||||
static char buf[32];
|
||||
|
||||
uint8_t code = 0;
|
||||
for (int i = 0; i < channelSettings.psk.size; i++)
|
||||
code ^= channelSettings.psk.bytes[i];
|
||||
|
||||
snprintf(buf, sizeof(buf), "#%s-%c", channelSettings.name, 'A' + (code % 26));
|
||||
return buf;
|
||||
}
|
||||
|
||||
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {}
|
||||
|
||||
void NodeDB::resetRadioConfig()
|
||||
@@ -93,8 +120,8 @@ void NodeDB::resetRadioConfig()
|
||||
// so incompatible radios can talk together
|
||||
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
||||
|
||||
channelSettings.tx_power = 23;
|
||||
memcpy(&channelSettings.psk.bytes, &defaultpsk, sizeof(channelSettings.psk));
|
||||
channelSettings.tx_power = 0; // default
|
||||
memcpy(&channelSettings.psk.bytes, defaultpsk, sizeof(channelSettings.psk));
|
||||
channelSettings.psk.size = sizeof(defaultpsk);
|
||||
strcpy(channelSettings.name, "Default");
|
||||
}
|
||||
@@ -356,7 +383,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
info->position.time = oldtime;
|
||||
info->has_position = true;
|
||||
updateGUIforNode = info;
|
||||
notifyObservers(true); //Force an update whether or not our node counts have changed
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -371,7 +398,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
devicestate.has_rx_text_message = true;
|
||||
updateTextMessage = true;
|
||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
|
||||
notifyObservers(true); //Force an update whether or not our node counts have changed
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -390,7 +417,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
if (changed) {
|
||||
updateGUIforNode = info;
|
||||
powerFSM.trigger(EVENT_NODEDB_UPDATED);
|
||||
notifyObservers(true); //Force an update whether or not our node counts have changed
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
|
||||
// Not really needed - we will save anyways when we go to sleep
|
||||
// We just changed something important about the user, store our DB
|
||||
@@ -400,7 +427,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
}
|
||||
|
||||
default: {
|
||||
notifyObservers(); //If the node counts have changed, notify observers
|
||||
notifyObservers(); // If the node counts have changed, notify observers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "Observer.h"
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
#include "Observer.h"
|
||||
|
||||
#include "MeshTypes.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "NodeStatus.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
|
||||
extern DeviceState devicestate;
|
||||
extern MyNodeInfo &myNodeInfo;
|
||||
@@ -28,7 +28,7 @@ class NodeDB
|
||||
NodeInfo *nodes;
|
||||
pb_size_t *numNodes;
|
||||
|
||||
int readPointer = 0;
|
||||
uint32_t readPointer = 0;
|
||||
|
||||
public:
|
||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||
@@ -95,7 +95,8 @@ class NodeDB
|
||||
NodeInfo *getOrCreateNode(NodeNum n);
|
||||
|
||||
/// Notify observers of changes to the DB
|
||||
void notifyObservers(bool forceUpdate = false) {
|
||||
void notifyObservers(bool forceUpdate = false)
|
||||
{
|
||||
// Notify observers of the current node state
|
||||
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineNodes(), getNumNodes(), forceUpdate);
|
||||
newStatus.notifyObservers(&status);
|
||||
@@ -115,3 +116,20 @@ class NodeDB
|
||||
extern NodeNum displayedNodeNum;
|
||||
|
||||
extern NodeDB nodeDB;
|
||||
|
||||
/**
|
||||
* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs.
|
||||
* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they
|
||||
their nodes
|
||||
* aren't talking to each other.
|
||||
*
|
||||
* This string is of the form "#name-XY".
|
||||
*
|
||||
* Where X is a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together.
|
||||
* Y is not yet used but should eventually indicate 'speed/range' of the link
|
||||
*
|
||||
* This function will also need to be implemented in GUI apps that talk to the radio.
|
||||
*
|
||||
* https://github.com/meshtastic/Meshtastic-device/issues/269
|
||||
*/
|
||||
const char *getChannelName();
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "PhoneAPI.h"
|
||||
#include "GPS.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "GPS.h"
|
||||
#include "timing.h"
|
||||
#include <assert.h>
|
||||
|
||||
@@ -80,7 +80,6 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next packet we want to send to the phone, or NULL if no such packet is available.
|
||||
*
|
||||
@@ -96,7 +95,7 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
|
||||
size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
{
|
||||
if (!available()) {
|
||||
DEBUG_MSG("getFromRadio, !available\n");
|
||||
// DEBUG_MSG("getFromRadio, !available\n");
|
||||
return false;
|
||||
} else {
|
||||
DEBUG_MSG("getFromRadio, state=%d\n", state);
|
||||
|
||||
@@ -68,6 +68,9 @@ void printPacket(const char *prefix, const MeshPacket *p)
|
||||
if (p->rx_time != 0) {
|
||||
DEBUG_MSG(" rxtime=%u", p->rx_time);
|
||||
}
|
||||
if (p->rx_snr != 0.0) {
|
||||
DEBUG_MSG(" rxSNR=%g", p->rx_snr);
|
||||
}
|
||||
DEBUG_MSG(")\n");
|
||||
}
|
||||
|
||||
@@ -115,22 +118,26 @@ unsigned long hash(char *str)
|
||||
return hash;
|
||||
}
|
||||
|
||||
#define POWER_DEFAULT 17
|
||||
|
||||
/**
|
||||
* Pull our channel settings etc... from protobufs to the dumb interface settings
|
||||
*/
|
||||
void RadioInterface::applyModemConfig()
|
||||
{
|
||||
// Set up default configuration
|
||||
// No Sync Words in LORA mode.
|
||||
modemConfig = (ModemConfigChoice)channelSettings.modem_config;
|
||||
// No Sync Words in LORA mode
|
||||
|
||||
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
||||
int channel_num = hash(channelSettings.name) % NUM_CHANNELS;
|
||||
freq = CH0 + CH_SPACING * channel_num;
|
||||
power = channelSettings.tx_power;
|
||||
if (power == 0)
|
||||
power = POWER_DEFAULT;
|
||||
|
||||
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
|
||||
int channel_num = (channelSettings.channel_num ? channelSettings.channel_num - 1 : hash(channelSettings.name)) % NUM_CHANNELS;
|
||||
freq = CH0 + CH_SPACING * channel_num;
|
||||
|
||||
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelSettings.name, channelSettings.modem_config, channel_num,
|
||||
channelSettings.tx_power);
|
||||
power);
|
||||
}
|
||||
|
||||
ErrorCode SimRadio::send(MeshPacket *p)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/NotifiedWorkerThread.h"
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "../concurrency/NotifiedWorkerThread.h"
|
||||
#include "mesh.pb.h"
|
||||
|
||||
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
|
||||
@@ -31,13 +31,6 @@ typedef struct {
|
||||
uint8_t flags;
|
||||
} PacketHeader;
|
||||
|
||||
typedef enum {
|
||||
Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
|
||||
Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
|
||||
Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
|
||||
Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range
|
||||
} ModemConfigChoice;
|
||||
|
||||
/**
|
||||
* Basic operations all radio chipsets must implement.
|
||||
*
|
||||
@@ -72,9 +65,7 @@ class RadioInterface : protected concurrency::NotifiedWorkerThread
|
||||
void deliverToReceiver(MeshPacket *p);
|
||||
|
||||
public:
|
||||
float freq = 915.0; // FIXME, init all these params from user setings
|
||||
int8_t power = 17;
|
||||
ModemConfigChoice modemConfig;
|
||||
float freq = 915.0;
|
||||
|
||||
/** 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
|
||||
@@ -116,6 +107,8 @@ class RadioInterface : protected concurrency::NotifiedWorkerThread
|
||||
virtual bool reconfigure() = 0;
|
||||
|
||||
protected:
|
||||
int8_t power = 17; // Set by applyModemConfig()
|
||||
|
||||
/***
|
||||
* given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of bytes to send (including the
|
||||
* PacketHeader & payload).
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "RadioLibInterface.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "NodeDB.h"
|
||||
#include "SPILock.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include <configuration.h>
|
||||
#include <pb_decode.h>
|
||||
@@ -8,11 +10,17 @@
|
||||
// FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that
|
||||
static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
|
||||
void LockingModule::SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes)
|
||||
{
|
||||
concurrency::LockGuard g(spiLock);
|
||||
|
||||
Module::SPItransfer(cmd, reg, dataOut, dataIn, numBytes);
|
||||
}
|
||||
|
||||
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
||||
SPIClass &spi, PhysicalLayer *_iface)
|
||||
: concurrency::PeriodicTask(0), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
|
||||
{
|
||||
assert(!instance); // We assume only one for now
|
||||
instance = this;
|
||||
}
|
||||
|
||||
@@ -64,29 +72,41 @@ void RadioLibInterface::applyModemConfig()
|
||||
{
|
||||
RadioInterface::applyModemConfig();
|
||||
|
||||
switch (modemConfig) {
|
||||
case Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
|
||||
bw = 125;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
|
||||
bw = 500;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
|
||||
bw = 31.25;
|
||||
cr = 8;
|
||||
sf = 9;
|
||||
break;
|
||||
case Bw125Cr48Sf4096:
|
||||
bw = 125;
|
||||
cr = 8;
|
||||
sf = 12;
|
||||
break;
|
||||
default:
|
||||
assert(0); // Unknown enum
|
||||
if (channelSettings.spread_factor == 0) {
|
||||
switch (channelSettings.modem_config) {
|
||||
case ChannelSettings_ModemConfig_Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium
|
||||
///< range
|
||||
bw = 125;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case ChannelSettings_ModemConfig_Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short
|
||||
///< range
|
||||
bw = 500;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case ChannelSettings_ModemConfig_Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long
|
||||
///< range
|
||||
bw = 31.25;
|
||||
cr = 8;
|
||||
sf = 9;
|
||||
break;
|
||||
case ChannelSettings_ModemConfig_Bw125Cr48Sf4096:
|
||||
bw = 125;
|
||||
cr = 8;
|
||||
sf = 12;
|
||||
break;
|
||||
default:
|
||||
assert(0); // Unknown enum
|
||||
}
|
||||
} else {
|
||||
sf = channelSettings.spread_factor;
|
||||
cr = channelSettings.coding_rate;
|
||||
bw = channelSettings.bandwidth;
|
||||
|
||||
if (bw == 31) // This parameter is not an integer
|
||||
bw = 31.25;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +134,8 @@ bool RadioLibInterface::canSendImmediately()
|
||||
/// bluetooth comms code. If the txmit queue is empty it might return an error
|
||||
ErrorCode RadioLibInterface::send(MeshPacket *p)
|
||||
{
|
||||
// Sometimes when testing it is useful to be able to never turn on the xmitter
|
||||
#ifndef LORA_DISABLE_SENDING
|
||||
printPacket("enqueuing for send", p);
|
||||
DEBUG_MSG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad);
|
||||
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
|
||||
@@ -128,6 +150,10 @@ ErrorCode RadioLibInterface::send(MeshPacket *p)
|
||||
startTransmitTimer(true);
|
||||
|
||||
return res;
|
||||
#else
|
||||
packetPool.release(p);
|
||||
return ERRNO_UNKNOWN;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RadioLibInterface::canSleep()
|
||||
|
||||
@@ -16,6 +16,49 @@
|
||||
#define INTERRUPT_ATTR
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A wrapper for the RadioLib Module class, that adds mutex for SPI bus access
|
||||
*/
|
||||
class LockingModule : public Module
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
\brief Extended SPI-based module constructor.
|
||||
|
||||
\param cs Arduino pin to be used as chip select.
|
||||
|
||||
\param irq Arduino pin to be used as interrupt/GPIO.
|
||||
|
||||
\param rst Arduino pin to be used as hardware reset for the module.
|
||||
|
||||
\param gpio Arduino pin to be used as additional interrupt/GPIO.
|
||||
|
||||
\param spi SPI interface to be used, can also use software SPI implementations.
|
||||
|
||||
\param spiSettings SPI interface settings.
|
||||
*/
|
||||
LockingModule(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE gpio, SPIClass &spi,
|
||||
SPISettings spiSettings)
|
||||
: Module(cs, irq, rst, gpio, spi, spiSettings)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief SPI single transfer method.
|
||||
|
||||
\param cmd SPI access command (read/write/burst/...).
|
||||
|
||||
\param reg Address of SPI register to transfer to/from.
|
||||
|
||||
\param dataOut Data that will be transfered from master to slave.
|
||||
|
||||
\param dataIn Data that was transfered from slave to master.
|
||||
|
||||
\param numBytes Number of bytes to transfer.
|
||||
*/
|
||||
virtual void SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes);
|
||||
};
|
||||
|
||||
class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTask
|
||||
{
|
||||
/// Used as our notification from the ISR
|
||||
@@ -49,7 +92,7 @@ class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTa
|
||||
float currentLimit = 100; // FIXME
|
||||
uint16_t preambleLength = 32; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving
|
||||
|
||||
Module module; // The HW interface to the radio
|
||||
LockingModule module; // The HW interface to the radio
|
||||
|
||||
/**
|
||||
* provides lowest common denominator RadioLib API
|
||||
|
||||
@@ -12,12 +12,19 @@ SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RA
|
||||
/// \return true if initialisation succeeded.
|
||||
bool SX1262Interface::init()
|
||||
{
|
||||
#ifdef SX1262_POWER_EN
|
||||
digitalWrite(SX1262_POWER_EN, HIGH);
|
||||
pinMode(SX1262_POWER_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
RadioLibInterface::init();
|
||||
|
||||
#ifdef SX1262_RXEN // set not rx or tx mode
|
||||
#ifdef SX1262_RXEN // set not rx or tx mode
|
||||
digitalWrite(SX1262_RXEN, LOW); // Set low before becoming an output
|
||||
pinMode(SX1262_RXEN, OUTPUT);
|
||||
#endif
|
||||
#ifdef SX1262_TXEN
|
||||
digitalWrite(SX1262_TXEN, LOW);
|
||||
pinMode(SX1262_TXEN, OUTPUT);
|
||||
#endif
|
||||
|
||||
@@ -67,6 +74,10 @@ bool SX1262Interface::reconfigure()
|
||||
err = lora.setCodingRate(cr);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
// Hmm - seems to lower SNR when the signal levels are high. Leaving off for now...
|
||||
// err = lora.setRxGain(true);
|
||||
// assert(err == ERR_NONE);
|
||||
|
||||
err = lora.setSyncWord(syncWord);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
@@ -116,6 +127,7 @@ void SX1262Interface::setStandby()
|
||||
*/
|
||||
void SX1262Interface::addReceiveMetadata(MeshPacket *mp)
|
||||
{
|
||||
// DEBUG_MSG("PacketStatus %x\n", lora.getPacketStatus());
|
||||
mp->rx_snr = lora.getSNR();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ typedef struct _ChannelSettings {
|
||||
ChannelSettings_ModemConfig modem_config;
|
||||
ChannelSettings_psk_t psk;
|
||||
char name[12];
|
||||
uint32_t bandwidth;
|
||||
uint32_t spread_factor;
|
||||
uint32_t coding_rate;
|
||||
uint32_t channel_num;
|
||||
} ChannelSettings;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(240) Data_payload_t;
|
||||
@@ -189,6 +193,7 @@ typedef struct _DeviceState {
|
||||
MeshPacket rx_text_message;
|
||||
uint32_t version;
|
||||
bool no_save;
|
||||
bool did_gps_reset;
|
||||
} DeviceState;
|
||||
|
||||
typedef struct _FromRadio {
|
||||
@@ -241,12 +246,12 @@ typedef struct _ToRadio {
|
||||
#define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}}
|
||||
#define SubPacket_init_default {0, {Position_init_default}, 0, 0, 0, 0, {0}, 0}
|
||||
#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0}
|
||||
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""}
|
||||
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0}
|
||||
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
|
||||
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 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 MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0, 0}
|
||||
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0, 0, 0}
|
||||
#define DebugString_init_default {""}
|
||||
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
|
||||
#define ToRadio_init_default {0, {MeshPacket_init_default}}
|
||||
@@ -257,12 +262,12 @@ typedef struct _ToRadio {
|
||||
#define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}}
|
||||
#define SubPacket_init_zero {0, {Position_init_zero}, 0, 0, 0, 0, {0}, 0}
|
||||
#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0}
|
||||
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""}
|
||||
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0}
|
||||
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
|
||||
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 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 MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0, 0}
|
||||
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0, 0, 0}
|
||||
#define DebugString_init_zero {""}
|
||||
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
|
||||
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
|
||||
@@ -271,6 +276,10 @@ typedef struct _ToRadio {
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define ChannelSettings_tx_power_tag 1
|
||||
#define ChannelSettings_modem_config_tag 3
|
||||
#define ChannelSettings_bandwidth_tag 6
|
||||
#define ChannelSettings_spread_factor_tag 7
|
||||
#define ChannelSettings_coding_rate_tag 8
|
||||
#define ChannelSettings_channel_num_tag 9
|
||||
#define ChannelSettings_psk_tag 4
|
||||
#define ChannelSettings_name_tag 5
|
||||
#define Data_typ_tag 1
|
||||
@@ -355,6 +364,7 @@ typedef struct _ToRadio {
|
||||
#define DeviceState_version_tag 8
|
||||
#define DeviceState_rx_text_message_tag 7
|
||||
#define DeviceState_no_save_tag 9
|
||||
#define DeviceState_did_gps_reset_tag 11
|
||||
#define FromRadio_packet_tag 2
|
||||
#define FromRadio_my_info_tag 3
|
||||
#define FromRadio_node_info_tag 4
|
||||
@@ -436,7 +446,11 @@ X(a, STATIC, SINGULAR, BOOL, want_ack, 11)
|
||||
X(a, STATIC, SINGULAR, INT32, tx_power, 1) \
|
||||
X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \
|
||||
X(a, STATIC, SINGULAR, BYTES, psk, 4) \
|
||||
X(a, STATIC, SINGULAR, STRING, name, 5)
|
||||
X(a, STATIC, SINGULAR, STRING, name, 5) \
|
||||
X(a, STATIC, SINGULAR, UINT32, bandwidth, 6) \
|
||||
X(a, STATIC, SINGULAR, UINT32, spread_factor, 7) \
|
||||
X(a, STATIC, SINGULAR, UINT32, coding_rate, 8) \
|
||||
X(a, STATIC, SINGULAR, UINT32, channel_num, 9)
|
||||
#define ChannelSettings_CALLBACK NULL
|
||||
#define ChannelSettings_DEFAULT NULL
|
||||
|
||||
@@ -504,7 +518,8 @@ X(a, STATIC, REPEATED, MESSAGE, node_db, 4) \
|
||||
X(a, STATIC, REPEATED, MESSAGE, receive_queue, 5) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, rx_text_message, 7) \
|
||||
X(a, STATIC, SINGULAR, UINT32, version, 8) \
|
||||
X(a, STATIC, SINGULAR, BOOL, no_save, 9)
|
||||
X(a, STATIC, SINGULAR, BOOL, no_save, 9) \
|
||||
X(a, STATIC, SINGULAR, BOOL, did_gps_reset, 11)
|
||||
#define DeviceState_CALLBACK NULL
|
||||
#define DeviceState_DEFAULT NULL
|
||||
#define DeviceState_radio_MSGTYPE RadioConfig
|
||||
@@ -597,12 +612,12 @@ extern const pb_msgdesc_t ManufacturingData_msg;
|
||||
#define RouteDiscovery_size 88
|
||||
#define SubPacket_size 274
|
||||
#define MeshPacket_size 313
|
||||
#define ChannelSettings_size 60
|
||||
#define RadioConfig_size 253
|
||||
#define ChannelSettings_size 84
|
||||
#define RadioConfig_size 277
|
||||
#define RadioConfig_UserPreferences_size 188
|
||||
#define NodeInfo_size 132
|
||||
#define MyNodeInfo_size 110
|
||||
#define DeviceState_size 5403
|
||||
#define DeviceState_size 5429
|
||||
#define DebugString_size 258
|
||||
#define FromRadio_size 322
|
||||
#define ToRadio_size 316
|
||||
|
||||
601
src/nimble/BluetoothUtil.cpp
Normal file
601
src/nimble/BluetoothUtil.cpp
Normal file
@@ -0,0 +1,601 @@
|
||||
#include "BluetoothUtil.h"
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include "NimbleBluetoothAPI.h"
|
||||
#include "NodeDB.h" // FIXME - we shouldn't really douch this here - we are using it only because we currently do wifi setup when ble gets turned on
|
||||
#include "PhoneAPI.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "WiFi.h"
|
||||
#include "configuration.h"
|
||||
#include "esp_bt.h"
|
||||
#include "host/util/util.h"
|
||||
#include "main.h"
|
||||
#include "nimble/NimbleDefs.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "services/gatt/ble_svc_gatt.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
static bool pinShowing;
|
||||
|
||||
static void startCb(uint32_t pin)
|
||||
{
|
||||
pinShowing = true;
|
||||
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
|
||||
screen.startBluetoothPinScreen(pin);
|
||||
};
|
||||
|
||||
static void stopCb()
|
||||
{
|
||||
if (pinShowing) {
|
||||
pinShowing = false;
|
||||
screen.stopBluetoothPinScreen();
|
||||
}
|
||||
};
|
||||
|
||||
static uint8_t own_addr_type;
|
||||
|
||||
// Force arduino to keep ble data around
|
||||
extern "C" bool btInUse()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Given a level between 0-100, update the BLE attribute
|
||||
void updateBatteryLevel(uint8_t level)
|
||||
{
|
||||
// FIXME
|
||||
}
|
||||
|
||||
void deinitBLE()
|
||||
{
|
||||
// DEBUG_MSG("Shutting down bluetooth\n");
|
||||
// ble_gatts_show_local();
|
||||
|
||||
// FIXME - do we need to dealloc things? - what needs to stay alive across light sleep?
|
||||
auto ret = nimble_port_stop();
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
nimble_port_deinit(); // teardown nimble datastructures
|
||||
|
||||
// DEBUG_MSG("BLE port_deinit done\n");
|
||||
|
||||
ret = esp_nimble_hci_and_controller_deinit();
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
// DEBUG_MSG("BLE task exiting\n");
|
||||
|
||||
DEBUG_MSG("Done shutting down bluetooth\n");
|
||||
}
|
||||
|
||||
void loopBLE()
|
||||
{
|
||||
// FIXME
|
||||
}
|
||||
|
||||
extern "C" void ble_store_config_init(void);
|
||||
|
||||
/// Print a macaddr - bytes are sometimes stored in reverse order
|
||||
static void print_addr(const uint8_t v[], bool isReversed = true)
|
||||
{
|
||||
const int macaddrlen = 6;
|
||||
|
||||
for (int i = 0; i < macaddrlen; i++) {
|
||||
DEBUG_MSG("%02x%c", v[isReversed ? macaddrlen - i : i], (i == macaddrlen - 1) ? '\n' : ':');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs information about a connection to the console.
|
||||
*/
|
||||
static void print_conn_desc(struct ble_gap_conn_desc *desc)
|
||||
{
|
||||
DEBUG_MSG("handle=%d our_ota_addr_type=%d our_ota_addr=", desc->conn_handle, desc->our_ota_addr.type);
|
||||
print_addr(desc->our_ota_addr.val);
|
||||
DEBUG_MSG(" our_id_addr_type=%d our_id_addr=", desc->our_id_addr.type);
|
||||
print_addr(desc->our_id_addr.val);
|
||||
DEBUG_MSG(" peer_ota_addr_type=%d peer_ota_addr=", desc->peer_ota_addr.type);
|
||||
print_addr(desc->peer_ota_addr.val);
|
||||
DEBUG_MSG(" peer_id_addr_type=%d peer_id_addr=", desc->peer_id_addr.type);
|
||||
print_addr(desc->peer_id_addr.val);
|
||||
DEBUG_MSG(" conn_itvl=%d conn_latency=%d supervision_timeout=%d "
|
||||
"encrypted=%d authenticated=%d bonded=%d\n",
|
||||
desc->conn_itvl, desc->conn_latency, desc->supervision_timeout, desc->sec_state.encrypted,
|
||||
desc->sec_state.authenticated, desc->sec_state.bonded);
|
||||
}
|
||||
|
||||
static void advertise();
|
||||
|
||||
/**
|
||||
* The nimble host executes this callback when a GAP event occurs. The
|
||||
* application associates a GAP event callback with each connection that forms.
|
||||
* bleprph uses the same callback for all connections.
|
||||
*
|
||||
* @param event The type of event being signalled.
|
||||
* @param ctxt Various information pertaining to the event.
|
||||
* @param arg Application-specified argument; unused by
|
||||
* bleprph.
|
||||
*
|
||||
* @return 0 if the application successfully handled the
|
||||
* event; nonzero on failure. The semantics
|
||||
* of the return code is specific to the
|
||||
* particular GAP event being signalled.
|
||||
*/
|
||||
static int gap_event(struct ble_gap_event *event, void *arg)
|
||||
{
|
||||
struct ble_gap_conn_desc desc;
|
||||
int rc;
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
/* A new connection was established or a connection attempt failed. */
|
||||
DEBUG_MSG("connection %s; status=%d ", event->connect.status == 0 ? "established" : "failed", event->connect.status);
|
||||
if (event->connect.status == 0) {
|
||||
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
print_conn_desc(&desc);
|
||||
curConnectionHandle = event->connect.conn_handle;
|
||||
}
|
||||
DEBUG_MSG("\n");
|
||||
|
||||
if (event->connect.status != 0) {
|
||||
/* Connection failed; resume advertising. */
|
||||
advertise();
|
||||
}
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
DEBUG_MSG("disconnect; reason=%d ", event->disconnect.reason);
|
||||
print_conn_desc(&event->disconnect.conn);
|
||||
DEBUG_MSG("\n");
|
||||
|
||||
curConnectionHandle = -1;
|
||||
|
||||
/* Connection terminated; resume advertising. */
|
||||
advertise();
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_CONN_UPDATE:
|
||||
/* The central has updated the connection parameters. */
|
||||
DEBUG_MSG("connection updated; status=%d ", event->conn_update.status);
|
||||
rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
print_conn_desc(&desc);
|
||||
DEBUG_MSG("\n");
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_ADV_COMPLETE:
|
||||
DEBUG_MSG("advertise complete; reason=%d", event->adv_complete.reason);
|
||||
advertise();
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_ENC_CHANGE:
|
||||
/* Encryption has been enabled or disabled for this connection. */
|
||||
DEBUG_MSG("encryption change event; status=%d ", event->enc_change.status);
|
||||
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
print_conn_desc(&desc);
|
||||
DEBUG_MSG("\n");
|
||||
|
||||
// Remove our custom PIN request screen.
|
||||
stopCb();
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_SUBSCRIBE:
|
||||
DEBUG_MSG("subscribe event; conn_handle=%d attr_handle=%d "
|
||||
"reason=%d prevn=%d curn=%d previ=%d curi=%d\n",
|
||||
event->subscribe.conn_handle, event->subscribe.attr_handle, event->subscribe.reason,
|
||||
event->subscribe.prev_notify, event->subscribe.cur_notify, event->subscribe.prev_indicate,
|
||||
event->subscribe.cur_indicate);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_MTU:
|
||||
DEBUG_MSG("mtu update event; conn_handle=%d cid=%d mtu=%d\n", event->mtu.conn_handle, event->mtu.channel_id,
|
||||
event->mtu.value);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_REPEAT_PAIRING:
|
||||
DEBUG_MSG("repeat pairing event; conn_handle=%d "
|
||||
"cur_key_sz=%d cur_auth=%d cur_sc=%d "
|
||||
"new_key_sz=%d new_auth=%d new_sc=%d "
|
||||
"new_bonding=%d\n",
|
||||
event->repeat_pairing.conn_handle, event->repeat_pairing.cur_key_size, event->repeat_pairing.cur_authenticated,
|
||||
event->repeat_pairing.cur_sc, event->repeat_pairing.new_key_size, event->repeat_pairing.new_authenticated,
|
||||
event->repeat_pairing.new_sc, event->repeat_pairing.new_bonding);
|
||||
/* We already have a bond with the peer, but it is attempting to
|
||||
* establish a new secure link. This app sacrifices security for
|
||||
* convenience: just throw away the old bond and accept the new link.
|
||||
*/
|
||||
|
||||
/* Delete the old bond. */
|
||||
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
ble_store_util_delete_peer(&desc.peer_id_addr);
|
||||
|
||||
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
|
||||
* continue with the pairing operation.
|
||||
*/
|
||||
return BLE_GAP_REPEAT_PAIRING_RETRY;
|
||||
|
||||
case BLE_GAP_EVENT_PASSKEY_ACTION:
|
||||
DEBUG_MSG("PASSKEY_ACTION_EVENT started \n");
|
||||
struct ble_sm_io pkey = {0};
|
||||
|
||||
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
|
||||
pkey.action = event->passkey.params.action;
|
||||
pkey.passkey = random(
|
||||
100000, 999999); // This is the passkey to be entered on peer - we pick a number >100,000 to ensure 6 digits
|
||||
DEBUG_MSG("*** Enter passkey %d on the peer side ***\n", pkey.passkey);
|
||||
|
||||
startCb(pkey.passkey);
|
||||
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
DEBUG_MSG("ble_sm_inject_io result: %d\n", rc);
|
||||
} else {
|
||||
DEBUG_MSG("FIXME - unexpected auth type %d\n", event->passkey.params.action);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Enables advertising with the following parameters:
|
||||
* o General discoverable mode.
|
||||
* o Undirected connectable mode.
|
||||
*/
|
||||
static void advertise(void)
|
||||
{
|
||||
/**
|
||||
* Set the advertisement data included in our advertisements:
|
||||
* o Flags (indicates advertisement type and other general info).
|
||||
* o Advertising tx power.
|
||||
* o Device name.
|
||||
* o 16-bit service UUIDs (alert notifications).
|
||||
*/
|
||||
|
||||
struct ble_hs_adv_fields adv_fields;
|
||||
memset(&adv_fields, 0, sizeof adv_fields);
|
||||
|
||||
/* Advertise two flags:
|
||||
* o Discoverability in forthcoming advertisement (general)
|
||||
* o BLE-only (BR/EDR unsupported).
|
||||
*/
|
||||
adv_fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
|
||||
|
||||
/* Indicate that the TX power level field should be included; have the
|
||||
* stack fill this value automatically. This is done by assigning the
|
||||
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
|
||||
*/
|
||||
adv_fields.tx_pwr_lvl_is_present = 1;
|
||||
adv_fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
|
||||
|
||||
const char *name = ble_svc_gap_device_name();
|
||||
adv_fields.name = (uint8_t *)name;
|
||||
adv_fields.name_len = strlen(name);
|
||||
adv_fields.name_is_complete = 1;
|
||||
|
||||
auto rc = ble_gap_adv_set_fields(&adv_fields);
|
||||
if (rc != 0) {
|
||||
DEBUG_MSG("error setting advertisement data; rc=%d\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
// add scan response fields
|
||||
struct ble_hs_adv_fields scan_fields;
|
||||
memset(&scan_fields, 0, sizeof scan_fields);
|
||||
scan_fields.uuids128 = const_cast<ble_uuid128_t *>(&mesh_service_uuid);
|
||||
scan_fields.num_uuids128 = 1;
|
||||
scan_fields.uuids128_is_complete = 1;
|
||||
|
||||
rc = ble_gap_adv_rsp_set_fields(&scan_fields);
|
||||
if (rc != 0) {
|
||||
DEBUG_MSG("error setting scan response data; rc=%d\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Begin advertising. */
|
||||
struct ble_gap_adv_params adv_params;
|
||||
memset(&adv_params, 0, sizeof adv_params);
|
||||
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
|
||||
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
|
||||
// FIXME - use RPA for first parameter
|
||||
rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, gap_event, NULL);
|
||||
if (rc != 0) {
|
||||
DEBUG_MSG("error enabling advertisement; rc=%d\n", rc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void on_reset(int reason)
|
||||
{
|
||||
// 19 == BLE_HS_ETIMEOUT_HCI
|
||||
DEBUG_MSG("Resetting state; reason=%d\n", reason);
|
||||
}
|
||||
|
||||
static void on_sync(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ble_hs_util_ensure_addr(0);
|
||||
assert(rc == 0);
|
||||
|
||||
/* Figure out address to use while advertising (no privacy for now) */
|
||||
rc = ble_hs_id_infer_auto(0, &own_addr_type);
|
||||
if (rc != 0) {
|
||||
DEBUG_MSG("error determining address type; rc=%d\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Printing ADDR */
|
||||
uint8_t addr_val[6] = {0};
|
||||
int isPrivate = 0;
|
||||
rc = ble_hs_id_copy_addr(own_addr_type, addr_val, &isPrivate);
|
||||
assert(rc == 0);
|
||||
DEBUG_MSG("BLE advertisting type=%d, Private=%d, Device Address: ", own_addr_type, isPrivate);
|
||||
print_addr(addr_val);
|
||||
DEBUG_MSG("\n");
|
||||
/* Begin advertising. */
|
||||
advertise();
|
||||
}
|
||||
|
||||
static void ble_host_task(void *param)
|
||||
{
|
||||
DEBUG_MSG("BLE task running\n");
|
||||
nimble_port_run(); // This function will return only when nimble_port_stop() is executed.
|
||||
|
||||
// DEBUG_MSG("BLE run complete\n");
|
||||
|
||||
nimble_port_freertos_deinit(); // delete the task
|
||||
}
|
||||
|
||||
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
|
||||
{
|
||||
char buf[BLE_UUID_STR_LEN];
|
||||
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_REGISTER_OP_SVC:
|
||||
DEBUG_MSG("registered service %s with handle=%d\n", ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), ctxt->svc.handle);
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_CHR:
|
||||
/* DEBUG_MSG("registering characteristic %s with "
|
||||
"def_handle=%d val_handle=%d\n",
|
||||
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), ctxt->chr.def_handle, ctxt->chr.val_handle); */
|
||||
|
||||
if (ctxt->chr.chr_def->uuid == &fromnum_uuid.u) {
|
||||
fromNumValHandle = ctxt->chr.val_handle;
|
||||
// DEBUG_MSG("FromNum handle %d\n", fromNumValHandle);
|
||||
}
|
||||
if (ctxt->chr.chr_def->uuid == &update_result_uuid.u) {
|
||||
updateResultHandle = ctxt->chr.val_handle;
|
||||
// DEBUG_MSG("update result handle %d\n", updateResultHandle);
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_DSC:
|
||||
DEBUG_MSG("registering descriptor %s with handle=%d\n", ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), ctxt->dsc.handle);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function that implements simple read and write handling for a uint32_t
|
||||
*
|
||||
* If a read, the provided value will be returned over bluetooth. If a write, the value from the received packet
|
||||
* will be written into the variable.
|
||||
*/
|
||||
int chr_readwrite32le(uint32_t *v, struct ble_gatt_access_ctxt *ctxt)
|
||||
{
|
||||
uint8_t le[4];
|
||||
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
|
||||
DEBUG_MSG("BLE reading a uint32\n");
|
||||
put_le32(le, *v);
|
||||
auto rc = os_mbuf_append(ctxt->om, le, sizeof(le));
|
||||
assert(rc == 0);
|
||||
} else if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
uint16_t len = 0;
|
||||
|
||||
auto rc = ble_hs_mbuf_to_flat(ctxt->om, le, sizeof(le), &len);
|
||||
assert(rc == 0);
|
||||
if (len < sizeof(le)) {
|
||||
DEBUG_MSG("Error: wrongsized write32\n");
|
||||
*v = 0;
|
||||
} else {
|
||||
*v = get_le32(le);
|
||||
DEBUG_MSG("BLE writing a uint32\n");
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG("Unexpected readwrite32 op\n");
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper for readwrite access to an array of bytes (with no endian conversion)
|
||||
*/
|
||||
int chr_readwrite8(uint8_t *v, size_t vlen, struct ble_gatt_access_ctxt *ctxt)
|
||||
{
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
|
||||
DEBUG_MSG("BLE reading bytes\n");
|
||||
auto rc = os_mbuf_append(ctxt->om, v, vlen);
|
||||
assert(rc == 0);
|
||||
} else if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
uint16_t len = 0;
|
||||
|
||||
auto rc = ble_hs_mbuf_to_flat(ctxt->om, v, vlen, &len);
|
||||
assert(rc == 0);
|
||||
if (len < vlen)
|
||||
DEBUG_MSG("Error: wrongsized write\n");
|
||||
else {
|
||||
DEBUG_MSG("BLE writing bytes\n");
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG("Unexpected readwrite8 op\n");
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
// This routine is called multiple times, once each time we come back from sleep
|
||||
void reinitBluetooth()
|
||||
{
|
||||
auto isFirstTime = !bluetoothPhoneAPI;
|
||||
|
||||
DEBUG_MSG("Starting bluetooth\n");
|
||||
if (isFirstTime) {
|
||||
bluetoothPhoneAPI = new BluetoothPhoneAPI();
|
||||
bluetoothPhoneAPI->init();
|
||||
}
|
||||
|
||||
// FIXME - if waking from light sleep, only esp_nimble_hci_init?
|
||||
auto res = esp_nimble_hci_and_controller_init(); // : esp_nimble_hci_init();
|
||||
// DEBUG_MSG("BLE result %d\n", res);
|
||||
assert(res == ESP_OK);
|
||||
|
||||
nimble_port_init();
|
||||
|
||||
ble_att_set_preferred_mtu(512);
|
||||
|
||||
res = ble_gatts_reset(); // Teardown the service tables, so the next restart assigns the same handle numbers
|
||||
assert(res == ESP_OK);
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = on_reset;
|
||||
ble_hs_cfg.sync_cb = on_sync;
|
||||
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
|
||||
ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_ONLY;
|
||||
ble_hs_cfg.sm_bonding = 1;
|
||||
ble_hs_cfg.sm_mitm = 1;
|
||||
ble_hs_cfg.sm_sc = 1;
|
||||
// per https://github.com/espressif/esp-idf/issues/5530#issuecomment-652933685
|
||||
ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ID | BLE_SM_PAIR_KEY_DIST_ENC;
|
||||
ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ID | BLE_SM_PAIR_KEY_DIST_ENC;
|
||||
|
||||
// add standard GAP services
|
||||
ble_svc_gap_init();
|
||||
ble_svc_gatt_init();
|
||||
|
||||
res = ble_gatts_count_cfg(gatt_svr_svcs); // assigns handles? see docstring for note about clearing the handle list
|
||||
// before calling SLEEP SUPPORT
|
||||
assert(res == 0);
|
||||
|
||||
res = ble_gatts_add_svcs(gatt_svr_svcs);
|
||||
assert(res == 0);
|
||||
|
||||
reinitUpdateService();
|
||||
|
||||
/* Set the default device name. */
|
||||
res = ble_svc_gap_device_name_set(getDeviceName());
|
||||
assert(res == 0);
|
||||
|
||||
/* XXX Need to have template for store */
|
||||
ble_store_config_init();
|
||||
|
||||
nimble_port_freertos_init(ble_host_task);
|
||||
}
|
||||
|
||||
void initWifi()
|
||||
{
|
||||
// Note: Wifi is not yet supported ;-)
|
||||
strcpy(radioConfig.preferences.wifi_ssid, "");
|
||||
strcpy(radioConfig.preferences.wifi_password, "");
|
||||
if (radioConfig.has_preferences) {
|
||||
const char *wifiName = radioConfig.preferences.wifi_ssid;
|
||||
|
||||
if (*wifiName) {
|
||||
const char *wifiPsw = radioConfig.preferences.wifi_password;
|
||||
if (radioConfig.preferences.wifi_ap_mode) {
|
||||
DEBUG_MSG("STARTING WIFI AP: ssid=%s, ok=%d\n", wifiName, WiFi.softAP(wifiName, wifiPsw));
|
||||
} else {
|
||||
WiFi.mode(WIFI_MODE_STA);
|
||||
DEBUG_MSG("JOINING WIFI: ssid=%s\n", wifiName);
|
||||
if (WiFi.begin(wifiName, wifiPsw) == WL_CONNECTED) {
|
||||
DEBUG_MSG("MY IP ADDRESS: %s\n", WiFi.localIP().toString().c_str());
|
||||
} else {
|
||||
DEBUG_MSG("Started Joining WIFI\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
DEBUG_MSG("Not using WIFI\n");
|
||||
}
|
||||
|
||||
bool bluetoothOn;
|
||||
|
||||
// Enable/disable bluetooth.
|
||||
void setBluetoothEnable(bool on)
|
||||
{
|
||||
if (on != bluetoothOn) {
|
||||
DEBUG_MSG("Setting bluetooth enable=%d\n", on);
|
||||
|
||||
bluetoothOn = on;
|
||||
if (on) {
|
||||
Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap());
|
||||
// ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
|
||||
reinitBluetooth();
|
||||
initWifi();
|
||||
} else {
|
||||
// We have to totally teardown our bluetooth objects to prevent leaks
|
||||
deinitBLE();
|
||||
WiFi.mode(WIFI_MODE_NULL); // shutdown wifi
|
||||
Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap());
|
||||
// ESP_ERROR_CHECK( heap_trace_stop() );
|
||||
// heap_trace_dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
static BLECharacteristic *batteryLevelC;
|
||||
|
||||
/**
|
||||
* Create a battery level service
|
||||
*/
|
||||
BLEService *createBatteryService(BLEServer *server)
|
||||
{
|
||||
// Create the BLE Service
|
||||
BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F));
|
||||
|
||||
batteryLevelC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL),
|
||||
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||
|
||||
addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100");
|
||||
batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
||||
|
||||
// I don't think we need to advertise this? and some phones only see the first thing advertised anyways...
|
||||
// server->getAdvertising()->addServiceUUID(pBattery->getUUID());
|
||||
pBattery->start();
|
||||
|
||||
return pBattery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the battery level we are currently telling clients.
|
||||
* level should be a pct between 0 and 100
|
||||
*/
|
||||
void updateBatteryLevel(uint8_t level)
|
||||
{
|
||||
if (batteryLevelC) {
|
||||
DEBUG_MSG("set BLE battery level %u\n", level);
|
||||
batteryLevelC->setValue(&level, 1);
|
||||
batteryLevelC->notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Note: these callbacks might be coming in from a different thread.
|
||||
BLEServer *serve = initBLE(, , getDeviceName(), HW_VENDOR, optstr(APP_VERSION),
|
||||
optstr(HW_VERSION)); // FIXME, use a real name based on the macaddr
|
||||
|
||||
#endif
|
||||
30
src/nimble/BluetoothUtil.h
Normal file
30
src/nimble/BluetoothUtil.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
/// We only allow one BLE connection at a time
|
||||
extern int16_t curConnectionHandle;
|
||||
|
||||
// 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)>;
|
||||
|
||||
/// Given a level between 0-100, update the BLE attribute
|
||||
void updateBatteryLevel(uint8_t level);
|
||||
void deinitBLE();
|
||||
void loopBLE();
|
||||
void reinitBluetooth();
|
||||
|
||||
/**
|
||||
* A helper function that implements simple read and write handling for a uint32_t
|
||||
*
|
||||
* If a read, the provided value will be returned over bluetooth. If a write, the value from the received packet
|
||||
* will be written into the variable.
|
||||
*/
|
||||
int chr_readwrite32le(uint32_t *v, struct ble_gatt_access_ctxt *ctxt);
|
||||
|
||||
/**
|
||||
* A helper for readwrite access to an array of bytes (with no endian conversion)
|
||||
*/
|
||||
int chr_readwrite8(uint8_t *v, size_t vlen, struct ble_gatt_access_ctxt *ctxt);
|
||||
68
src/nimble/NimbleBluetoothAPI.cpp
Normal file
68
src/nimble/NimbleBluetoothAPI.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "NimbleBluetoothAPI.h"
|
||||
#include "PhoneAPI.h"
|
||||
#include "configuration.h"
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
#include "nimble/NimbleDefs.h"
|
||||
#include <Arduino.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[FromRadio_size < ToRadio_size ? ToRadio_size : FromRadio_size];
|
||||
static uint32_t fromNum;
|
||||
|
||||
uint16_t fromNumValHandle;
|
||||
|
||||
/// We only allow one BLE connection at a time
|
||||
int16_t curConnectionHandle = -1;
|
||||
|
||||
PhoneAPI *bluetoothPhoneAPI;
|
||||
|
||||
void BluetoothPhoneAPI::onNowHasData(uint32_t fromRadioNum)
|
||||
{
|
||||
PhoneAPI::onNowHasData(fromRadioNum);
|
||||
|
||||
fromNum = fromRadioNum;
|
||||
if (curConnectionHandle >= 0 && fromNumValHandle) {
|
||||
DEBUG_MSG("BLE notify fromNum\n");
|
||||
auto res = ble_gattc_notify(curConnectionHandle, fromNumValHandle);
|
||||
assert(res == 0);
|
||||
} else {
|
||||
DEBUG_MSG("No BLE notify\n");
|
||||
}
|
||||
}
|
||||
|
||||
int toradio_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
auto om = ctxt->om;
|
||||
uint16_t len = 0;
|
||||
|
||||
auto rc = ble_hs_mbuf_to_flat(om, trBytes, sizeof(trBytes), &len);
|
||||
if (rc != 0) {
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
/// DEBUG_MSG("toRadioWriteCb data %p, len %u\n", trBytes, len);
|
||||
|
||||
bluetoothPhoneAPI->handleToRadio(trBytes, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fromradio_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
size_t numBytes = bluetoothPhoneAPI->getFromRadio(trBytes);
|
||||
|
||||
DEBUG_MSG("BLE fromRadio called omlen=%d, ourlen=%d\n", OS_MBUF_PKTLEN(ctxt->om),
|
||||
numBytes); // the normal case has omlen 1 here
|
||||
|
||||
// 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
|
||||
auto rc = os_mbuf_append(ctxt->om, trBytes, numBytes);
|
||||
assert(rc == 0);
|
||||
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
int fromnum_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
return chr_readwrite32le(&fromNum, ctxt);
|
||||
}
|
||||
15
src/nimble/NimbleBluetoothAPI.h
Normal file
15
src/nimble/NimbleBluetoothAPI.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "PhoneAPI.h"
|
||||
|
||||
extern uint16_t fromNumValHandle;
|
||||
|
||||
class BluetoothPhoneAPI : public PhoneAPI
|
||||
{
|
||||
/**
|
||||
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
|
||||
*/
|
||||
virtual void onNowHasData(uint32_t fromRadioNum);
|
||||
};
|
||||
|
||||
extern PhoneAPI *bluetoothPhoneAPI;
|
||||
46
src/nimble/NimbleDefs.c
Normal file
46
src/nimble/NimbleDefs.c
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "NimbleDefs.h"
|
||||
|
||||
// NRF52 wants these constants as byte arrays
|
||||
// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER
|
||||
const ble_uuid128_t mesh_service_uuid =
|
||||
BLE_UUID128_INIT(0xfd, 0xea, 0x73, 0xe2, 0xca, 0x5d, 0xa8, 0x9f, 0x1f, 0x46, 0xa8, 0x15, 0x18, 0xb2, 0xa1, 0x6b);
|
||||
|
||||
static const ble_uuid128_t toradio_uuid =
|
||||
BLE_UUID128_INIT(0xe7, 0x01, 0x44, 0x12, 0x66, 0x78, 0xdd, 0xa1, 0xad, 0x4d, 0x9e, 0x12, 0xd2, 0x76, 0x5c, 0xf7);
|
||||
|
||||
static const ble_uuid128_t fromradio_uuid =
|
||||
BLE_UUID128_INIT(0xd5, 0x54, 0xe4, 0xc5, 0x25, 0xc5, 0x31, 0xa5, 0x55, 0x4a, 0x02, 0xee, 0xc2, 0xbc, 0xa2, 0x8b);
|
||||
|
||||
const ble_uuid128_t fromnum_uuid =
|
||||
BLE_UUID128_INIT(0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6, 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed);
|
||||
|
||||
const struct ble_gatt_svc_def gatt_svr_svcs[] = {
|
||||
{
|
||||
/*** Service: Security test. */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = &mesh_service_uuid.u,
|
||||
.characteristics =
|
||||
(struct ble_gatt_chr_def[]){{
|
||||
.uuid = &toradio_uuid.u,
|
||||
.access_cb = toradio_callback,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_AUTHEN,
|
||||
},
|
||||
{
|
||||
.uuid = &fromradio_uuid.u,
|
||||
.access_cb = fromradio_callback,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_AUTHEN,
|
||||
},
|
||||
{
|
||||
.uuid = &fromnum_uuid.u,
|
||||
.access_cb = fromnum_callback,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_NOTIFY,
|
||||
},
|
||||
{
|
||||
0, /* No more characteristics in this service. */
|
||||
}},
|
||||
},
|
||||
|
||||
{
|
||||
0, /* No more services. */
|
||||
},
|
||||
};
|
||||
32
src/nimble/NimbleDefs.h
Normal file
32
src/nimble/NimbleDefs.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
// Keep nimble #defs from messing up the build
|
||||
#ifndef max
|
||||
#define max max
|
||||
#define min min
|
||||
#endif
|
||||
|
||||
#include "esp_nimble_hci.h"
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/ble_uuid.h"
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int toradio_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
|
||||
int fromradio_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
|
||||
int fromnum_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
|
||||
extern const struct ble_gatt_svc_def gatt_svr_svcs[];
|
||||
|
||||
extern const ble_uuid128_t mesh_service_uuid, fromnum_uuid;
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
@@ -4,16 +4,7 @@
|
||||
#include "main.h"
|
||||
#include <bluefruit.h>
|
||||
|
||||
// NRF52 wants these constants as byte arrays
|
||||
// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER
|
||||
const uint8_t MESH_SERVICE_UUID_16[16u] = {0xfd, 0xea, 0x73, 0xe2, 0xca, 0x5d, 0xa8, 0x9f,
|
||||
0x1f, 0x46, 0xa8, 0x15, 0x18, 0xb2, 0xa1, 0x6b};
|
||||
const uint8_t TORADIO_UUID_16[16u] = {0xe7, 0x01, 0x44, 0x12, 0x66, 0x78, 0xdd, 0xa1,
|
||||
0xad, 0x4d, 0x9e, 0x12, 0xd2, 0x76, 0x5c, 0xf7};
|
||||
const uint8_t FROMRADIO_UUID_16[16u] = {0xd5, 0x54, 0xe4, 0xc5, 0x25, 0xc5, 0x31, 0xa5,
|
||||
0x55, 0x4a, 0x02, 0xee, 0xc2, 0xbc, 0xa2, 0x8b};
|
||||
const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6,
|
||||
0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed};
|
||||
|
||||
|
||||
static BLEService meshBleService = BLEService(BLEUuid(MESH_SERVICE_UUID_16));
|
||||
static BLECharacteristic fromNum = BLECharacteristic(BLEUuid(FROMNUM_UUID_16));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "NRF52Bluetooth.h"
|
||||
#include "configuration.h"
|
||||
#include "graphics/TFT.h"
|
||||
#include <assert.h>
|
||||
#include <ble_gap.h>
|
||||
#include <memory.h>
|
||||
|
||||
22
src/power.h
22
src/power.h
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include "concurrency/PeriodicTask.h"
|
||||
#include "PowerStatus.h"
|
||||
#include "concurrency/PeriodicTask.h"
|
||||
|
||||
/**
|
||||
* Per @spattinson
|
||||
@@ -18,23 +18,23 @@
|
||||
class Power : public concurrency::PeriodicTask
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
public:
|
||||
Observable<const meshtastic::PowerStatus *> newStatus;
|
||||
|
||||
void readPowerStatus();
|
||||
void loop();
|
||||
virtual bool setup();
|
||||
virtual void doTask();
|
||||
void setStatusHandler(meshtastic::PowerStatus *handler)
|
||||
{
|
||||
statusHandler = handler;
|
||||
}
|
||||
|
||||
protected:
|
||||
meshtastic::PowerStatus *statusHandler;
|
||||
virtual void axp192Init();
|
||||
void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; }
|
||||
|
||||
protected:
|
||||
meshtastic::PowerStatus *statusHandler;
|
||||
|
||||
/// Setup a axp192, return true if found
|
||||
bool axp192Init();
|
||||
|
||||
/// Setup a simple ADC input based battery sensor
|
||||
bool analogInit();
|
||||
};
|
||||
|
||||
extern Power *power;
|
||||
@@ -5,9 +5,9 @@
|
||||
#include "NodeDB.h"
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "timing.h"
|
||||
#include "main.h"
|
||||
#include "target_specific.h"
|
||||
#include "timing.h"
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "esp32/pm.h"
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <driver/rtc_io.h>
|
||||
#include <driver/uart.h>
|
||||
|
||||
#include "BluetoothUtil.h"
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
|
||||
esp_sleep_source_t wakeCause; // the reason we booted this time
|
||||
#endif
|
||||
@@ -197,7 +197,7 @@ void doDeepSleep(uint64_t msecToWake)
|
||||
static const uint8_t rtcGpios[] = {/* 0, */ 2,
|
||||
/* 4, */
|
||||
#ifndef USE_JTAG
|
||||
12, 13,
|
||||
13,
|
||||
/* 14, */ /* 15, */
|
||||
#endif
|
||||
/* 25, */ 26, /* 27, */
|
||||
@@ -284,8 +284,10 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
|
||||
assert(esp_light_sleep_start() == ESP_OK);
|
||||
|
||||
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
|
||||
#ifdef BUTTON_PIN
|
||||
if (cause == ESP_SLEEP_WAKEUP_GPIO)
|
||||
DEBUG_MSG("Exit light sleep gpio: btn=%d\n", !digitalRead(BUTTON_PIN));
|
||||
#endif
|
||||
|
||||
return cause;
|
||||
}
|
||||
@@ -294,18 +296,18 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
|
||||
|
||||
/**
|
||||
* enable modem sleep mode as needed and available. Should lower our CPU current draw to an average of about 20mA.
|
||||
*
|
||||
*
|
||||
* per https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/power_management.html
|
||||
*
|
||||
*
|
||||
* supposedly according to https://github.com/espressif/arduino-esp32/issues/475 this is already done in arduino
|
||||
*/
|
||||
void enableModemSleep()
|
||||
{
|
||||
static esp_pm_config_esp32_t config; // filled with zeros because bss
|
||||
static esp_pm_config_esp32_t config; // filled with zeros because bss
|
||||
|
||||
config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
|
||||
config.min_freq_mhz = 20; // 10Mhz is minimum recommended
|
||||
config.light_sleep_enable = false;
|
||||
DEBUG_MSG("Sleep request result %x\n", esp_pm_configure(&config));
|
||||
config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
|
||||
config.min_freq_mhz = 20; // 10Mhz is minimum recommended
|
||||
config.light_sleep_enable = false;
|
||||
DEBUG_MSG("Sleep request result %x\n", esp_pm_configure(&config));
|
||||
}
|
||||
#endif
|
||||
|
||||
105
variants/lora_relay_v1/variant.cpp
Normal file
105
variants/lora_relay_v1/variant.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
|
||||
Copyright (c) 2016 Sandeep Mistry All right reserved.
|
||||
Copyright (c) 2018, Adafruit Industries (adafruit.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "variant.h"
|
||||
#include "nrf.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] = {
|
||||
// D0 .. D13
|
||||
25, // D0 is P0.25 (UART TX)
|
||||
24, // D1 is P0.24 (UART RX
|
||||
10, // D2 is P0.10 (NFC2)
|
||||
47, // D3 is P1.15 (LED1)
|
||||
42, // D4 is P1.10 (LED2)
|
||||
40, // D5 is P1.08
|
||||
7, // D6 is P0.07
|
||||
34, // D7 is P1.02 (Button)
|
||||
16, // D8 is P0.16 (NeoPixel)
|
||||
26, // D9 is P0.26 D_RS (IPS data/command control)
|
||||
27, // D10 is P0.27
|
||||
6, // D11 is P0.06 D_RES (IPS display reset)
|
||||
8, // D12 is P0.08 D_CS (IPS display chip select)
|
||||
41, // D13 is P1.09 BLT (IPS display backlight)
|
||||
4, // D14 is P0.04 SX1262 RXEN
|
||||
5, // D15 is P0.05 BOOST_EN (5V buck converter enable for the the radio power)
|
||||
|
||||
// D14 .. D21 (aka A0 .. A7)
|
||||
30, // D16 is P0.30 (A0)
|
||||
28, // D17 is P0.28 (A1)
|
||||
2, // D18 is P0.02 (A2)
|
||||
3, // D19 is P0.03 (A3)
|
||||
29, // D20 is P0.29 (A4, Battery)
|
||||
31, // D21 is P0.31 (A5, ARef)
|
||||
|
||||
// D22 .. D23 (aka I2C pins)
|
||||
12, // D22 is P0.12 (SDA)
|
||||
11, // D23 is P0.11 (SCL)
|
||||
|
||||
// D24 .. D26 (aka SPI pins)
|
||||
15, // D24 is P0.15 (SPI MISO)
|
||||
13, // D25 is P0.13 (SPI MOSI)
|
||||
14, // D26 is P0.14 (SPI SCK )
|
||||
|
||||
// QSPI pins (not exposed via any header / test point)
|
||||
// 19, // P0.19 (QSPI CLK)
|
||||
// 20, // P0.20 (QSPI CS)
|
||||
// 17, // P0.17 (QSPI Data 0)
|
||||
// 22, // P0.22 (QSPI Data 1)
|
||||
// 23, // P0.23 (QSPI Data 2)
|
||||
// 21, // P0.21 (QSPI Data 3)
|
||||
|
||||
// The remaining NFC pin
|
||||
9, // D27 P0.09 (NFC1, exposed only via test point on bottom of board)
|
||||
|
||||
// The following pins were never listed as they were considered unusable
|
||||
// 0, // P0.00 is XL1 (attached to 32.768kHz crystal) Never expose as GPIOs
|
||||
// 1, // P0.01 is XL2 (attached to 32.768kHz crystal)
|
||||
18, // D28 P0.18 is RESET (attached to switch)
|
||||
// 32, // P1.00 is SWO (attached to debug header)
|
||||
|
||||
// D29-D43
|
||||
27, // D29 P0.27 E22-SX1262 DIO1
|
||||
28, // D30 P0.28 E22-SX1262 DIO2
|
||||
30, // D31 P0.30 E22-SX1262 TXEN
|
||||
35, // D32 P1.03 E22-SX1262 NSS
|
||||
32 + 8, // D33 P1.08 E22-SX1262 BUSY
|
||||
32 + 12, // D34 P1.12 E22-SX1262 RESET
|
||||
32 + 1, // P1.01 BTN_UP
|
||||
32 + 2, // P1.02 SWITCH
|
||||
32 + 14, // D37 P1.14 is not connected per schematic
|
||||
36, // P1.04 is not connected per schematic
|
||||
37, // P1.05 is not connected per schematic
|
||||
38, // P1.06 is not connected per schematic
|
||||
39, // P1.07 is not connected per schematic
|
||||
43, // P1.11 is not connected per schematic
|
||||
45, // P1.13 is not connected per schematic
|
||||
};
|
||||
|
||||
void initVariant()
|
||||
{
|
||||
// LED1 & LED2
|
||||
pinMode(PIN_LED1, OUTPUT);
|
||||
ledOff(PIN_LED1);
|
||||
|
||||
pinMode(PIN_LED2, OUTPUT);
|
||||
ledOff(PIN_LED2);
|
||||
}
|
||||
152
variants/lora_relay_v1/variant.h
Normal file
152
variants/lora_relay_v1/variant.h
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
|
||||
Copyright (c) 2016 Sandeep Mistry All right reserved.
|
||||
Copyright (c) 2018, Adafruit Industries (adafruit.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _VARIANT_LORA_RELAY_V1_
|
||||
#define _VARIANT_LORA_RELAY_V1_
|
||||
|
||||
/** Master clock frequency */
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
|
||||
#define USE_LFXO // Board uses 32khz crystal for LF
|
||||
// define USE_LFRC // Board uses RC for LF
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Headers
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
// Number of pins defined in PinDescription array
|
||||
#define PINS_COUNT (43)
|
||||
#define NUM_DIGITAL_PINS (43)
|
||||
#define NUM_ANALOG_INPUTS (6) // A6 is used for battery, A7 is analog reference
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// LEDs
|
||||
#define PIN_LED1 (3)
|
||||
#define PIN_LED2 (4)
|
||||
#define PIN_NEOPIXEL (8)
|
||||
|
||||
#define LED_BUILTIN PIN_LED1
|
||||
#define LED_CONN PIN_LED2
|
||||
|
||||
#define LED_RED PIN_LED1
|
||||
#define LED_BLUE PIN_LED2
|
||||
|
||||
#define LED_STATE_ON 1 // State when LED is litted
|
||||
|
||||
/*
|
||||
* Buttons
|
||||
*/
|
||||
#define PIN_BUTTON1 (7)
|
||||
|
||||
/*
|
||||
* Analog pins
|
||||
*/
|
||||
#define PIN_A0 (16)
|
||||
#define PIN_A1 (17)
|
||||
#define PIN_A2 (18)
|
||||
#define PIN_A3 (19)
|
||||
#define PIN_A4 (20)
|
||||
#define PIN_A5 (21)
|
||||
|
||||
static const uint8_t A0 = PIN_A0;
|
||||
static const uint8_t A1 = PIN_A1;
|
||||
static const uint8_t A2 = PIN_A2;
|
||||
static const uint8_t A3 = PIN_A3;
|
||||
static const uint8_t A4 = PIN_A4;
|
||||
static const uint8_t A5 = PIN_A5;
|
||||
#define ADC_RESOLUTION 14
|
||||
|
||||
// Other pins
|
||||
#define PIN_AREF PIN_A5
|
||||
#define PIN_VBAT PIN_A4
|
||||
#define PIN_NFC1 (33)
|
||||
#define PIN_NFC2 (2)
|
||||
#define PIN_PIEZO (37)
|
||||
static const uint8_t AREF = PIN_AREF;
|
||||
|
||||
/*
|
||||
* Serial interfaces
|
||||
*/
|
||||
#define PIN_SERIAL1_RX (1)
|
||||
#define PIN_SERIAL1_TX (0)
|
||||
|
||||
/*
|
||||
* SPI Interfaces
|
||||
*/
|
||||
#define SPI_INTERFACES_COUNT 1
|
||||
|
||||
#define PIN_SPI_MISO (24)
|
||||
#define PIN_SPI_MOSI (25)
|
||||
#define PIN_SPI_SCK (26)
|
||||
|
||||
static const uint8_t SS = (5);
|
||||
static const uint8_t MOSI = PIN_SPI_MOSI;
|
||||
static const uint8_t MISO = PIN_SPI_MISO;
|
||||
static const uint8_t SCK = PIN_SPI_SCK;
|
||||
|
||||
/*
|
||||
* Wire Interfaces
|
||||
*/
|
||||
#define WIRE_INTERFACES_COUNT 1
|
||||
|
||||
#define PIN_WIRE_SDA (22)
|
||||
#define PIN_WIRE_SCL (23)
|
||||
|
||||
// I2C device addresses
|
||||
#define I2C_ADDR_BQ27441 0x55 // Battery gauge
|
||||
|
||||
// CUSTOM GPIOs the SX1262
|
||||
#define SX1262_CS (32)
|
||||
|
||||
// If you would prefer to get console debug output over the JTAG ICE connection rather than the CDC-ACM USB serial device, just
|
||||
// define this. #define USE_SEGGER
|
||||
|
||||
#define SX1262_DIO1 (29)
|
||||
#define SX1262_DIO2 (30)
|
||||
#define SX1262_BUSY (33) // Supposed to be P0.18 but because of reworks, now on P0.31 (18)
|
||||
#define SX1262_RESET (34)
|
||||
// #define SX1262_ANT_SW (32 + 10)
|
||||
#define SX1262_RXEN (14)
|
||||
#define SX1262_TXEN (31)
|
||||
#define SX1262_POWER_EN \
|
||||
(15) // FIXME, see warning hre https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay/blob/master/LORA_RELAY_NRF52840.ino
|
||||
#define SX1262_E22 // Indicates this SX1262 is inside of an ebyte E22 module and special config should be done for that
|
||||
|
||||
#define ST7735_RESET (11) // Output
|
||||
#define ST7735_CS (12)
|
||||
#define ST7735_BACKLIGHT_EN (13)
|
||||
#define ST7735_RS (9)
|
||||
|
||||
// #define LORA_DISABLE_SENDING // The board can brownout during lora TX if you don't have a battery connected. Disable sending
|
||||
// to allow USB power only based debugging
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Arduino objects - C++ only
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#endif
|
||||
@@ -128,7 +128,25 @@ static const uint8_t SCK = PIN_SPI_SCK;
|
||||
#define PIN_WIRE_SDA (32 + 2)
|
||||
#define PIN_WIRE_SCL (32)
|
||||
|
||||
#define GPS_I2C_ADDR FIXME
|
||||
// CUSTOM GPIOs the SX1262
|
||||
#define SX1262_CS (10)
|
||||
#define SX1262_DIO1 (20)
|
||||
#define SX1262_DIO2 (26)
|
||||
#define SX1262_BUSY (31) // Supposed to be P0.18 but because of reworks, now on P0.31 (18)
|
||||
#define SX1262_RESET (17)
|
||||
// #define SX1262_ANT_SW (32 + 10)
|
||||
#define SX1262_RXEN (22)
|
||||
#define SX1262_TXEN (24)
|
||||
#define SX1262_E22 // Indicates this SX1262 is inside of an ebyte E22 module and special config should be done for that
|
||||
|
||||
// ERC12864-10 LCD
|
||||
#define ERC12864_CS (32 + 4)
|
||||
#define ERC12864_RESET (32 + 6)
|
||||
#define ERC12864_CD (32 + 9)
|
||||
|
||||
// L80 GPS
|
||||
#define L80_PPS (28)
|
||||
#define L80_RESET (29)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user