Compare commits

...

111 Commits

Author SHA1 Message Date
Kevin Hester
16f897d27c Merge pull request #547 from geeksville/udp
1.1.20
2020-12-10 13:32:59 +08:00
Kevin Hester
4cbf0a0730 oops - forgot to check in the new version file, broke CI 2020-12-10 13:19:00 +08:00
Kevin Hester
99c8df8e7d try to fix CI again 2020-12-10 13:17:43 +08:00
Kevin Hester
8e2e4f7e6a Merge remote-tracking branch 'root/master' into udp 2020-12-10 12:46:51 +08:00
Kevin Hester
45d72bd51b fix CI build - make projectdir relative 2020-12-10 12:44:35 +08:00
Jm Casler
781ed3eafd Merge pull request #544 from IZ1IVA/patch-1
Update radio-settings.md
2020-12-09 20:39:18 -08:00
Jm Casler
c66a0a37d8 Merge branch 'master' into patch-1 2020-12-09 20:25:17 -08:00
Jm Casler
92f2007207 Merge pull request #545 from IZ1IVA/patch-2
Update radio-settings.md
2020-12-09 20:24:32 -08:00
Kevin Hester
1f7b537d2d Merge remote-tracking branch 'root/master' into udp 2020-12-10 11:46:03 +08:00
Kevin Hester
cabeacfa94 1.1.21 2020-12-10 11:45:22 +08:00
Kevin Hester
df8b3ebbc7 always pull min_app_version from appload 2020-12-10 11:32:51 +08:00
Kevin Hester
b1c30f0650 Use a custom platformio py file to always set build version 2020-12-10 11:28:15 +08:00
IZ1IVA
194028f9fc Update radio-settings.md
- added ERP reference to LoRaWAN for NA
2020-12-09 11:16:31 +01:00
IZ1IVA
f49c8f4c43 Update radio-settings.md
- corrected dBM into dBm
- specified ERP reference
- added link to ERP in-depth article
2020-12-09 11:04:07 +01:00
Kevin Hester
b3b4c2c1c3 Merge pull request #543 from geeksville/udp
big set of changes from my weeks away
2020-12-08 22:46:26 -08:00
Kevin Hester
a0076eb394 better position debug output 2020-12-09 13:42:36 +08:00
Kevin Hester
a6a4fec4b9 Merge remote-tracking branch 'root/master' into udp 2020-12-09 12:15:32 +08:00
Kevin Hester
2d4657c8d4 Merge pull request #542 from IZ1IVA/patch-1
Update README.md
2020-12-08 20:14:43 -08:00
Kevin Hester
32b8e4f20a fix #536 allow fixed positions
meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337
2020-12-09 12:05:15 +08:00
Kevin Hester
3753fef298 add debug_log_enabled 2020-12-09 11:56:41 +08:00
Kevin Hester
a4bb1937c1 Merge remote-tracking branch 'root/master' into udp 2020-12-09 11:21:59 +08:00
Kevin Hester
4bd22dd5db ignore our own msgs for gpio ctrl 2020-12-08 08:16:58 +08:00
Kevin Hester
79a24c200e use autogened protobuf init code 2020-12-07 10:27:31 +08:00
Kevin Hester
90060e84c0 WIP on GPIO example 2020-12-07 10:18:11 +08:00
Kevin Hester
8f5a1f19d3 add remote gpio control as an example plugin
https://github.com/meshtastic/Meshtastic-device/issues/182
2020-12-06 18:33:42 +08:00
Kevin Hester
3e0dc44210 move want_replies into new plugin system 2020-12-05 11:15:06 +08:00
Kevin Hester
91b99bd584 require apps to be 1.1.20 or later 2020-12-05 10:27:04 +08:00
Kevin Hester
b6e21bcbcd add SinglePortPlugin to simpilify api 2020-12-05 10:14:15 +08:00
Kevin Hester
ae7d3ee5ed move nodeinfo messages into new plugin system 2020-12-05 10:00:46 +08:00
Kevin Hester
f1179bd3ea positions now sent using the new API 2020-12-05 08:46:19 +08:00
Kevin Hester
9b24cc6dd6 update protobufs 2020-12-04 18:54:00 +08:00
IZ1IVA
20c5b98b2d Update README.md
Added "Related Groups" section
Added Meshtastic Italian Telegram group link
2020-12-03 12:27:44 +01:00
Kevin Hester
d3cb9bdd4a WIP moving positions to new system 2020-12-03 16:48:44 +08:00
Kevin Hester
7737123d0f begin moving position stuff into plugin 2020-11-28 18:10:31 +08:00
Kevin Hester
5138aff4b2 fix static initializer bug with mesh plugins 2020-11-28 13:25:03 +08:00
Kevin Hester
0b0d293a66 Move text message handling into the new plugin system 2020-11-28 12:10:19 +08:00
Kevin Hester
ddab4a0235 remove support for 8bit nodenums 2020-11-28 09:56:21 +08:00
Jm Casler
50615540ce Merge pull request #538 from meshtastic/dev-https
Merging in dev-https to master
2020-11-27 17:21:56 -08:00
Kevin Hester
f5e42b2533 update protos 2020-11-28 09:17:20 +08:00
Kevin Hester
9e9913101f Merge pull request #527 from geeksville/spiffs-bug496
Spiffs bug496
2020-11-27 17:11:34 -08:00
Kevin Hester
b1289b632a Merge remote-tracking branch 'root/master' into dev 2020-11-28 09:00:21 +08:00
Kevin Hester
c427c8abf9 oops - fix my crummy merge 2020-11-28 08:55:56 +08:00
Jm Casler
9170dc7738 Merge pull request #534 from mc-hamster/master
Fix for #533 - We will deinit wifi even if it were never initialized.
2020-11-27 16:19:03 -08:00
Kevin Hester
cc36e3a9a6 Merge branch 'dev-https' into spiffs-bug496 2020-11-27 16:15:14 -08:00
Jm Casler
7d4c77abfd Fix for #533 - deinitWifi() will de initialize the radio even if it was never initialized 2020-11-27 14:49:44 -08:00
Jm Casler
1ba91ec27f Merge pull request #26 from meshtastic/master
update my repo from master
2020-11-25 21:36:56 -08:00
Jm Casler
575c5b2193 Merge pull request #532 from meshtastic/mc-hamster-patch-2
wait_bluetooth_secs to 60 seconds.
2020-11-22 19:38:39 -08:00
Jm Casler
3473a1e323 wait_bluetooth_secs to 60 seconds. 2020-11-22 19:32:48 -08:00
Jm Casler
11a00e2977 Merge pull request #531 from mc-hamster/master
Add support for the power button #528
2020-11-22 19:19:35 -08:00
Jm Casler
817c99e09c trying to fix it again 2020-11-22 19:12:11 -08:00
Jm Casler
9801a62d2d Fix for CI on other boards. 2020-11-22 19:01:48 -08:00
Jm Casler
2bd40b7053 Add support for the power button #528 2020-11-22 18:50:14 -08:00
Jm Casler
dccc15946b Merge pull request #530 from mc-hamster/master
Removing stuff I accidently checked in for the duplexer
2020-11-22 16:50:45 -08:00
Jm Casler
3ab9d2a50e Removing stuff I accidently checked in for the duplexer 2020-11-22 16:24:22 -08:00
Jm Casler
776a978ea0 Merge pull request #529 from mc-hamster/master
Accidentally checked in my rf deduplixer
2020-11-22 15:44:19 -08:00
Jm Casler
f60922af34 Accidently checked in my rf deduplexer 2020-11-22 15:42:36 -08:00
Kevin Hester
cb34fd5eb9 Merge remote-tracking branch 'root/master' into dev 2020-11-20 08:59:54 +08:00
Kevin Hester
19d81347f2 1.1.9 (release for @mc-hamster only in android app alpha) 2020-11-20 07:42:02 +08:00
Kevin Hester
d7d13d637c clean up filesystem goo, add spiffs to install scripts, fix #496
@mc-hamster seems to work pretty good for me, so I'll send a PR to you
for the dev-http branch.

I'll push out an android alpha build later today (once the build is
complete).  Once this new device load is out in the field _future_
device builds will support updating spiffs from android. (i.e. device
loads older than 1.1.9 must be updated to 1.1.9 or later before spiffs
support is implemented on the device side - so some users might need to
update twice before the new spiffs contents will appear on their device)
2020-11-19 09:25:02 +08:00
Kevin Hester
df75cefeeb fix #525 - turn off (buggy) TinyGPS custom candidates support 2020-11-18 10:54:25 +08:00
Jm Casler
66f9dbec45 Merge pull request #524 from meshtastic/mc-hamster-patch-1
Lora Design Guide
2020-11-17 13:16:49 -08:00
Jm Casler
4679dd7c4d Lora Design Guide
Airtime calculation is in section 4.
2020-11-17 11:14:57 -08:00
Jm Casler
a02979d564 Merge branch 'externalAmp' into master 2020-11-16 19:54:06 -08:00
Kevin Hester
f2698bbf91 bug #496 - allow update clients to program SPIFFS region
also have build system properly build spiffs release builds
2020-11-16 08:56:32 +08:00
Kevin Hester
d045139945 return ble errors for invalid values 2020-11-16 07:57:32 +08:00
Kevin Hester
2c9c5991a0 add a script for testing release builds 2020-11-15 08:24:34 +08:00
Jm Casler
1b365fa0aa Merge pull request #24 from meshtastic/dev-https
Dev https
2020-11-13 21:40:00 -08:00
Jm Casler
71d1d4d8fa Merge pull request #521 from meshtastic/master
Update dev-https from master
2020-11-13 21:35:20 -08:00
Jm Casler
64df994a32 Merge pull request #520 from crossan007/dev-https
Only issue ContentType header once; correctly.
2020-11-13 20:13:31 -08:00
Charles Crossan
49a19e26d5 Only issue ContentType header once; correctly. 2020-11-13 22:07:52 -05:00
Kevin Hester
ef37f955c3 Merge remote-tracking branch 'root/master' into dev 2020-11-14 10:22:10 +08:00
Kevin Hester
ac50b9544b Merge pull request #519 from geeksville/bug513
Bug513
2020-11-14 10:21:44 +08:00
Kevin Hester
ccc1600bc9 remove stale fixme 2020-11-14 10:19:55 +08:00
Kevin Hester
7c220f8a39 Merge branch 'master' into bug513 2020-11-14 10:14:22 +08:00
Kevin Hester
1839f8f7ca fix #513 scale retransmission times based on true packet time on wire 2020-11-14 10:07:25 +08:00
Kevin Hester
48c461c50c Merge pull request #515 from geeksville/eink
nrf52 deep sleep support
2020-11-13 11:08:17 +08:00
Kevin Hester
f346b4f0f2 Merge branch 'eink' into bug513 2020-11-13 09:41:23 +08:00
Kevin Hester
5aab4f5c95 Merge branch 'master' into eink 2020-11-13 09:36:18 +08:00
Kevin Hester
d407db5ee1 add more deep sleep support for nrf52 boards 2020-11-13 09:33:59 +08:00
Kevin Hester
93afc71e2e Show "sleeping" on eink while in deep sleep 2020-11-13 07:49:01 +08:00
Kevin Hester
67e657f10f always print debug output to both serial and segger debug port
cc @mc-hamster, this  makes USE_SEGGER optional on nrf52 targets
2020-11-13 07:48:25 +08:00
Kevin Hester
619a48085a pinetab docs 2020-11-12 18:31:35 +08:00
Kevin Hester
68937d52fe bug #503 wip 2020-11-12 17:49:04 +08:00
Jm Casler
e33657eb75 Toggle GPIO2 to HIGH when transmitting a packet out the LORA radio. 2020-11-09 19:47:31 -08:00
Kevin Hester
21751da5a2 Merge pull request #512 from geeksville/dev
Dev
2020-11-07 04:19:36 -08:00
Kevin Hester
c2e8ac7173 1.1.8 2020-11-07 19:20:38 +08:00
Jm Casler
825001f313 Merge pull request #23 from mc-hamster/master
update
2020-11-06 23:12:50 -08:00
Jm Casler
576526576a Merge pull request #511 from mc-hamster/master
Web text editor #510
2020-11-06 23:11:09 -08:00
Kevin Hester
2fd5ce00ce fix build for esp32 targets 2020-11-07 15:09:05 +08:00
Jm Casler
4204c494ae fix poorly merged file 2020-11-06 22:41:26 -08:00
Jm Casler
84beae1001 Merge branch 'dev-https' into master 2020-11-06 22:29:03 -08:00
Jm Casler
951b4293c4 Set web cookie and web text editor. 2020-11-06 22:21:20 -08:00
Kevin Hester
952c216bf7 Merge remote-tracking branch 'root/master' into dev 2020-11-07 14:08:22 +08:00
Kevin Hester
ff4b03b8c1 Fix #509 was causing failure on non GPS equipped boards 2020-11-07 09:15:28 +08:00
Kevin Hester
c5903a790b update for portduino 2020-10-31 15:50:39 +08:00
Kevin Hester
bbc36f7b6f switch to my new SD 7.2.0 S113 based bootloader 2020-10-31 12:22:20 +08:00
Jm Casler
2f9ef463d8 Merge pull request #507 from meshtastic/master
update dev-https from master
2020-10-30 21:04:16 -07:00
Jm Casler
bea00569fd Merge pull request #22 from meshtastic/master
update my fork from master
2020-10-30 21:03:40 -07:00
Kevin Hester
d7368d5a51 begin deep sleep support for nrf52 2020-10-30 17:05:32 +08:00
Kevin Hester
47bbde3c60 fix #505 allow forced standby mode to not assert fail 2020-10-29 16:13:44 +08:00
Kevin Hester
04942a3570 fix #505 - device can reboot due to race condition in sending 2020-10-29 15:27:05 +08:00
Jm Casler
b1dae3608e Merge pull request #502 from mc-hamster/master
Update to basic.js and add mime types.
2020-10-26 18:18:16 -07:00
Jm Casler
44aafd5b9c Update to basic.js and add mime types. 2020-10-26 18:17:33 -07:00
Jm Casler
91756d1fec Merge pull request #501 from mc-hamster/master
Fixed typo. Updated js library. Update root file handler.
2020-10-22 18:46:26 -07:00
Jm Casler
5981831bc0 Fixed typo. Updated js library. Update root file handler.
Typo fixed. I placed the latest javascript library files into the static folder. Updated the root file handler to be able to serve both compressed and uncompressed files.
2020-10-22 18:43:54 -07:00
Jm Casler
00eed206cb Update typo, import from js library 2020-10-22 18:26:43 -07:00
Jm Casler
130d55aaaa Merge pull request #500 from mc-hamster/master
Updated dev-https from my fork.
2020-10-21 21:29:34 -07:00
Jm Casler
13ef48094d Merge pull request #21 from meshtastic/dev-https
Dev https to my fork
2020-10-21 21:20:55 -07:00
Jm Casler
529fd5a830 Merge pull request #499 from meshtastic/master
Update dev-https from master
2020-10-21 21:20:07 -07:00
Jm Casler
baa3d1dae4 Support for the SPIFFS 2020-10-21 20:57:44 -07:00
Jm Casler
e9279919ae Remove uncompressed style.css. Combine SSID/PWD on same line and toggle between the two every 10 seconds. 2020-10-19 16:58:11 -07:00
92 changed files with 2059 additions and 2381 deletions

View File

@@ -49,7 +49,8 @@
"string_view": "cpp",
"cassert": "cpp",
"iterator": "cpp",
"shared_mutex": "cpp"
"shared_mutex": "cpp",
"iostream": "cpp"
},
"cSpell.words": [
"Blox",

View File

@@ -2,14 +2,14 @@
set -e
source bin/version.sh
VERSION=`bin/buildinfo.py`
COUNTRIES="US EU433 EU865 CN JP ANZ KR"
#COUNTRIES=US
#COUNTRIES=CN
BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
# BOARDS_ESP32=tbeam
#BOARDS_ESP32=tbeam
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
BOARDS_NRF52="lora-relay-v1"
@@ -87,8 +87,12 @@ platformio lib update
do_boards "$BOARDS_ESP32" "false"
do_boards "$BOARDS_NRF52" "true"
echo "Building SPIFFS for ESP32 targets"
pio run --environment tbeam -t buildfs
cp .pio/build/tbeam/spiffs.bin $OUTDIR/bins/universal/spiffs-$VERSION.bin
# keep the bins in archive also
cp $OUTDIR/bins/firmware* $OUTDIR/elfs/firmware* $OUTDIR/bins/universal/firmware* $OUTDIR/elfs/universal/firmware* $ARCHIVEDIR
cp $OUTDIR/bins/firmware* $OUTDIR/bins/universal/spiffs* $OUTDIR/elfs/firmware* $OUTDIR/bins/universal/firmware* $OUTDIR/elfs/universal/firmware* $ARCHIVEDIR
echo Updating android bins $OUTDIR/forandroid
rm -rf $OUTDIR/forandroid
@@ -109,6 +113,6 @@ XML
echo Generating $ARCHIVEDIR/firmware-$VERSION.zip
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $ARCHIVEDIR/spiffs-$VERSION.bin $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
echo BUILT ALL

11
bin/buildinfo.py Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python3
import configparser
config = configparser.RawConfigParser()
config.read('version.properties')
version = dict(config.items('VERSION'))
verStr = "{}.{}.{}".format(version["major"], version["minor"], version["build"])
print(f"{verStr}")

View File

@@ -1,5 +1,7 @@
#!/bin/sh
set -e
# Usage info
show_help() {
cat << EOF
@@ -36,6 +38,7 @@ 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 0x00390000 spiffs-*.bin
esptool.py --baud 921600 write_flash 0x10000 ${FILENAME}
else
echo "Invalid file: ${FILENAME}"

21
bin/platformio-custom.py Normal file
View File

@@ -0,0 +1,21 @@
Import("projenv")
import configparser
prefsLoc = projenv["PROJECT_DIR"] + "/version.properties"
print(f"Preferences in {prefsLoc}")
try:
config = configparser.RawConfigParser()
config.read(prefsLoc)
version = dict(config.items('VERSION'))
verStr = "{}.{}.{}".format(version["major"], version["minor"], version["build"])
except:
print("Can't read preferences, using 0.0.0")
verStr = "0.0.0"
print(f"Using meshtastic platform-custom.py, firmare version {verStr}")
# General options that are passed to the C and C++ compilers
projenv.Append(CCFLAGS=[
f"-DAPP_VERSION={verStr}"
])

View File

@@ -0,0 +1,6 @@
set -e
source bin/version.sh
esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/universal/firmware-tbeam-$VERSION.bin

View File

@@ -1,6 +1,12 @@
#!/bin/bash
set -e
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1"
# the nanopb tool seems to require that the .options file be in the current directory!
cd proto
../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src/mesh -I=../proto mesh.proto
../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src/mesh -I=../proto *.proto
echo "Regenerating protobuf documentation - if you see an error message"
echo "you can ignore it unless doing a new protobuf release to github."
bin/regen-docs.sh

View File

@@ -1,3 +0,0 @@
export VERSION=1.1.7

View File

@@ -1,7 +1,7 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
"ldscript": "nrf52840_s113_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
@@ -16,9 +16,9 @@
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_flags": "-DS113",
"sd_name": "s113",
"sd_version": "7.2.0",
"sd_fwid": "0x00B6"
},
"bootloader": {

43
data/static/basic.js Normal file
View File

@@ -0,0 +1,43 @@
var meshtasticClient;
var connectionOne;
// Important: the connect action must be called from a user interaction (e.g. button press), otherwise the browsers won't allow the connect
function connect() {
// Create new connection
var httpconn = new meshtasticjs.IHTTPConnection();
// Set connection params
let sslActive;
if (window.location.protocol === 'https:') {
sslActive = true;
} else {
sslActive = false;
}
let deviceIp = window.location.hostname; // Your devices IP here
// Add event listeners that get called when a new packet is received / state of device changes
httpconn.addEventListener('fromRadio', function (packet) { console.log(packet) });
// Connect to the device async, then send a text message
httpconn.connect(deviceIp, sslActive)
.then(result => {
alert('device has been configured')
// This gets called when the connection has been established
// -> send a message over the mesh network. If no recipient node is provided, it gets sent as a broadcast
return httpconn.sendText('meshtastic is awesome');
})
.then(result => {
// This gets called when the message has been sucessfully sent
console.log('Message sent!');
})
.catch(error => { console.log(error); });
}

18
data/static/index.html Normal file
View File

@@ -0,0 +1,18 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<title></title>
<script src="/static/meshtastic.js"></script>
<script src="/static/basic.js"></script>
</head>
<body>
<button id="connect_button" onclick="connect()">Connect to Meshtastic device</button>
</body>
</html>

Binary file not shown.

View File

@@ -1,277 +0,0 @@
/* latin-ext */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: local('Lato Regular'), local('Lato-Regular'), url(./Google.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
*, *:before, *:after {
box-sizing: border-box;
}
body {
background: #C5DDEB;
font: 14px/20px "Lato", Arial, sans-serif;
padding: 40px 0;
color: white;
}
.grid {
display: grid;
grid-template-columns:
1fr 4fr;
grid-template-areas:
"header header"
"sidebar content";
margin: 0 auto;
width: 750px;
background: #444753;
border-radius: 5px;
}
.top {grid-area: header;}
.side {grid-area: sidebar;}
.main {grid-area: content;}
.top {
border-bottom: 2px solid white;
}
.top-text {
font-weight: bold;
font-size: 24px;
text-align: center;
padding: 20px;
}
.side {
width: 260px;
float: left;
}
.side .side-header {
padding: 20px;
border-bottom: 2px solid white;
}
.side .side-header .side-text {
padding-left: 10px;
margin-top: 6px;
font-size: 16px;
text-align: left;
font-weight: bold;
}
.channel-list ul {
padding: 20px;
height: 570px;
list-style-type: none;
}
.channel-list ul li {
padding-bottom: 20px;
}
.channel-list .channel-name {
font-size: 20px;
margin-top: 8px;
padding-left: 8px;
}
.channel-list .message-count {
padding-left: 16px;
color: #92959E;
}
.icon {
display: inline-block;
width: 1em;
height: 1em;
stroke-width: 0;
stroke: currentColor;
fill: currentColor;
}
.icon-map-marker {
width: 0.5714285714285714em;
}
.icon-circle {
width: 0.8571428571428571em;
}
.content {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
/* width: 490px; */
float: left;
background: #F2F5F8;
/* border-top-right-radius: 5px;
border-bottom-right-radius: 5px; */
color: #434651;
}
.content .content-header {
flex-grow: 0;
padding: 20px;
border-bottom: 2px solid white;
}
.content .content-header .content-from {
padding-left: 10px;
margin-top: 6px;
font-size: 20px;
text-align: center;
font-size: 16px;
}
.content .content-header .content-from .content-from-highlight {
font-weight: bold;
}
.content .content-header .content-num-messages {
color: #92959E;
}
.content .content-history {
flex-grow: 1;
padding: 20px 20px 20px;
border-bottom: 2px solid white;
overflow-y: scroll;
height: 375px;
}
.content .content-history ul {
list-style-type: none;
padding-inline-start: 10px;
}
.content .content-history .message-data {
margin-bottom: 10px;
}
.content .content-history .message-data-time {
color: #a8aab1;
padding-left: 6px;
}
.content .content-history .message {
color: white;
padding: 8px 10px;
line-height: 20px;
font-size: 14px;
border-radius: 7px;
margin-bottom: 30px;
width: 90%;
position: relative;
}
.content .content-history .message:after {
bottom: 100%;
left: 7%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-bottom-color: #86BB71;
border-width: 10px;
margin-left: -10px;
}
.content .content-history .my-message {
background: #86BB71;
}
.content .content-history .other-message {
background: #94C2ED;
}
.content .content-history .other-message:after {
border-bottom-color: #94C2ED;
left: 93%;
}
.content .content-message {
flex-grow: 0;
padding: 10px;
}
.content .content-message textarea {
width: 100%;
border: none;
padding: 10px 10px;
font: 14px/22px "Lato", Arial, sans-serif;
margin-bottom: 10px;
border-radius: 5px;
resize: none;
}
.content .content-message button {
float: right;
color: #94C2ED;
font-size: 16px;
text-transform: uppercase;
border: none;
cursor: pointer;
font-weight: bold;
background: #F2F5F8;
}
.content .content-message button:hover {
color: #75b1e8;
}
/* Tooltip container */
.tooltip {
color: #86BB71;
position: relative;
display: inline-block;
border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
}
/* Tooltip text */
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #444753;
color: #fff;
text-align: center;
padding: 5px 0;
border-radius: 6px;
/* Position the tooltip text - see examples below! */
position: absolute;
z-index: 1;
}
/* Show the tooltip text when you mouse over the tooltip container */
.tooltip:hover .tooltiptext {
visibility: visible;
}
.online, .offline, .me {
margin-right: 3px;
font-size: 10px;
}
.online {
color: #86BB71;
}
.offline {
color: #E38968;
}
.me {
color: #94C2ED;
}
.align-left {
text-align: left;
}
.align-right {
text-align: right;
}
.float-right {
float: right;
}
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}

Binary file not shown.

View File

@@ -39,6 +39,10 @@ This software is 100% open source and developed by a group of hobbyist experimen
For an detailed walk-through aimed at beginners, we recommend [meshtastic.letstalkthis.com](https://meshtastic.letstalkthis.com/).
### Related Groups
Telegram group for **Italy**-based users [t.me/meshtastic_italia](http://t.me/meshtastic_italia) (Italian language, unofficial).
# Updates
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.

Binary file not shown.

Binary file not shown.

View File

@@ -8,7 +8,7 @@ See [this site](https://www.rfwireless-world.com/Tutorials/LoRa-channels-list.ht
## LoRaWAN Europe Frequency Band
The maximum power allowed is +14dBM.
The maximum power allowed is +14dBm ERP (Effective Radiated Power, see [this site](https://en.wikipedia.org/wiki/Effective_radiated_power) for more information).
### 433 MHz
@@ -24,7 +24,7 @@ Channel zero starts at 865.20 MHz
LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
The maximum output power for North America is +30 dBM.
The maximum output power for North America is +30 dBm ERP.
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are separated by 2.16 MHz with respect to the adjacent channels.
Channel zero starts at 903.08 MHz center frequency.
Channel zero starts at 903.08 MHz center frequency.

View File

@@ -2,31 +2,90 @@
You probably don't care about this section - skip to the next one.
Threading tasks:
For app cleanup:
- Use https://github.com/ivanseidel/ArduinoThread? rather than full coroutines
- clean up main loop()
- check that we are mostly asleep, show which thread is causing us to wake
-
- use tickless idle on nrf52, and sleep X msec or until an interrupt occurs or the cooperative scheduling changes. https://devzone.nordicsemi.com/f/nordic-q-a/12363/nrf52-freertos-power-consumption-tickless-idle
- BAD IDEA: use vTaskDelay and https://www.freertos.org/xTaskAbortDelay.html if scheduling changes. (define INCLUDE_xTaskAbortDelay on ESP32 and NRF52 - seems impossible to find?)
- GOOD IDEA: use xSemaphoreTake to take a semaphore using a timeout. Expect semaphore to not be set, but set it to indicate scheduling has changed.
* DONE make device build always have a valid version
* DONE do fixed position bug https://github.com/meshtastic/Meshtastic-device/issues/536
* check build guide
* generate autodocs
* write devapi user guide
* DONE update android code: https://developer.android.com/topic/libraries/view-binding/migration
* only do wantReplies once per packet type, if we change network settings force it again
* make gpio watch work, use thread and setup
* make hello world example service
* make python ping command
* make python gpio read a bit cleaner
* DONE have python tool check max packet size before sending to device
* DONE if request was sent reliably, send reply reliably
* DONE require a recent python api to talk to these new device loads
* DONE require a recent android app to talk to these new device loads
* DONE fix handleIncomingPosition
* DONE move want_replies handling into plugins
* DONE on android for received positions handle either old or new positions / user messages
* on android side send old or new positions as needed / user messages
* test python side handle new position/user messages
* DONE make a gpio example. --gpiowrb 4 1, --gpiord 0x444, --gpiowatch 0x3ff
* DONE fix position sending to use new plugin
* DONE Add SinglePortNumPlugin - as the new most useful baseclass
* DONE move positions into regular data packets (use new app framework)
* DONE move user info into regular data packets (use new app framework)
* DONE test that positions, text messages and user info still work
* DONE test that position, text messages and user info work properly with new android app and old device code
* fix the RTC drift bug
* move python ping functionality into device, reply with rxsnr info
* use channels for gpio security https://github.com/meshtastic/Meshtastic-device/issues/104
Nimble tasks:
For high speed/lots of devices/short range 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
- When guessing numhops for sending: if I've heard from many local (0 hop neighbors) decrease hopcount by 2 rather than 1.
This should nicely help 'router' nodes do the right thing when long range, or if there are many local nodes for short range.
- fix timeouts/delays to be based on packet length at current radio settings
* 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
# Old docs to merge
MESH RADIO PROTOCOL
Old TODO notes on the mesh radio protocol, merge into real docs someday...
for each named group we have a pre-shared key known by all group members and
wrapped around the device. you can only be in one group at a time (FIXME?!) To
join the group we read a qr code with the preshared key and ParamsCodeEnum. that
gets sent via bluetooth to the device. ParamsCodeEnum maps to a set of various
radio params (regulatory region, center freq, SF, bandwidth, bitrate, power
etc...) so all members of the mesh can have their radios set the same way.
once in that group, we can talk between 254 node numbers.
to get our node number (and announce our presence in the channel) we pick a
random node number and broadcast as that node with WANT-NODENUM(my globally
unique name). If anyone on the channel has seen someone _else_ using that name
within the last 24 hrs(?) they reply with DENY-NODENUM. Note: we might receive
multiple denies. Note: this allows others to speak up for some other node that
might be saving battery right now. Any time we hear from another node (for any
message type), we add that node number to the unpickable list. To dramatically
decrease the odds a node number we request is already used by someone. If no one
denies within TBD seconds, we assume that we have that node number. As long as
we keep talking to folks at least once every 24 hrs, others should remember we
have it.
Once we have a node number we can broadcast POSITION-UPDATE(my globally unique
name, lat, lon, alt, amt battery remaining). All receivers will use this to a)
update the mapping of who is at what node nums, b) the time of last rx, c)
position. If we haven't heard from that node in a while we reply to that node
(only) with our current POSITION_UPDATE state - so that node (presumably just
rejoined the network) can build a map of all participants.
We will periodically broadcast POSITION-UPDATE as needed based on distance moved
or a periodic minimum heartbeat.
If user wants to send a text they can SEND_TEXT(dest user, short text message).
Dest user is a node number, or 0xff for broadcast.
# Medium priority
Items to complete before 1.0.

View File

@@ -111,6 +111,7 @@ Characteristics
| e272ebac-d463-4b98-bc84-5cc1a39ee517 | write | data, variable sized, recommended 512 bytes, write one for each block of file |
| 4826129c-c22a-43a3-b066-ce8f0d5bacc6 | write | crc32, write last - writing this will complete the OTA operation, now you can read result |
| 5e134862-7411-4424-ac4a-210937432c77 | read,notify | result code, readable but will notify when the OTA operation completes |
| 5e134862-7411-4424-ac4a-210937432c67 | write | sets the region for programming, currently only 0 (app) or 100 (spiffs) are defined, if not set app is assumed |
| GATT_UUID_SW_VERSION_STR/0x2a28 | read | We also implement these standard GATT entries because SW update probably needs them: |
| GATT_UUID_MANU_NAME/0x2a29 | read | |
| GATT_UUID_HW_VERSION_STR/0x2a27 | read | |

View File

@@ -14,6 +14,13 @@ Notes here on using that driver: https://www.linuxquestions.org/questions/linux-
Or if **absolutely** necessary could bitbang: https://www.cnx-software.com/2018/02/16/wch-ch341-usb-to-serial-chip-gets-linux-driver-to-control-gpios-over-usb/
## Portduino tasks
* How to access spi devices via ioctl (spidev): https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md#:~:text=Troubleshooting-,Overview,bus)%2C%20UARTs%2C%20etc.
* access gpio via libgpiod?
* Use dkms to distribute driver?
* echo 100 > /sys/module/spi_ch341_usb/parameters/poll_period
## Task list
* Port meshtastic to build (under platformio) for a poxix target. spec: no screen, no gpios, sim network interface, posix threads, posix semaphores & queues, IO to the console only

View File

@@ -50,7 +50,7 @@ From lower to higher power consumption.
- At cold boot: The initial state (after setup() has run) is DARK
- While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep)
- While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages.
- While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 60 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages.
- While in LS: Every send*owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we \_also* broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments.
- While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout
- While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), we go to full ON mode for screen_on_secs (same as if user pressed a button)

View File

@@ -19,18 +19,20 @@ default_envs = tbeam # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you
; The following environment variables must be set in the shell if you'd like to override them.
; They are used in this ini file as systenv.VARNAME, so in your shell do export "VARNAME=fish"
; COUNTRY (default US), i.e. "export COUNTRY=EU865"
; APP_VERSION (default emptystring)
; HW_VERSION (default emptystring)
[env]
; note: APP_VERSION now comes from bin/version.json
extra_scripts = bin/platformio-custom.py
; 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 -Wl,-Map,.pio/build/output.map
-DHW_VERSION_${sysenv.COUNTRY}
-DAPP_VERSION=${sysenv.APP_VERSION}
-DHW_VERSION=${sysenv.HW_VERSION}
-DUSE_THREAD_NAMES
-DTINYGPSPLUS_OPTION_NO_CUSTOM_FIELDS
; leave this commented out to avoid breaking Windows
;upload_port = /dev/ttyUSB0
@@ -64,7 +66,7 @@ lib_deps =
https://github.com/meshtastic/arduino-fsm.git#2f106146071fc7bc620e1e8d4b88dc4e0266ce39
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
https://github.com/meshtastic/RadioLib.git#8657380241bce681c33aab46598bbf13b11f876c
https://github.com/meshtastic/TinyGPSPlus.git
https://github.com/meshtastic/TinyGPSPlus.git#9c1d584d2469523381e077b0b9c1bf868d6c0206
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
Wire ; explicitly needed here because the AXP202 library forgets to add it
SPI

2
proto

Submodule proto updated: a0b8d88896...ebd18145ca

21
src/FSCommon.cpp Normal file
View File

@@ -0,0 +1,21 @@
#include "FSCommon.h"
void fsInit()
{
#ifdef FS
if (!FSBegin())
{
DEBUG_MSG("ERROR filesystem mount Failed\n");
assert(0); // FIXME - report failure to phone
}
DEBUG_MSG("Filesystem files:\n");
File dir = FS.open("/");
File f = dir.openNextFile();
while (f) {
DEBUG_MSG(" %s\n", f.name());
f.close();
f = dir.openNextFile();
}
#endif
}

29
src/FSCommon.h Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#include "configuration.h"
// Cross platform filesystem API
#ifdef PORTDUINO
// Portduino version
#include "PortduinoFS.h"
#define FS PortduinoFS
#define FSBegin() true
#define FILE_O_WRITE "w"
#define FILE_O_READ "r"
#elif !defined(NO_ESP32)
// ESP32 version
#include "SPIFFS.h"
#define FS SPIFFS
#define FSBegin() FS.begin(true)
#define FILE_O_WRITE "w"
#define FILE_O_READ "r"
#else
// NRF52 version
#include "InternalFileSystem.h"
#define FS InternalFS
#define FSBegin() FS.begin()
using namespace Adafruit_LittleFS_Namespace;
#endif
void fsInit();

View File

@@ -134,6 +134,14 @@ bool Power::setup()
return found;
}
void Power::shutdown()
{
#ifdef TBEAM_V10
DEBUG_MSG("Shutting down\n");
axp.shutdown();
#endif
}
/// Reads power status to powerStatus singleton.
//
// TODO(girts): move this and other axp stuff to power.h/power.cpp.

View File

@@ -251,13 +251,11 @@ void PowerFSM_setup()
#ifndef NRF52_SERIES
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
lowPowerState = &stateDARK;
powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout");
powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout");
powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout");
#else
lowPowerState = &stateDARK;
#endif
auto meshSds = getPref_mesh_sds_timeout_secs();

View File

@@ -1,4 +1,5 @@
#include "RedirectablePrint.h"
#include "configuration.h"
#include <assert.h>
/**
@@ -10,4 +11,14 @@ void RedirectablePrint::setDestination(Print *_dest)
{
assert(_dest);
dest = _dest;
}
size_t RedirectablePrint::write(uint8_t c)
{
#ifdef SEGGER_STDOUT_CH
SEGGER_RTT_PutCharSkip(SEGGER_STDOUT_CH, c);
#endif
dest->write(c);
return 1; // We always claim one was written, rather than trusting what the serial port said (which could be zero)
}

View File

@@ -19,7 +19,7 @@ class RedirectablePrint : public Print
*/
void setDestination(Print *dest);
virtual size_t write(uint8_t c) { return dest->write(c); }
virtual size_t write(uint8_t c);
};
class NoopPrint : public Print

View File

@@ -1,6 +1,7 @@
#include "SerialConsole.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "NodeDB.h"
#include <Arduino.h>
#define Port Serial
@@ -28,7 +29,8 @@ void SerialConsole::init()
void SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
{
// Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets
setDestination(&noopPrint);
if(!radioConfig.preferences.debug_log_enabled)
setDestination(&noopPrint);
canWrite = true;
StreamAPI::handleToRadio(buf, len);

View File

@@ -31,9 +31,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// If app version is not specified we assume we are not being invoked by the build script
#ifndef APP_VERSION
#error APP_VERSION, HW_VERSION, and HW_VERSION_countryname must be set by the build environment
//#define APP_VERSION 0.0.0 // this def normally comes from build-all.sh
//#define HW_VERSION 1.0 - US // normally comes from build-all.sh and contains the region code
#error APP_VERSION must be set by the build environment
#endif
// If app version is not specified we assume we are not being invoked by the build script
#ifndef HW_VERSION
#error HW_VERSION, and HW_VERSION_countryname must be set by the build environment
#endif
// -----------------------------------------------------------------------------
@@ -404,8 +407,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Always include the SEGGER code on NRF52 - because useful for debugging
#include "SEGGER_RTT.h"
// The channel we send stdout data to
#define SEGGER_STDOUT_CH 0
// Debug printing to segger console
#define SEGGER_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#define SEGGER_MSG(...) SEGGER_RTT_printf(SEGGER_STDOUT_CH, __VA_ARGS__)
// 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

View File

@@ -6,6 +6,7 @@
#include "RadioLibInterface.h"
#include "configuration.h"
#include "nimble/BluetoothUtil.h"
#include "NodeDB.h"
#include <CRC32.h>
#include <Update.h>
@@ -16,6 +17,8 @@ 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)
static uint32_t updateExpectedSize, updateActualSize;
static uint8_t update_result;
static uint8_t update_region;
static concurrency::Lock *updateLock;
@@ -32,8 +35,8 @@ int update_size_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
crc.reset();
if (Update.isRunning())
Update.abort();
bool canBegin = Update.begin(updateExpectedSize);
DEBUG_MSG("Setting update size %u, result %d\n", updateExpectedSize, canBegin);
bool canBegin = Update.begin(updateExpectedSize, update_region);
DEBUG_MSG("Setting region %d update size %u, result %d\n", update_region, updateExpectedSize, canBegin);
if (!canBegin) {
// Indicate failure by forcing the size to 0 (client will read it back)
updateExpectedSize = 0;
@@ -72,13 +75,11 @@ int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
crc.update(data, len);
Update.write(data, len);
updateActualSize += len;
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
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)
{
@@ -100,8 +101,14 @@ int update_crc32_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble
result = 0xe0; // FIXME, use real error codes
} else {
if (Update.end()) {
DEBUG_MSG("OTA done, rebooting in 5 seconds!\n");
rebootAtMsec = millis() + 5000;
if (update_region == U_SPIFFS) {
DEBUG_MSG("SPIFFS updated!\n");
nodeDB.saveToDisk(); // Since we just wiped spiffs, we need to save our current state
}
else {
DEBUG_MSG("Appload updated, rebooting in 5 seconds!\n");
rebootAtMsec = millis() + 5000;
}
} else {
DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError());
}
@@ -125,6 +132,11 @@ int update_result_callback(uint16_t conn_handle, uint16_t attr_handle, struct bl
return chr_readwrite8(&update_result, sizeof(update_result), ctxt);
}
int update_region_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
return chr_readwrite8(&update_region, sizeof(update_region), ctxt);
}
void bluetoothRebootCheck()
{
if (rebootAtMsec && millis() > rebootAtMsec) {

View File

@@ -14,10 +14,11 @@ int update_size_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
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);
int update_region_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 const ble_uuid128_t update_result_uuid, update_region_uuid;
extern int16_t updateResultHandle;

View File

@@ -23,6 +23,10 @@ const ble_uuid128_t update_crc32_uuid =
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);
// "5e134862-7411-4424-ac4a-210937432c67" write
const ble_uuid128_t update_region_uuid =
BLE_UUID128_INIT(0x67, 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. */
@@ -47,9 +51,14 @@ const struct ble_gatt_svc_def gatt_update_svcs[] = {
},
{
.uuid = &update_result_uuid.u,
.access_cb = update_size_callback,
.access_cb = update_result_callback,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_NOTIFY,
},
{
.uuid = &update_region_uuid.u,
.access_cb = update_region_callback,
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_AUTHEN,
},
{
0, /* No more characteristics in this service. */
}},

View File

@@ -9,6 +9,7 @@
#include "utils.h"
#include <nvs.h>
#include <nvs_flash.h>
#include <driver/rtc_io.h>
void getMacAddr(uint8_t *dmac)
{
@@ -86,4 +87,58 @@ void esp32Loop()
// for debug printing
// radio.radioIf.canSleep();
}
void cpuDeepSleep(uint64_t msecToWake)
{
/*
Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default.
If an external circuit drives this pin in deep sleep mode, current consumption may
increase due to current flowing through these pullups and pulldowns.
To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function.
For example, on ESP32-WROVER module, GPIO12 is pulled up externally.
GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep,
some current will flow through these external and internal resistors, increasing deep
sleep current above the minimal possible value.
Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button
*/
static const uint8_t rtcGpios[] = {/* 0, */ 2,
/* 4, */
#ifndef USE_JTAG
13,
/* 14, */ /* 15, */
#endif
/* 25, */ 26, /* 27, */
32, 33, 34, 35,
36, 37
/* 38, 39 */};
for (int i = 0; i < sizeof(rtcGpios); i++)
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
// to detect wake and in normal operation the external part drives them hard.
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#ifdef BUTTON_PIN
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
uint64_t gpioMask = (1ULL << BUTTON_PIN);
#ifdef BUTTON_NEED_PULLUP
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
#endif
// Not needed because both of the current boards have external pullups
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of
// just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
#endif
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
}

View File

@@ -25,9 +25,15 @@ uint8_t GPS::i2cAddress = 0;
GPS *gps;
/// Multiple GPS instances might use the same serial port (in sequence), but we can
/// only init that port once.
static bool didSerialInit;
bool GPS::setupGPS()
{
if (_serial_gps) {
if (_serial_gps && !didSerialInit) {
didSerialInit = true;
#ifdef GPS_RX_PIN
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
#else
@@ -59,8 +65,10 @@ bool GPS::setup()
setAwake(true); // Wake GPS power before doing any init
bool ok = setupGPS();
if (ok)
if (ok) {
notifySleepObserver.observe(&notifySleep);
notifyDeepSleepObserver.observe(&notifyDeepSleep);
}
return ok;
}
@@ -275,7 +283,19 @@ void GPS::forceWake(bool on)
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
int GPS::prepareSleep(void *unused)
{
DEBUG_MSG("GPS prepare sleep!\n");
forceWake(false);
return 0;
}
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
int GPS::prepareDeepSleep(void *unused)
{
DEBUG_MSG("GPS deep sleep!\n");
// For deep sleep we also want abandon any lock attempts (because we want minimum power)
setAwake(false);
return 0;
}

View File

@@ -30,6 +30,7 @@ class GPS : private concurrency::OSThread
uint8_t numSatellites = 0;
CallbackObserver<GPS, void *> notifySleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareSleep);
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
public:
/** If !NULL we will use this serial port to construct our GPS */
@@ -115,6 +116,10 @@ class GPS : private concurrency::OSThread
/// always returns 0 to indicate okay to sleep
int prepareSleep(void *unused);
/// Prepare the GPS for the cpu entering deep sleep, expect to be gone for at least 100s of msecs
/// always returns 0 to indicate okay to sleep
int prepareDeepSleep(void *unused);
/**
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
*

View File

@@ -32,6 +32,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "main.h"
#include "mesh-pb-constants.h"
#include "meshwifi/meshwifi.h"
#include "plugins/TextMessagePlugin.h"
#include "target_specific.h"
#include "utils.h"
@@ -85,13 +86,16 @@ static uint16_t displayWidth, displayHeight;
#define SCREEN_TRANSITION_MSECS 300
#endif
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
/**
* Draw the icon with extra info printed around the corners
*/
static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// draw an xbm image.
// Please note that everything that should be transitioned
// needs to be drawn relative to x and y
// draw centered left to right and centered above the one line of app text
// draw centered icon left to right and centered above the one line of app text
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
icon_width, icon_height, (const uint8_t *)icon_bits);
@@ -101,15 +105,31 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL);
const char *region = myRegion ? myRegion->name : NULL;
if (region)
display->drawString(x + 0, y + 0, region);
// Draw region in upper left
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
// Draw version in upper right
char buf[16];
snprintf(buf, sizeof(buf), "%s",
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf);
screen->forceDisplay();
// FIXME - draw serial # somewhere?
}
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Draw region in upper left
const char *region = myRegion ? myRegion->name : NULL;
drawIconScreen(region, display, state, x, y);
}
/// Used on eink displays while in deep sleep
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
drawIconScreen("Sleeping...", display, state, x, y);
}
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -600,6 +620,21 @@ Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue
cmdQueue.setReader(this);
}
/**
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
*/
void Screen::doDeepSleep()
{
#ifdef HAS_EINK
static FrameCallback sleepFrames[] = {drawSleepScreen};
static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
ui.setFrames(sleepFrames, sleepFrameCount);
ui.update();
#endif
setOn(false);
}
void Screen::handleSetOn(bool on)
{
if (!useDisplay)
@@ -636,7 +671,7 @@ void Screen::setup()
displayWidth = dispdev.width();
displayHeight = dispdev.height();
ui.setTimePerTransition(SCREEN_TRANSITION_MSECS);
ui.setTimePerTransition(SCREEN_TRANSITION_MSECS);
ui.setIndicatorPosition(BOTTOM);
// Defines where the first frame is located in the bar.
@@ -686,6 +721,7 @@ void Screen::setup()
powerStatusObserver.observe(&powerStatus->onNewStatus);
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
textMessageObserver.observe(&textMessagePlugin);
}
void Screen::forceDisplay()
@@ -1052,7 +1088,7 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i
}
}
if ((millis() / 1000) % 2) {
if ((millis() / 10000) % 2) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName));
} else {
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "PWD: " + String(wifiPsw));
@@ -1149,14 +1185,24 @@ 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 (showingNormalScreen && (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal())) {
if (showingNormalScreen &&
nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
setFrames(); // Regen the list of screens
}
nodeDB.updateGUI = false;
nodeDB.updateTextMessage = false;
break;
}
return 0;
}
int Screen::handleTextMessage(const MeshPacket *arg)
{
if (showingNormalScreen) {
setFrames(); // Regen the list of screens (will show new text message)
}
return 0;
}
} // namespace graphics

View File

@@ -77,6 +77,8 @@ class Screen : public concurrency::OSThread
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 MeshPacket *> textMessageObserver =
CallbackObserver<Screen, const MeshPacket *>(this, &Screen::handleTextMessage);
public:
Screen(uint8_t address, int sda = -1, int scl = -1);
@@ -99,6 +101,12 @@ class Screen : public concurrency::OSThread
enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
}
/**
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
*/
void doDeepSleep();
/// Handles a button press.
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }
@@ -186,6 +194,7 @@ class Screen : public concurrency::OSThread
DebugInfo *debug_info() { return &debugInfo; }
int handleStatusUpdate(const meshtastic::Status *arg);
int handleTextMessage(const MeshPacket *arg);
/// Used to force (super slow) eink displays to draw critical frames
void forceDisplay();

View File

@@ -11,6 +11,7 @@
// #include "rom/rtc.h"
#include "DSRRouter.h"
// #include "debug.h"
#include "FSCommon.h"
#include "RTC.h"
#include "SPILock.h"
#include "concurrency/OSThread.h"
@@ -167,21 +168,35 @@ class ButtonThread : public OSThread
#endif
public:
static uint32_t longPressTime;
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
ButtonThread() : OSThread("Button")
{
#ifdef BUTTON_PIN
userButton = OneButton(BUTTON_PIN, true, true);
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(BUTTON_PIN, INPUT_PULLUP_SENSE);
#endif
userButton.attachClick(userButtonPressed);
userButton.attachDuringLongPress(userButtonPressedLong);
userButton.attachDoubleClick(userButtonDoublePressed);
userButton.attachLongPressStart(userButtonPressedLongStart);
userButton.attachLongPressStop(userButtonPressedLongStop);
wakeOnIrq(BUTTON_PIN, FALLING);
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
#endif
userButtonAlt.attachClick(userButtonPressed);
userButtonAlt.attachDuringLongPress(userButtonPressedLong);
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
#endif
}
@@ -201,7 +216,7 @@ class ButtonThread : public OSThread
canSleep &= userButtonAlt.isIdle();
#endif
// if (!canSleep) DEBUG_MSG("Supressing sleep!\n");
//else DEBUG_MSG("sleep ok\n");
// else DEBUG_MSG("sleep ok\n");
return 5;
}
@@ -214,25 +229,53 @@ class ButtonThread : public OSThread
}
static void userButtonPressedLong()
{
DEBUG_MSG("Long press!\n");
// DEBUG_MSG("Long press!\n");
screen->adjustBrightness();
}
static void userButtonDoublePressed()
{
#ifndef NO_ESP32
disablePin();
// If user button is held down for 10 seconds, shutdown the device.
if (millis() - longPressTime > 10 * 1000) {
#ifdef TBEAM_V10
if (axp192_found == true) {
power->shutdown();
}
#endif
}
} else {
//DEBUG_MSG("Long press %u\n", (millis() - longPressTime));
}
}
static void userButtonDoublePressed()
{
#ifndef NO_ESP32
disablePin();
#endif
}
static void userButtonPressedLongStart()
{
DEBUG_MSG("Long press start!\n");
longPressTime = millis();
}
static void userButtonPressedLongStop()
{
DEBUG_MSG("Long press stop!\n");
longPressTime = 0;
}
};
static Periodic *ledPeriodic;
static OSThread *powerFSMthread, *buttonThread;
uint32_t ButtonThread::longPressTime = 0;
RadioInterface *rIf = NULL;
void setup()
{
#ifdef SEGGER_STDOUT_CH
SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, 1024, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
#endif
#ifdef USE_SEGGER
SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
#endif
@@ -258,6 +301,8 @@ void setup()
ledPeriodic = new Periodic("Blink", ledBlinker);
fsInit();
router = new DSRRouter();
#ifdef I2C_SDA

69
src/mesh/MeshPlugin.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include "MeshPlugin.h"
#include "NodeDB.h"
#include "MeshService.h"
#include <assert.h>
std::vector<MeshPlugin *> *MeshPlugin::plugins;
MeshPlugin::MeshPlugin(const char *_name) : name(_name)
{
// Can't trust static initalizer order, so we check each time
if(!plugins)
plugins = new std::vector<MeshPlugin *>();
plugins->push_back(this);
}
void MeshPlugin::setup() {
}
MeshPlugin::~MeshPlugin()
{
assert(0); // FIXME - remove from list of plugins once someone needs this feature
}
void MeshPlugin::callPlugins(const MeshPacket &mp)
{
// DEBUG_MSG("In call plugins\n");
for (auto i = plugins->begin(); i != plugins->end(); ++i) {
auto &pi = **i;
if (pi.wantPortnum(mp.decoded.data.portnum)) {
bool handled = pi.handleReceived(mp);
// Possibly send replies (unless we are handling a locally generated message)
if (mp.decoded.want_response && mp.from != nodeDB.getNodeNum())
pi.sendResponse(mp);
DEBUG_MSG("Plugin %s handled=%d\n", pi.name, handled);
if (handled)
break;
}
else {
DEBUG_MSG("Plugin %s not interested\n", pi.name);
}
}
}
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. Implementing this method
* is optional
*/
void MeshPlugin::sendResponse(const MeshPacket &req) {
auto r = allocReply();
if(r) {
DEBUG_MSG("Sending response\n");
setReplyTo(r, req);
service.sendToMesh(r);
}
else {
DEBUG_MSG("WARNING: Client requested response but this plugin did not provide\n");
}
}
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
*/
void setReplyTo(MeshPacket *p, const MeshPacket &to) {
p->to = to.from;
p->want_ack = to.want_ack;
}

67
src/mesh/MeshPlugin.h Normal file
View File

@@ -0,0 +1,67 @@
#pragma once
#include "mesh/MeshTypes.h"
#include <vector>
/** A baseclass for any mesh "plugin".
*
* A plugin allows you to add new features to meshtastic device code, without needing to know messaging details.
*
* A key concept for this is that your plugin should use a particular "portnum" for each message type you want to receive
* and handle.
*
* Interally we use plugins to implement the core meshtastic text messaging and gps position sharing features. You
* can use these classes as examples for how to write your own custom plugin. See here: (FIXME)
*/
class MeshPlugin
{
static std::vector<MeshPlugin *> *plugins;
public:
/** Constructor
* name is for debugging output
*/
MeshPlugin(const char *_name);
virtual ~MeshPlugin();
/** For use only by MeshService
*/
static void callPlugins(const MeshPacket &mp);
protected:
const char *name;
/**
* Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have
* been initialized
*/
virtual void setup();
/**
* @return true if you want to receive the specified portnum
*/
virtual bool wantPortnum(PortNum p) = 0;
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual bool handleReceived(const MeshPacket &mp) { return false; }
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual MeshPacket *allocReply() { return NULL; }
private:
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. This method calls allocReply()
* to generate the reply message, and if !NULL that message will be delivered to whoever sent req
*/
void sendResponse(const MeshPacket &req);
};
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
*/
void setReplyTo(MeshPacket *p, const MeshPacket &to);

View File

@@ -13,6 +13,8 @@
#include "RTC.h"
#include "main.h"
#include "mesh-pb-constants.h"
#include "plugins/PositionPlugin.h"
#include "plugins/NodeInfoPlugin.h"
#include "power.h"
/*
@@ -51,7 +53,7 @@ MeshService service;
static int32_t sendOwnerCb()
{
service.sendOurOwner();
nodeInfoPlugin.sendOurNodeInfo();
return getPref_send_owner_interval() * getPref_position_broadcast_secs() * 1000;
}
@@ -74,113 +76,25 @@ void MeshService::init()
packetReceivedObserver.observe(&router->notifyPacketReceived);
}
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
{
MeshPacket *p = router->allocForSending();
p->to = dest;
p->decoded.want_response = wantReplies;
p->decoded.which_payload = SubPacket_user_tag;
User &u = p->decoded.user;
u = owner;
DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name);
sendToMesh(p);
}
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp)
{
bool wasBroadcast = mp->to == NODENUM_BROADCAST;
// Disable this collision testing if we use 32 bit nodenums
bool isCollision = (sizeof(NodeNum) == 1) && (mp->from == myNodeInfo.my_node_num);
if (isCollision) {
// we win if we have a lower macaddr
bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0;
if (weWin) {
DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n");
mp = NULL;
sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum
} else {
// we lost, we need to try for a new nodenum!
DEBUG_MSG("NOTE! Received a nodenum collision we lost, so picking a new nodenum\n");
nodeDB.updateFrom(
*mp); // update the DB early - before trying to repick (so we don't select the same node number again)
nodeDB.pickNewNodeNum();
sendOurOwner(); // broadcast our new attempt at a node number
}
} else if (wasBroadcast) {
// If we haven't yet abandoned the packet and it was a broadcast, reply (just to them) with our User record so they can
// build their DB
// Someone just sent us a User, reply with our Owner
DEBUG_MSG("Received broadcast Owner from 0x%x, replying with our owner\n", mp->from);
sendOurOwner(mp->from);
String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n";
screen->print(lcd.c_str());
}
return mp;
}
void MeshService::handleIncomingPosition(const MeshPacket *mp)
{
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_position_tag) {
DEBUG_MSG("handled incoming position time=%u\n", mp->decoded.position.time);
if (mp->decoded.position.time) {
struct timeval tv;
uint32_t secs = mp->decoded.position.time;
tv.tv_sec = secs;
tv.tv_usec = 0;
perhapsSetRTC(RTCQualityFromNet, &tv);
}
} else {
DEBUG_MSG("Ignoring incoming packet - not a position\n");
}
}
int MeshService::handleFromRadio(const MeshPacket *mp)
{
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
// If it is a position packet, perhaps set our clock - this must be before nodeDB.updateFrom
handleIncomingPosition(mp);
printPacket("Forwarding to phone", mp);
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) {
mp = handleFromRadioUser(mp);
fromNum++;
if (toPhoneQueue.numFree() == 0) {
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
MeshPacket *d = toPhoneQueue.dequeuePtr(0);
if (d)
releaseToPool(d);
}
// If we veto a received User packet, we don't put it into the DB or forward it to the phone (to prevent confusing it)
if (mp) {
printPacket("Forwarding to phone", mp);
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
fromNum++;
if (toPhoneQueue.numFree() == 0) {
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
MeshPacket *d = toPhoneQueue.dequeuePtr(0);
if (d)
releaseToPool(d);
}
MeshPacket *copied = packetPool.allocCopy(*mp);
assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages
if (mp->decoded.want_response)
sendNetworkPing(mp->from);
} else {
DEBUG_MSG("Not delivering vetoed User message\n");
}
MeshPacket *copied = packetPool.allocCopy(*mp);
assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages
return 0;
}
@@ -201,7 +115,7 @@ bool MeshService::reloadConfig()
// This will also update the region as needed
bool didReset = nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings
configChanged.notifyObservers(NULL);
nodeDB.saveToDisk();
@@ -211,7 +125,7 @@ bool MeshService::reloadConfig()
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
void MeshService::reloadOwner()
{
sendOurOwner();
nodeInfoPlugin.sendOurNodeInfo();
nodeDB.saveToDisk();
}
@@ -222,8 +136,6 @@ void MeshService::reloadOwner()
*/
void MeshService::handleToRadio(MeshPacket &p)
{
handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock
if (p.from == 0) // If the phone didn't set a sending node ID, use ours
p.from = nodeDB.getNodeNum();
@@ -253,7 +165,8 @@ void MeshService::sendToMesh(MeshPacket *p)
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
// nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless
// devices can get time.
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) {
if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag &&
p->decoded.position.time) {
if (getRTCQuality() < RTCQualityGPS) {
DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time);
p->decoded.position.time = 0;
@@ -272,36 +185,16 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
DEBUG_MSG("Sending network ping to 0x%x, with position=%d, wantReplies=%d\n", dest, node->has_position, wantReplies);
if (node->has_position)
sendOurPosition(dest, wantReplies);
positionPlugin.sendOurPosition(dest, wantReplies);
else
sendOurOwner(dest, wantReplies);
}
void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
{
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
assert(node);
assert(node->has_position);
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = router->allocForSending();
p->to = dest;
p->decoded.which_payload = SubPacket_position_tag;
p->decoded.position = node->position;
p->decoded.want_response = wantReplies;
p->decoded.position.time =
getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid.
sendToMesh(p);
nodeInfoPlugin.sendOurNodeInfo(dest, wantReplies);
}
int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
{
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = router->allocForSending();
p->decoded.which_payload = SubPacket_position_tag;
Position &pos = p->decoded.position;
Position pos = Position_init_default;
if (gps->hasLock()) {
if (gps->altitude != 0)
@@ -309,6 +202,17 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
pos.latitude_i = gps->latitude;
pos.longitude_i = gps->longitude;
}
else {
// The GPS has lost lock, if we are fixed position we should just keep using
// the old position
if(radioConfig.preferences.fixed_position) {
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
assert(node);
assert(node->has_position);
pos = node->position;
DEBUG_MSG("WARNING: Using fixed position\n");
}
}
pos.time = getValidTime(RTCQualityGPS);
@@ -316,21 +220,18 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *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);
DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.latitude_i, pos.time, pos.battery_level);
// Update our current position in the local DB
nodeDB.updatePosition(nodeDB.getNodeNum(), pos);
// We limit our GPS broadcasts to a max rate
static uint32_t lastGpsSend;
uint32_t now = millis();
if (lastGpsSend == 0 || now - lastGpsSend > getPref_position_broadcast_secs() * 1000) {
lastGpsSend = now;
DEBUG_MSG("Sending position to mesh\n");
sendToMesh(p);
} else {
// We don't need to send this packet to anyone else, but it still serves as a nice uniform way to update our local state
nodeDB.updateFrom(*p);
releaseToPool(p);
DEBUG_MSG("Sending position to mesh (not requesting replies)\n");
positionPlugin.sendOurPosition();
}
return 0;

View File

@@ -75,18 +75,13 @@ class MeshService
/// sends our owner
void sendNetworkPing(NodeNum dest, bool wantReplies = false);
/// Send our owner info to a particular node
void sendOurOwner(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
private:
/// Broadcasts our last known position
void sendOurPosition(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after
/// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb
/// cache
void sendToMesh(MeshPacket *p);
private:
/// 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(const meshtastic::GPSStatus *arg);
@@ -94,12 +89,6 @@ class MeshService
/// 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
int handleFromRadio(const MeshPacket *p);
/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all
const MeshPacket *handleFromRadioUser(const MeshPacket *mp);
/// look at inbound packets and if they contain a position with time, possibly set our clock
void handleIncomingPosition(const MeshPacket *mp);
};
extern MeshService service;

View File

@@ -9,7 +9,7 @@
typedef uint32_t NodeNum;
typedef uint32_t PacketId; // A packet sequence number
#define NODENUM_BROADCAST (sizeof(NodeNum) == 4 ? UINT32_MAX : UINT8_MAX)
#define NODENUM_BROADCAST UINT32_MAX
#define ERRNO_OK 0
#define ERRNO_NO_INTERFACES 33
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER

View File

@@ -16,6 +16,7 @@
#include "error.h"
#include "mesh-pb-constants.h"
#include "meshwifi/meshwifi.h"
#include "FSCommon.h"
#include <pb_decode.h>
#include <pb_encode.h>
@@ -35,27 +36,7 @@ DeviceState versions used to be defined in the .proto file but really only this
#define DEVICESTATE_CUR_VER 11
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
#ifdef PORTDUINO
// Portduino version
#include "PortduinoFS.h"
#define FS PortduinoFS
#define FSBegin() true
#define FILE_O_WRITE "w"
#define FILE_O_READ "r"
#elif !defined(NO_ESP32)
// ESP32 version
#include "SPIFFS.h"
#define FS SPIFFS
#define FSBegin() FS.begin(true)
#define FILE_O_WRITE "w"
#define FILE_O_READ "r"
#else
// NRF52 version
#include "InternalFileSystem.h"
#define FS InternalFS
#define FSBegin() FS.begin()
using namespace Adafruit_LittleFS_Namespace;
#endif
// FIXME - move this somewhere else
extern void getMacAddr(uint8_t *dmac);
@@ -143,16 +124,20 @@ bool NodeDB::resetRadioConfig()
DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n");
// Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins
radioConfig.preferences.screen_on_secs = 30;
radioConfig.preferences.wait_bluetooth_secs = 30;
radioConfig.preferences.screen_on_secs = 10;
radioConfig.preferences.wait_bluetooth_secs = 10;
radioConfig.preferences.position_broadcast_secs = 6 * 60;
radioConfig.preferences.ls_secs = 60;
radioConfig.preferences.region = RegionCode_TW;
// Enter super deep sleep soon and stay there not very long
// radioConfig.preferences.mesh_sds_timeout_secs = 10;
// radioConfig.preferences.sds_secs = 60;
}
// Update the global myRegion
initRegion();
return didFactoryReset;
}
@@ -180,7 +165,6 @@ void NodeDB::installDefaultDeviceState()
// default to no GPS, until one has been found by probing
myNodeInfo.has_gps = false;
myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME;
myNodeInfo.min_app_version = 172;
generatePacketId(); // FIXME - ugly way to init current_packet_id;
// Init our blank owner info to reasonable defaults
@@ -206,12 +190,6 @@ void NodeDB::init()
{
installDefaultDeviceState();
if (!FSBegin()) // FIXME - do this in main?
{
DEBUG_MSG("ERROR filesystem mount Failed\n");
assert(0); // FIXME - report failure to phone
}
// saveToDisk();
loadFromDisk();
// saveToDisk();
@@ -221,6 +199,9 @@ void NodeDB::init()
myNodeInfo.node_num_bits = sizeof(NodeNum) * 8;
myNodeInfo.packet_id_bits = sizeof(PacketId) * 8;
// likewise - we always want the app requirements to come from the running appload
myNodeInfo.min_app_version = 20120; // format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
// Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
// keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
pickNewNodeNum();
@@ -269,8 +250,7 @@ void NodeDB::pickNewNodeNum()
// If we don't have a nodenum at app - pick an initial nodenum based on the macaddr
if (r == 0)
r = sizeof(NodeNum) == 1 ? ourMacAddr[5]
: ((ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]);
r = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
if (r == NODENUM_BROADCAST || r < NUM_RESERVED)
r = NUM_RESERVED; // don't pick a reserved node number
@@ -400,6 +380,48 @@ size_t NodeDB::getNumOnlineNodes()
return numseen;
}
#include "MeshPlugin.h"
/** Update position info for this node based on received position data
*/
void NodeDB::updatePosition(uint32_t nodeId, const Position &p)
{
NodeInfo *info = getOrCreateNode(nodeId);
DEBUG_MSG("DB update position node=0x%x time=%u, latI=%d, lonI=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i);
info->position = p;
info->has_position = true;
updateGUIforNode = info;
notifyObservers(true); // Force an update whether or not our node counts have changed
}
/** Update user info for this node based on received user data
*/
void NodeDB::updateUser(uint32_t nodeId, const User &p)
{
NodeInfo *info = getOrCreateNode(nodeId);
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
bool changed = memcmp(&info->user, &p,
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
info->user = p;
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
info->has_user = true;
if (changed) {
updateGUIforNode = info;
powerFSM.trigger(EVENT_NODEDB_UPDATED);
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
// saveToDisk();
}
}
/// given a subpacket sniffed from the network, update our DB state
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void NodeDB::updateFrom(const MeshPacket &mp)
@@ -419,58 +441,21 @@ void NodeDB::updateFrom(const MeshPacket &mp)
switch (p.which_payload) {
case SubPacket_position_tag: {
// we always trust our local timestamps more
info->position = p.position;
if (mp.rx_time)
info->position.time = mp.rx_time;
info->has_position = true;
updateGUIforNode = info;
notifyObservers(true); // Force an update whether or not our node counts have changed
// handle a legacy position packet
DEBUG_MSG("WARNING: Processing a (deprecated) position packet from %d\n", mp.from);
updatePosition(mp.from, p.position);
break;
}
case SubPacket_data_tag: {
// Keep a copy of the most recent text message.
if (p.data.typ == Data_Type_CLEAR_TEXT) {
DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.data.payload.size,
p.data.payload.bytes);
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) {
// We only store/display messages destined for us.
devicestate.rx_text_message = 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
// This is going into the wifidev feature branch
// Only update the WebUI if WiFi is enabled
//#if WiFi_MODE != 0
// notifyWebUI();
//#endif
}
}
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum())
MeshPlugin::callPlugins(mp);
break;
}
case SubPacket_user_tag: {
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
bool changed = memcmp(&info->user, &p.user,
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
info->user = p.user;
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
info->has_user = true;
if (changed) {
updateGUIforNode = info;
powerFSM.trigger(EVENT_NODEDB_UPDATED);
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
// saveToDisk();
}
DEBUG_MSG("WARNING: Processing a (deprecated) user packet from %d\n", mp.from);
updateUser(mp.from, p.user);
break;
}

View File

@@ -33,7 +33,6 @@ class NodeDB
public:
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
bool updateTextMessage = false; // if true, the GUI should show a new text message
Observable<const meshtastic::NodeStatus *> newStatus;
/// don't do mesh based algoritm for node id assignment (initially)
@@ -58,6 +57,14 @@ class NodeDB
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void updateFrom(const MeshPacket &p);
/** Update position info for this node based on received position data
*/
void updatePosition(uint32_t nodeId, const Position &p);
/** Update user info for this node based on received user data
*/
void updateUser(uint32_t nodeId, const User &p);
/// @return our node number
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }

View File

@@ -0,0 +1,3 @@
#include "ProtobufPlugin.h"

66
src/mesh/ProtobufPlugin.h Normal file
View File

@@ -0,0 +1,66 @@
#pragma once
#include "SinglePortPlugin.h"
/**
* A base class for mesh plugins that assume that they are sending/receiving one particular protobuf based
* payload. Using one particular app ID.
*
* If you are using protobufs to encode your packets (recommended) you can use this as a baseclass for your plugin
* and avoid a bunch of boilerplate code.
*/
template <class T> class ProtobufPlugin : private SinglePortPlugin
{
const pb_msgdesc_t *fields;
public:
/** Constructor
* name is for debugging output
*/
ProtobufPlugin(const char *_name, PortNum _ourPortNum, const pb_msgdesc_t *_fields)
: SinglePortPlugin(_name, _ourPortNum), fields(_fields)
{
}
protected:
/**
* Handle a received message, the data field in the message is already decoded and is provided
*/
virtual bool handleReceivedProtobuf(const MeshPacket &mp, const T &decoded) = 0;
/**
* Return a mesh packet which has been preinited with a particular protobuf data payload and port number.
* You can then send this packet (after customizing any of the payload fields you might need) with
* service.sendToMesh()
*/
MeshPacket *allocDataProtobuf(const T &payload)
{
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = allocDataPacket();
p->decoded.data.payload.size =
pb_encode_to_bytes(p->decoded.data.payload.bytes, sizeof(p->decoded.data.payload.bytes), fields, &payload);
// DEBUG_MSG("did encode\n");
return p;
}
private:
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual bool handleReceived(const MeshPacket &mp)
{
// FIXME - we currently update position data in the DB only if the message was a broadcast or destined to us
// it would be better to update even if the message was destined to others.
auto &p = mp.decoded.data;
DEBUG_MSG("Received %s from=0x%0x, id=%d, payloadlen=%d\n", name, mp.from, mp.id, p.payload.size);
T scratch;
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch))
handleReceivedProtobuf(mp, scratch);
return false; // Let others look at this message also if they want
}
};

View File

@@ -53,6 +53,68 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts
// 1kb was too small
#define RADIO_STACK_SIZE 4096
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
* section 4
*
* @return num msecs for the packet
*/
uint32_t RadioInterface::getPacketTime(uint32_t pl)
{
float bandwidthHz = bw * 1000.0f;
bool headDisable = false; // we currently always use the header
float tSym = (1 << sf) / bandwidthHz;
bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
float tPreamble = (preambleLength + 4.25f) * tSym;
float numPayloadSym =
8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
float tPayload = numPayloadSym * tSym;
float tPacket = tPreamble + tPayload;
uint32_t msecs = tPacket * 1000;
DEBUG_MSG("(bw=%d, sf=%d, cr=4/%d) packet symLen=%d ms, payloadSize=%u, time %d ms\n", (int)bw, sf, cr, (int)(tSym * 1000),
pl, msecs);
return msecs;
}
uint32_t RadioInterface::getPacketTime(MeshPacket *p)
{
assert(p->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now
uint32_t pl = p->encrypted.size + sizeof(PacketHeader);
return getPacketTime(pl);
}
/** The delay to use for retransmitting dropped packets */
uint32_t RadioInterface::getRetransmissionMsec(const MeshPacket *p)
{
// was 20 and 22 secs respectively, but now with shortPacketMsec as 2269, this should give the same range
return random(9 * shortPacketMsec, 10 * shortPacketMsec);
}
/** The delay to use when we want to send something but the ether is busy */
uint32_t RadioInterface::getTxDelayMsec()
{
/** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node)
* has had enough time to switch their radio back into receive mode.
*/
const uint32_t MIN_TX_WAIT_MSEC = 100;
/**
* At the high end, this value is used to spread node attempts across time so when they are replying to a packet
* they don't both check that the airwaves are clear at the same moment. As long as they are off by some amount
* one of the two will be first to start transmitting and the other will see that. I bet 500ms is more than enough
* to guarantee this.
*/
// const uint32_t MAX_TX_WAIT_MSEC = 2000; // stress test would still fail occasionally with 1000
return random(MIN_TX_WAIT_MSEC, shortPacketMsec);
}
void printPacket(const char *prefix, const MeshPacket *p)
{
DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d", prefix, p->id, p->from & 0xff, p->to & 0xff, p->want_ack,
@@ -125,6 +187,12 @@ bool RadioInterface::init()
return true;
}
int RadioInterface::notifyDeepSleepCb(void *unused)
{
sleep();
return 0;
}
/** hash a string into an integer
*
* djb2 by Dan Bernstein.
@@ -149,8 +217,47 @@ void RadioInterface::applyModemConfig()
// Set up default configuration
// No Sync Words in LORA mode
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;
}
power = channelSettings.tx_power;
shortPacketMsec = getPacketTime(sizeof(PacketHeader));
assert(myRegion); // Should have been found in init
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
@@ -165,6 +272,7 @@ void RadioInterface::applyModemConfig()
DEBUG_MSG("Radio myRegion->numChannels: %d\n", myRegion->numChannels);
DEBUG_MSG("Radio channel_num: %d\n", channel_num);
DEBUG_MSG("Radio frequency: %f\n", freq);
DEBUG_MSG("Short packet time: %u msec\n", shortPacketMsec);
}
/**

View File

@@ -48,9 +48,18 @@ class RadioInterface
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::preflightSleepCb);
CallbackObserver<RadioInterface, void *> notifyDeepSleepObserver =
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::notifyDeepSleepDb);
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::notifyDeepSleepCb);
/// Number of msecs we expect our shortest actual packet to be over the wire (used in retry timeout calcs)
uint32_t shortPacketMsec;
protected:
float bw = 125;
uint8_t sf = 9;
uint8_t cr = 7;
uint16_t preambleLength = 32; // 8 is default, but we use longer to increase the amount of sleep time when receiving
MeshPacket *sendingPacket = NULL; // The packet we are currently sending
uint32_t lastTxStart = 0L;
@@ -108,6 +117,22 @@ class RadioInterface
/// \return true if initialisation succeeded.
virtual bool reconfigure() = 0;
/** The delay to use for retransmitting dropped packets */
uint32_t getRetransmissionMsec(const MeshPacket *p);
/** The delay to use when we want to send something but the ether is busy */
uint32_t getTxDelayMsec();
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
* section 4
*
* @return num msecs for the packet
*/
uint32_t getPacketTime(MeshPacket *p);
uint32_t getPacketTime(uint32_t totalPacketLen);
protected:
int8_t power = 17; // Set by applyModemConfig()
@@ -136,11 +161,7 @@ class RadioInterface
/// Return 0 if sleep is okay
int preflightSleepCb(void *unused = NULL) { return canSleep() ? 0 : 1; }
int notifyDeepSleepDb(void *unused = NULL)
{
sleep();
return 0;
}
int notifyDeepSleepCb(void *unused = NULL);
int reloadConfig(void *unused)
{

View File

@@ -58,50 +58,6 @@ void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0()
*/
RadioLibInterface *RadioLibInterface::instance;
/**
* Convert our modemConfig enum into wf, sf, etc...
*/
void RadioLibInterface::applyModemConfig()
{
RadioInterface::applyModemConfig();
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;
}
}
/** Could we send right now (i.e. either not actively receving or transmitting)? */
bool RadioLibInterface::canSendImmediately()
@@ -130,6 +86,8 @@ 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);
uint32_t xmitMsec = getPacketTime(p);
DEBUG_MSG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad);
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
@@ -158,19 +116,6 @@ bool RadioLibInterface::canSleep()
return res;
}
/** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node)
* has had enough time to switch their radio back into receive mode.
*/
#define MIN_TX_WAIT_MSEC 100
/**
* At the high end, this value is used to spread node attempts across time so when they are replying to a packet
* they don't both check that the airwaves are clear at the same moment. As long as they are off by some amount
* one of the two will be first to start transmitting and the other will see that. I bet 500ms is more than enough
* to guarantee this.
*/
#define MAX_TX_WAIT_MSEC 2000 // stress test would still fail occasionally with 1000
/** radio helper thread callback.
We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and
@@ -226,8 +171,7 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.isEmpty()) {
uint32_t delay =
!withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values
uint32_t delay = !withDelay ? 1 : getTxDelayMsec();
// DEBUG_MSG("xmit timer %d\n", delay);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
}
@@ -236,20 +180,25 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
void RadioLibInterface::handleTransmitInterrupt()
{
// DEBUG_MSG("handling lora TX interrupt\n");
assert(sendingPacket); // Were we sending? - FIXME, this was null coming out of light sleep due to RF95 ISR!
completeSending();
// This can be null if we forced the device to enter standby mode. In that case
// ignore the transmit interrupt
if (sendingPacket)
completeSending();
}
void RadioLibInterface::completeSending()
{
if (sendingPacket) {
// We are careful to clear sending packet before calling printPacket because
// that can take a long time
auto p = sendingPacket;
sendingPacket = NULL;
if (p) {
txGood++;
printPacket("Completed sending", sendingPacket);
printPacket("Completed sending", p);
// We are done sending that packet, release it
packetPool.release(sendingPacket);
sendingPacket = NULL;
packetPool.release(p);
// DEBUG_MSG("Done with send\n");
}
}
@@ -295,7 +244,7 @@ void RadioLibInterface::handleReceiveInterrupt()
addReceiveMetadata(mp);
mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
assert(payloadLen <= sizeof(mp->encrypted.bytes));
assert(((uint32_t) payloadLen) <= sizeof(mp->encrypted.bytes));
memcpy(mp->encrypted.bytes, payload, payloadLen);
mp->encrypted.size = payloadLen;
@@ -305,7 +254,7 @@ void RadioLibInterface::handleReceiveInterrupt()
}
}
}
/** start an immediate transmit */
void RadioLibInterface::startSend(MeshPacket *txp)
{

View File

@@ -77,9 +77,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
PointerQueue<MeshPacket> txQueue = PointerQueue<MeshPacket>(MAX_TX_QUEUE);
protected:
float bw = 125;
uint8_t sf = 9;
uint8_t cr = 7;
/**
* FIXME, use a meshtastic sync word, but hashed with the Channel name. Currently picking the same default
@@ -88,7 +85,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
uint8_t syncWord = SX126X_SYNC_WORD_PRIVATE;
float currentLimit = 100; // FIXME
uint16_t preambleLength = 32; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving
LockingModule module; // The HW interface to the radio
@@ -165,13 +161,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */
virtual void configHardwareForSend() {}
/**
* Convert our modemConfig enum into wf, sf, etc...
*
* These paramaters will be pull from the channelSettings global
*/
virtual void applyModemConfig();
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
virtual bool canSendImmediately();

View File

@@ -111,7 +111,6 @@ PendingPacket::PendingPacket(MeshPacket *p)
{
packet = p;
numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send
setNextTx();
}
PendingPacket *ReliableRouter::findPendingPacket(GlobalPacketId key)
@@ -151,6 +150,7 @@ PendingPacket *ReliableRouter::startRetransmission(MeshPacket *p)
auto id = GlobalPacketId(p);
auto rec = PendingPacket(p);
setNextTx(&rec);
stopRetransmission(p->from, p->id);
pending[id] = rec;
@@ -190,10 +190,9 @@ int32_t ReliableRouter::doRetransmissions()
// Queue again
--p.numRetransmissions;
p.setNextTx();
setNextTx(&p);
}
}
else {
} else {
// Not yet time
int32_t t = p.nextTxMsec - now;

View File

@@ -46,8 +46,6 @@ struct PendingPacket {
PendingPacket() {}
PendingPacket(MeshPacket *p);
void setNextTx() { nextTxMsec = millis() + random(20 * 1000L, 22 * 1000L); }
};
class GlobalPacketIdHashFunction
@@ -130,4 +128,8 @@ class ReliableRouter : public FloodingRouter
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
*/
int32_t doRetransmissions();
void setNextTx(PendingPacket *pending) {
assert(iface);
pending->nextTxMsec = millis() + iface->getRetransmissionMsec(pending->packet); }
};

View File

@@ -14,12 +14,13 @@
class Router : protected concurrency::OSThread
{
private:
RadioInterface *iface;
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
/// forwarded to the phone.
PointerQueue<MeshPacket> fromRadioQueue;
protected:
RadioInterface *iface = NULL;
public:
/// Local services that want to see _every_ packet this node receives can observe this.
/// Observers should always return 0 and _copy_ any packets they want to keep for use later (this packet will be getting

View File

@@ -201,9 +201,21 @@ bool SX1262Interface::isActivelyReceiving()
bool SX1262Interface::sleep()
{
// put chipset into sleep mode
disableInterrupt();
lora.sleep();
// Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
DEBUG_MSG("sx1262 entering sleep mode (FIXME, don't keep config)\n");
setStandby(); // Stop any pending operations
// turn off TCXO if it was powered
// FIXME - this isn't correct
// lora.setTCXO(0);
// put chipset into sleep mode (we've already disabled interrupts by now)
bool keepConfig = true;
lora.sleep(keepConfig); // Note: we do not keep the config, full reinit will be needed
#ifdef SX1262_POWER_EN
digitalWrite(SX1262_POWER_EN, LOW);
#endif
return true;
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include "MeshPlugin.h"
#include "Router.h"
/**
* Most plugins are only interested in sending/receving one particular portnum. This baseclass simplifies that common
* case.
*/
class SinglePortPlugin : public MeshPlugin
{
protected:
PortNum ourPortNum;
public:
/** Constructor
* name is for debugging output
*/
SinglePortPlugin(const char *_name, PortNum _ourPortNum) : MeshPlugin(_name), ourPortNum(_ourPortNum) {}
protected:
/**
* @return true if you want to receive the specified portnum
*/
virtual bool wantPortnum(PortNum p) { return p == ourPortNum; }
/**
* Return a mesh packet which has been preinited as a data packet with a particular port number.
* You can then send this packet (after customizing any of the payload fields you might need) with
* service.sendToMesh()
*/
MeshPacket *allocDataPacket()
{
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = router->allocForSending();
p->decoded.which_payload = SubPacket_data_tag;
p->decoded.data.portnum = ourPortNum;
return p;
}
};

View File

@@ -51,10 +51,6 @@ PB_BIND(FromRadio, FromRadio, 2)
PB_BIND(ToRadio, ToRadio, 2)
PB_BIND(ManufacturingData, ManufacturingData, AUTO)

View File

@@ -4,6 +4,7 @@
#ifndef PB_MESH_PB_H_INCLUDED
#define PB_MESH_PB_H_INCLUDED
#include <pb.h>
#include "portnums.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
@@ -22,7 +23,8 @@ typedef enum _RouteError {
} RouteError;
typedef enum _Constants {
Constants_Unused = 0
Constants_Unused = 0,
Constants_DATA_PAYLOAD_LEN = 240
} Constants;
typedef enum _RegionCode {
@@ -50,12 +52,6 @@ typedef enum _LocationSharing {
LocationSharing_LocDisabled = 2
} LocationSharing;
typedef enum _Data_Type {
Data_Type_OPAQUE = 0,
Data_Type_CLEAR_TEXT = 1,
Data_Type_CLEAR_READACK = 2
} Data_Type;
typedef enum _ChannelSettings_ModemConfig {
ChannelSettings_ModemConfig_Bw125Cr45Sf128 = 0,
ChannelSettings_ModemConfig_Bw500Cr45Sf128 = 1,
@@ -78,7 +74,7 @@ typedef struct _ChannelSettings {
typedef PB_BYTES_ARRAY_T(240) Data_payload_t;
typedef struct _Data {
Data_Type typ;
PortNum portnum;
Data_payload_t payload;
} Data;
@@ -86,13 +82,6 @@ typedef struct _DebugString {
char message[256];
} DebugString;
typedef struct _ManufacturingData {
uint32_t fradioFreq;
pb_callback_t hw_model;
pb_callback_t hw_version;
int32_t selftest_result;
} ManufacturingData;
typedef struct _MyNodeInfo {
uint32_t my_node_num;
bool has_gps;
@@ -140,7 +129,9 @@ typedef struct _RadioConfig_UserPreferences {
uint32_t gps_attempt_time;
bool is_router;
bool is_low_power;
bool fixed_position;
bool factory_reset;
bool debug_log_enabled;
pb_size_t ignore_incoming_count;
uint32_t ignore_incoming[3];
} RadioConfig_UserPreferences;
@@ -260,8 +251,8 @@ typedef struct _ToRadio {
#define _RouteError_ARRAYSIZE ((RouteError)(RouteError_TIMEOUT+1))
#define _Constants_MIN Constants_Unused
#define _Constants_MAX Constants_Unused
#define _Constants_ARRAYSIZE ((Constants)(Constants_Unused+1))
#define _Constants_MAX Constants_DATA_PAYLOAD_LEN
#define _Constants_ARRAYSIZE ((Constants)(Constants_DATA_PAYLOAD_LEN+1))
#define _RegionCode_MIN RegionCode_Unset
#define _RegionCode_MAX RegionCode_TW
@@ -275,10 +266,6 @@ typedef struct _ToRadio {
#define _LocationSharing_MAX LocationSharing_LocDisabled
#define _LocationSharing_ARRAYSIZE ((LocationSharing)(LocationSharing_LocDisabled+1))
#define _Data_Type_MIN Data_Type_OPAQUE
#define _Data_Type_MAX Data_Type_CLEAR_READACK
#define _Data_Type_ARRAYSIZE ((Data_Type)(Data_Type_CLEAR_READACK+1))
#define _ChannelSettings_ModemConfig_MIN ChannelSettings_ModemConfig_Bw125Cr45Sf128
#define _ChannelSettings_ModemConfig_MAX ChannelSettings_ModemConfig_Bw125Cr48Sf4096
#define _ChannelSettings_ModemConfig_ARRAYSIZE ((ChannelSettings_ModemConfig)(ChannelSettings_ModemConfig_Bw125Cr48Sf4096+1))
@@ -286,37 +273,35 @@ typedef struct _ToRadio {
/* Initializer values for message structs */
#define Position_init_default {0, 0, 0, 0, 0}
#define Data_init_default {_Data_Type_MIN, {0, {0}}}
#define Data_init_default {_PortNum_MIN, {0, {0}}}
#define User_init_default {"", "", "", {0}}
#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}}, "", 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, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 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, 0}
#define DebugString_init_default {""}
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
#define ToRadio_init_default {0, {MeshPacket_init_default}}
#define ManufacturingData_init_default {0, {{NULL}, NULL}, {{NULL}, NULL}, 0}
#define Position_init_zero {0, 0, 0, 0, 0}
#define Data_init_zero {_Data_Type_MIN, {0, {0}}}
#define Data_init_zero {_PortNum_MIN, {0, {0}}}
#define User_init_zero {"", "", "", {0}}
#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}}, "", 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, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 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, 0}
#define DebugString_init_zero {""}
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
#define ManufacturingData_init_zero {0, {{NULL}, NULL}, {{NULL}, NULL}, 0}
/* Field tags (for use in manual encoding/decoding) */
#define ChannelSettings_tx_power_tag 1
@@ -327,13 +312,9 @@ typedef struct _ToRadio {
#define ChannelSettings_channel_num_tag 9
#define ChannelSettings_psk_tag 4
#define ChannelSettings_name_tag 5
#define Data_typ_tag 1
#define Data_portnum_tag 1
#define Data_payload_tag 2
#define DebugString_message_tag 1
#define ManufacturingData_fradioFreq_tag 1
#define ManufacturingData_hw_model_tag 2
#define ManufacturingData_hw_version_tag 3
#define ManufacturingData_selftest_result_tag 4
#define MyNodeInfo_my_node_num_tag 1
#define MyNodeInfo_has_gps_tag 2
#define MyNodeInfo_num_channels_tag 3
@@ -370,7 +351,9 @@ typedef struct _ToRadio {
#define RadioConfig_UserPreferences_region_tag 15
#define RadioConfig_UserPreferences_is_router_tag 37
#define RadioConfig_UserPreferences_is_low_power_tag 38
#define RadioConfig_UserPreferences_fixed_position_tag 39
#define RadioConfig_UserPreferences_factory_reset_tag 100
#define RadioConfig_UserPreferences_debug_log_enabled_tag 101
#define RadioConfig_UserPreferences_location_share_tag 32
#define RadioConfig_UserPreferences_gps_operation_tag 33
#define RadioConfig_UserPreferences_gps_update_interval_tag 34
@@ -442,7 +425,7 @@ X(a, STATIC, SINGULAR, FIXED32, time, 9)
#define Position_DEFAULT NULL
#define Data_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, typ, 1) \
X(a, STATIC, SINGULAR, UENUM, portnum, 1) \
X(a, STATIC, SINGULAR, BYTES, payload, 2)
#define Data_CALLBACK NULL
#define Data_DEFAULT NULL
@@ -537,7 +520,9 @@ X(a, STATIC, SINGULAR, UINT32, gps_update_interval, 34) \
X(a, STATIC, SINGULAR, UINT32, gps_attempt_time, 36) \
X(a, STATIC, SINGULAR, BOOL, is_router, 37) \
X(a, STATIC, SINGULAR, BOOL, is_low_power, 38) \
X(a, STATIC, SINGULAR, BOOL, fixed_position, 39) \
X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \
X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 101) \
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103)
#define RadioConfig_UserPreferences_CALLBACK NULL
#define RadioConfig_UserPreferences_DEFAULT NULL
@@ -623,14 +608,6 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,set_owner,variant.set_owner), 102)
#define ToRadio_variant_set_radio_MSGTYPE RadioConfig
#define ToRadio_variant_set_owner_MSGTYPE User
#define ManufacturingData_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, fradioFreq, 1) \
X(a, CALLBACK, SINGULAR, STRING, hw_model, 2) \
X(a, CALLBACK, SINGULAR, STRING, hw_version, 3) \
X(a, STATIC, SINGULAR, SINT32, selftest_result, 4)
#define ManufacturingData_CALLBACK pb_default_field_callback
#define ManufacturingData_DEFAULT NULL
extern const pb_msgdesc_t Position_msg;
extern const pb_msgdesc_t Data_msg;
extern const pb_msgdesc_t User_msg;
@@ -646,7 +623,6 @@ extern const pb_msgdesc_t DeviceState_msg;
extern const pb_msgdesc_t DebugString_msg;
extern const pb_msgdesc_t FromRadio_msg;
extern const pb_msgdesc_t ToRadio_msg;
extern const pb_msgdesc_t ManufacturingData_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define Position_fields &Position_msg
@@ -664,25 +640,23 @@ extern const pb_msgdesc_t ManufacturingData_msg;
#define DebugString_fields &DebugString_msg
#define FromRadio_fields &FromRadio_msg
#define ToRadio_fields &ToRadio_msg
#define ManufacturingData_fields &ManufacturingData_msg
/* Maximum encoded size of messages (where known) */
#define Position_size 39
#define Data_size 245
#define Data_size 246
#define User_size 72
#define RouteDiscovery_size 88
#define SubPacket_size 274
#define MeshPacket_size 313
#define SubPacket_size 275
#define MeshPacket_size 314
#define ChannelSettings_size 84
#define RadioConfig_size 308
#define RadioConfig_UserPreferences_size 219
#define RadioConfig_size 314
#define RadioConfig_UserPreferences_size 225
#define NodeInfo_size 132
#define MyNodeInfo_size 110
#define DeviceState_size 5460
#define DeviceState_size 5468
#define DebugString_size 258
#define FromRadio_size 322
#define ToRadio_size 316
/* ManufacturingData_size depends on runtime parameters */
#define FromRadio_size 323
#define ToRadio_size 318
#ifdef __cplusplus
} /* extern "C" */

10
src/mesh/portnums.pb.c Normal file
View File

@@ -0,0 +1,10 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.1 */
#include "portnums.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif

37
src/mesh/portnums.pb.h Normal file
View File

@@ -0,0 +1,37 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.1 */
#ifndef PB_PORTNUMS_PB_H_INCLUDED
#define PB_PORTNUMS_PB_H_INCLUDED
#include <pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Enum definitions */
typedef enum _PortNum {
PortNum_UNKNOWN_APP = 0,
PortNum_TEXT_MESSAGE_APP = 1,
PortNum_REMOTE_HARDWARE_APP = 2,
PortNum_POSITION_APP = 3,
PortNum_NODEINFO_APP = 4,
PortNum_PRIVATE_APP = 256,
PortNum_IP_TUNNEL_APP = 1024
} PortNum;
/* Helper constants for enums */
#define _PortNum_MIN PortNum_UNKNOWN_APP
#define _PortNum_MAX PortNum_IP_TUNNEL_APP
#define _PortNum_ARRAYSIZE ((PortNum)(PortNum_IP_TUNNEL_APP+1))
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -0,0 +1,13 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.1 */
#include "remote_hardware.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(HardwareMessage, HardwareMessage, AUTO)

View File

@@ -0,0 +1,69 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.1 */
#ifndef PB_REMOTE_HARDWARE_PB_H_INCLUDED
#define PB_REMOTE_HARDWARE_PB_H_INCLUDED
#include <pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Enum definitions */
typedef enum _HardwareMessage_Type {
HardwareMessage_Type_UNSET = 0,
HardwareMessage_Type_WRITE_GPIOS = 1,
HardwareMessage_Type_WATCH_GPIOS = 2,
HardwareMessage_Type_GPIOS_CHANGED = 3,
HardwareMessage_Type_READ_GPIOS = 4,
HardwareMessage_Type_READ_GPIOS_REPLY = 5
} HardwareMessage_Type;
/* Struct definitions */
typedef struct _HardwareMessage {
HardwareMessage_Type typ;
uint64_t gpio_mask;
uint64_t gpio_value;
} HardwareMessage;
/* Helper constants for enums */
#define _HardwareMessage_Type_MIN HardwareMessage_Type_UNSET
#define _HardwareMessage_Type_MAX HardwareMessage_Type_READ_GPIOS_REPLY
#define _HardwareMessage_Type_ARRAYSIZE ((HardwareMessage_Type)(HardwareMessage_Type_READ_GPIOS_REPLY+1))
/* Initializer values for message structs */
#define HardwareMessage_init_default {_HardwareMessage_Type_MIN, 0, 0}
#define HardwareMessage_init_zero {_HardwareMessage_Type_MIN, 0, 0}
/* Field tags (for use in manual encoding/decoding) */
#define HardwareMessage_typ_tag 1
#define HardwareMessage_gpio_mask_tag 2
#define HardwareMessage_gpio_value_tag 3
/* Struct field encoding specification for nanopb */
#define HardwareMessage_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, typ, 1) \
X(a, STATIC, SINGULAR, UINT64, gpio_mask, 2) \
X(a, STATIC, SINGULAR, UINT64, gpio_value, 3)
#define HardwareMessage_CALLBACK NULL
#define HardwareMessage_DEFAULT NULL
extern const pb_msgdesc_t HardwareMessage_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define HardwareMessage_fields &HardwareMessage_msg
/* Maximum encoded size of messages (where known) */
#define HardwareMessage_size 24
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -5,6 +5,10 @@
#include "meshhttpStatic.h"
#include "meshwifi/meshwifi.h"
#include "sleep.h"
#include <HTTPBodyParser.hpp>
#include <HTTPMultipartBodyParser.hpp>
#include <HTTPURLEncodedBodyParser.hpp>
#include <SPIFFS.h>
#include <WebServer.h>
#include <WiFi.h>
@@ -49,11 +53,11 @@ void handleStyleCSS(HTTPRequest *req, HTTPResponse *res);
void handleHotspot(HTTPRequest *req, HTTPResponse *res);
void handleFavicon(HTTPRequest *req, HTTPResponse *res);
void handleRoot(HTTPRequest *req, HTTPResponse *res);
void handleBasicHTML(HTTPRequest *req, HTTPResponse *res);
void handleBasicJS(HTTPRequest *req, HTTPResponse *res);
void handleScriptsScriptJS(HTTPRequest *req, HTTPResponse *res);
void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res);
void handleStaticPost(HTTPRequest *req, HTTPResponse *res);
void handleStatic(HTTPRequest *req, HTTPResponse *res);
void handle404(HTTPRequest *req, HTTPResponse *res);
void handleFormUpload(HTTPRequest *req, HTTPResponse *res);
void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
@@ -64,6 +68,13 @@ bool isCertReady = 0;
uint32_t timeSpeedUp = 0;
// We need to specify some content-type mapping, so the resources get delivered with the
// right content type and are displayed correctly in the browser
char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"}, {".js", "text/javascript"},
{".png", "image/png"}, {".jpg", "image/jpg"}, {".gz", "application/gzip"},
{".gif", "image/gif"}, {".json", "application/json"}, {".css", "text/css"},
{"", ""}};
void handleWebResponse()
{
if (isWifiAvailable() == 0) {
@@ -79,7 +90,7 @@ void handleWebResponse()
insecureServer->loop();
}
/*
/*
Slow down the CPU if we have not received a request within the last few
seconds.
*/
@@ -219,11 +230,11 @@ void initWebServer()
ResourceNode *nodeHotspot = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot);
ResourceNode *nodeFavicon = new ResourceNode("/favicon.ico", "GET", &handleFavicon);
ResourceNode *nodeRoot = new ResourceNode("/", "GET", &handleRoot);
ResourceNode *nodeScriptScriptsJS = new ResourceNode("/scripts/script.js", "GET", &handleScriptsScriptJS);
ResourceNode *nodeBasicHTML = new ResourceNode("/basic.html", "GET", &handleBasicHTML);
ResourceNode *nodeBasicJS = new ResourceNode("/basic.js", "GET", &handleBasicJS);
ResourceNode *nodeStaticBrowse = new ResourceNode("/static", "GET", &handleStaticBrowse);
ResourceNode *nodeStaticPOST = new ResourceNode("/static", "POST", &handleStaticPost);
ResourceNode *nodeStatic = new ResourceNode("/static/*", "GET", &handleStatic);
ResourceNode *node404 = new ResourceNode("", "GET", &handle404);
ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload);
// Secure nodes
secureServer->registerNode(nodeAPIv1ToRadioOptions);
@@ -232,11 +243,11 @@ void initWebServer()
secureServer->registerNode(nodeHotspot);
secureServer->registerNode(nodeFavicon);
secureServer->registerNode(nodeRoot);
secureServer->registerNode(nodeScriptScriptsJS);
secureServer->registerNode(nodeBasicHTML);
secureServer->registerNode(nodeBasicJS);
secureServer->registerNode(nodeStaticBrowse);
secureServer->registerNode(nodeStaticPOST);
secureServer->registerNode(nodeStatic);
secureServer->setDefaultNode(node404);
secureServer->setDefaultNode(nodeFormUpload);
secureServer->addMiddleware(&middlewareSpeedUp240);
@@ -247,11 +258,11 @@ void initWebServer()
insecureServer->registerNode(nodeHotspot);
insecureServer->registerNode(nodeFavicon);
insecureServer->registerNode(nodeRoot);
insecureServer->registerNode(nodeScriptScriptsJS);
insecureServer->registerNode(nodeBasicHTML);
insecureServer->registerNode(nodeBasicJS);
insecureServer->registerNode(nodeStaticBrowse);
insecureServer->registerNode(nodeStaticPOST);
insecureServer->registerNode(nodeStatic);
insecureServer->setDefaultNode(node404);
insecureServer->setDefaultNode(nodeFormUpload);
insecureServer->addMiddleware(&middlewareSpeedUp160);
@@ -289,40 +300,401 @@ void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function<voi
timeSpeedUp = millis();
}
void handleStaticPost(HTTPRequest *req, HTTPResponse *res)
{
// Assume POST request. Contains submitted data.
res->println("<html><head><title>File Edited</title><meta http-equiv=\"refresh\" content=\"3;url=/static\" "
"/><head><body><h1>File Edited</h1>");
// The form is submitted with the x-www-form-urlencoded content type, so we need the
// HTTPURLEncodedBodyParser to read the fields.
// Note that the content of the file's content comes from a <textarea>, so we
// can use the URL encoding here, since no file upload from an <input type="file"
// is involved.
HTTPURLEncodedBodyParser parser(req);
// The bodyparser will consume the request body. That means you can iterate over the
// fields only ones. For that reason, we need to create variables for all fields that
// we expect. So when parsing is done, you can process the field values from your
// temporary variables.
std::string filename;
bool savedFile = false;
// Iterate over the fields from the request body by calling nextField(). This function
// will update the field name and value of the body parsers. If the last field has been
// reached, it will return false and the while loop stops.
while (parser.nextField()) {
// Get the field name, so that we can decide what the value is for
std::string name = parser.getFieldName();
if (name == "filename") {
// Read the filename from the field's value, add the /public prefix and store it in
// the filename variable.
char buf[512];
size_t readLength = parser.read((byte *)buf, 512);
// filename = std::string("/public/") + std::string(buf, readLength);
filename = std::string(buf, readLength);
} else if (name == "content") {
// Browsers must return the fields in the order that they are placed in
// the HTML form, so if the broweser behaves correctly, this condition will
// never be true. We include it for safety reasons.
if (filename == "") {
res->println("<p>Error: form contained content before filename.</p>");
break;
}
// With parser.read() and parser.endOfField(), we can stream the field content
// into a buffer. That allows handling arbitrarily-sized field contents. Here,
// we use it and write the file contents directly to the SPIFFS:
size_t fieldLength = 0;
File file = SPIFFS.open(filename.c_str(), "w");
savedFile = true;
while (!parser.endOfField()) {
byte buf[512];
size_t readLength = parser.read(buf, 512);
file.write(buf, readLength);
fieldLength += readLength;
}
file.close();
res->printf("<p>Saved %d bytes to %s</p>", int(fieldLength), filename.c_str());
} else {
res->printf("<p>Unexpected field %s</p>", name.c_str());
}
}
if (!savedFile) {
res->println("<p>No file to save...</p>");
}
res->println("</body></html>");
}
void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res)
{
// Get access to the parameters
ResourceParameters *params = req->getParams();
std::string paramValDelete;
std::string paramValEdit;
// Set a default content type
res->setHeader("Content-Type", "text/html");
if (params->getQueryParameter("delete", paramValDelete)) {
std::string pathDelete = "/" + paramValDelete;
if (SPIFFS.remove(pathDelete.c_str())) {
Serial.println(pathDelete.c_str());
res->println("<html><head><meta http-equiv=\"refresh\" content=\"3;url=/static\" /><title>File "
"deleted!</title></head><body><h1>File deleted!</h1>");
res->println("<meta http-equiv=\"refresh\" content=\"2;url=/static\" />\n");
res->println("</body></html>");
return;
} else {
Serial.println(pathDelete.c_str());
res->println("<html><head><meta http-equiv=\"refresh\" content=\"3;url=/static\" /><title>Error deleteing "
"file!</title></head><body><h1>Error deleteing file!</h1>");
res->println("Error deleteing file!<br>");
return;
}
}
if (params->getQueryParameter("edit", paramValEdit)) {
std::string pathEdit = "/" + paramValEdit;
res->println("<html><head><title>Edit "
"file</title></head><body><h1>Edit file - ");
res->println(pathEdit.c_str());
res->println("</h1>");
res->println("<form method=post action=/static enctype=application/x-www-form-urlencoded>");
res->printf("<input name=\"filename\" type=\"hidden\" value=\"%s\">", pathEdit.c_str());
res->print("<textarea id=id name=content rows=20 cols=80>");
// Try to open the file from SPIFFS
File file = SPIFFS.open(pathEdit.c_str());
if (file.available()) {
// Read the file from SPIFFS and write it to the HTTP response body
size_t length = 0;
do {
char buffer[256];
length = file.read((uint8_t *)buffer, 256);
std::string bufferString(buffer, length);
// Escape gt and lt
replaceAll(bufferString, "<", "&lt;");
replaceAll(bufferString, ">", "&gt;");
res->write((uint8_t *)bufferString.c_str(), bufferString.size());
} while (length > 0);
} else {
res->println("Error: File not found");
}
res->println("</textarea><br>");
res->println("<input type=submit value=Submit>");
res->println("</form>");
res->println("</body></html>");
return;
}
res->println("<h2>Upload new file</h2>");
res->println("<p><b>*** This interface is experimental ***</b></p>");
res->println("<p>This form allows you to upload files. Keep your filenames very short and files small. Big filenames and big "
"files are a known problem.</p>");
res->println("<form method=\"POST\" action=\"/upload\" enctype=\"multipart/form-data\">");
res->println("file: <input type=\"file\" name=\"file\"><br>");
res->println("<input type=\"submit\" value=\"Upload\">");
res->println("</form>");
res->println("<h2>All Files</h2>");
File root = SPIFFS.open("/");
if (root.isDirectory()) {
res->println("<script type=\"text/javascript\">function confirm_delete() {return confirm('Are you sure?');}</script>");
res->println("<table>");
res->println("<tr>");
res->println("<td>File");
res->println("</td>");
res->println("<td>Size");
res->println("</td>");
res->println("<td colspan=2>Actions");
res->println("</td>");
res->println("</tr>");
File file = root.openNextFile();
while (file) {
String filePath = String(file.name());
if (filePath.indexOf("/static") == 0) {
res->println("<tr>");
res->println("<td>");
if (String(file.name()).substring(1).endsWith(".gz")) {
String modifiedFile = String(file.name()).substring(1);
modifiedFile.remove((modifiedFile.length() - 3), 3);
res->print("<a href=\"" + modifiedFile + "\">" + String(file.name()).substring(1) + "</a>");
} else {
res->print("<a href=\"" + String(file.name()).substring(1) + "\">" + String(file.name()).substring(1) +
"</a>");
}
res->println("</td>");
res->println("<td>");
res->print(String(file.size()));
res->println("</td>");
res->println("<td>");
res->print("<a href=\"/static?delete=" + String(file.name()).substring(1) +
"\" onclick=\"return confirm_delete()\">Delete</a> ");
res->println("</td>");
res->println("<td>");
if (!String(file.name()).substring(1).endsWith(".gz")) {
res->print("<a href=\"/static?edit=" + String(file.name()).substring(1) + "\">Edit</a>");
}
res->println("</td>");
res->println("</tr>");
}
file = root.openNextFile();
}
res->println("</table>");
res->print("<br>");
// res->print("Total : " + String(SPIFFS.totalBytes()) + " Bytes<br>");
res->print("Used : " + String(SPIFFS.usedBytes()) + " Bytes<br>");
res->print("Free : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes()) + " Bytes<br>");
}
}
void handleStatic(HTTPRequest *req, HTTPResponse *res)
{
// Get access to the parameters
ResourceParameters *params = req->getParams();
// Set a default content type
res->setHeader("Content-Type", "text/plain");
std::string parameter1;
// Print the first parameter value
if (params->getPathParameter(0, parameter1)) {
if (parameter1 == "meshtastic.js") {
res->setHeader("Content-Encoding", "gzip");
res->setHeader("Content-Type", "application/json");
res->write(STATIC_MESHTASTIC_JS_DATA, STATIC_MESHTASTIC_JS_LENGTH);
return;
} else if (parameter1 == "style.css") {
res->setHeader("Content-Encoding", "gzip");
res->setHeader("Content-Type", "text/css");
res->write(STATIC_STYLE_CSS_DATA, STATIC_STYLE_CSS_LENGTH);
return;
} else {
res->print("Parameter 1: ");
res->printStd(parameter1);
std::string filename = "/static/" + parameter1;
std::string filenameGzip = "/static/" + parameter1 + ".gz";
if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) {
// Send "404 Not Found" as response, as the file doesn't seem to exist
res->setStatusCode(404);
res->setStatusText("Not found");
res->println("404 Not Found");
res->printf("<p>File not found: %s</p>\n", filename.c_str());
return;
}
// Try to open the file from SPIFFS
File file;
if (SPIFFS.exists(filename.c_str())) {
file = SPIFFS.open(filename.c_str());
if (!file.available()) {
DEBUG_MSG("File not available - %s\n", filename.c_str());
}
} else if (SPIFFS.exists(filenameGzip.c_str())) {
file = SPIFFS.open(filenameGzip.c_str());
res->setHeader("Content-Encoding", "gzip");
if (!file.available()) {
DEBUG_MSG("File not available\n");
}
}
res->setHeader("Content-Length", httpsserver::intToString(file.size()));
bool has_set_content_type = false;
// Content-Type is guessed using the definition of the contentTypes-table defined above
int cTypeIdx = 0;
do {
if (filename.rfind(contentTypes[cTypeIdx][0]) != std::string::npos) {
res->setHeader("Content-Type", contentTypes[cTypeIdx][1]);
has_set_content_type = true;
break;
}
cTypeIdx += 1;
} while (strlen(contentTypes[cTypeIdx][0]) > 0);
if(!has_set_content_type) {
// Set a default content type
res->setHeader("Content-Type", "application/octet-stream");
}
// Read the file from SPIFFS and write it to the HTTP response body
size_t length = 0;
do {
char buffer[256];
length = file.read((uint8_t *)buffer, 256);
std::string bufferString(buffer, length);
res->write((uint8_t *)bufferString.c_str(), bufferString.size());
} while (length > 0);
file.close();
return;
} else {
res->println("ERROR: This should not have happened...");
}
}
void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
{
// First, we need to check the encoding of the form that we have received.
// The browser will set the Content-Type request header, so we can use it for that purpose.
// Then we select the body parser based on the encoding.
// Actually we do this only for documentary purposes, we know the form is going
// to be multipart/form-data.
HTTPBodyParser *parser;
std::string contentType = req->getHeader("Content-Type");
// The content type may have additional properties after a semicolon, for exampel:
// Content-Type: text/html;charset=utf-8
// Content-Type: multipart/form-data;boundary=------s0m3w31rdch4r4c73rs
// As we're interested only in the actual mime _type_, we strip everything after the
// first semicolon, if one exists:
size_t semicolonPos = contentType.find(";");
if (semicolonPos != std::string::npos) {
contentType = contentType.substr(0, semicolonPos);
}
// Now, we can decide based on the content type:
if (contentType == "multipart/form-data") {
parser = new HTTPMultipartBodyParser(req);
} else {
Serial.printf("Unknown POST Content-Type: %s\n", contentType.c_str());
return;
}
res->println("<html><head><meta http-equiv=\"refresh\" content=\"3;url=/static\" /><title>File "
"Upload</title></head><body><h1>File Upload</h1>");
// We iterate over the fields. Any field with a filename is uploaded.
// Note that the BodyParser consumes the request body, meaning that you can iterate over the request's
// fields only a single time. The reason for this is that it allows you to handle large requests
// which would not fit into memory.
bool didwrite = false;
// parser->nextField() will move the parser to the next field in the request body (field meaning a
// form field, if you take the HTML perspective). After the last field has been processed, nextField()
// returns false and the while loop ends.
while (parser->nextField()) {
// For Multipart data, each field has three properties:
// The name ("name" value of the <input> tag)
// The filename (If it was a <input type="file">, this is the filename on the machine of the
// user uploading it)
// The mime type (It is determined by the client. So do not trust this value and blindly start
// parsing files only if the type matches)
std::string name = parser->getFieldName();
std::string filename = parser->getFieldFilename();
std::string mimeType = parser->getFieldMimeType();
// We log all three values, so that you can observe the upload on the serial monitor:
DEBUG_MSG("handleFormUpload: field name='%s', filename='%s', mimetype='%s'\n", name.c_str(), filename.c_str(),
mimeType.c_str());
// Double check that it is what we expect
if (name != "file") {
DEBUG_MSG("Skipping unexpected field");
res->println("<p>No file found.</p>");
return;
}
// Double check that it is what we expect
if (filename == "") {
DEBUG_MSG("Skipping unexpected field");
res->println("<p>No file found.</p>");
return;
}
// SPIFFS limits the total lenth of a path + file to 31 characters.
if (filename.length() + 8 > 31) {
DEBUG_MSG("Uploaded filename too long!");
res->println("<p>Uploaded filename too long! Limit of 23 characters.</p>");
delete parser;
return;
}
// You should check file name validity and all that, but we skip that to make the core
// concepts of the body parser functionality easier to understand.
std::string pathname = "/static/" + filename;
// Create a new file on spiffs to stream the data into
File file = SPIFFS.open(pathname.c_str(), "w");
size_t fileLength = 0;
didwrite = true;
// With endOfField you can check whether the end of field has been reached or if there's
// still data pending. With multipart bodies, you cannot know the field size in advance.
while (!parser->endOfField()) {
byte buf[512];
size_t readLength = parser->read(buf, 512);
file.write(buf, readLength);
fileLength += readLength;
// Abort the transfer if there is less than 50k space left on the filesystem.
if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < 51200) {
file.close();
res->println("<p>Write aborted! File is won't fit!</p>");
delete parser;
return;
}
}
file.close();
res->printf("<p>Saved %d bytes to %s</p>", (int)fileLength, pathname.c_str());
}
if (!didwrite) {
res->println("<p>Did not write any file</p>");
}
res->println("</body></html>");
delete parser;
}
void handle404(HTTPRequest *req, HTTPResponse *res)
{
@@ -397,15 +769,22 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
uint32_t len = 1;
if (params->getQueryParameter("all", valueAll)) {
// If all is ture, return all the buffers we have available
// to us at this point in time.
if (valueAll == "true") {
while (len) {
len = webAPI.getFromRadio(txBuf);
res->write(txBuf, len);
}
// Otherwise, just return one protobuf
} else {
len = webAPI.getFromRadio(txBuf);
res->write(txBuf, len);
}
// the param "all" was not spcified. Return just one protobuf
} else {
len = webAPI.getFromRadio(txBuf);
res->write(txBuf, len);
@@ -458,248 +837,56 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
*/
void handleRoot(HTTPRequest *req, HTTPResponse *res)
{
String out = "";
out +=
"<!DOCTYPE html>\n"
"<html lang=\"en\" >\n"
"<!-- Updated 20200923 - Change JSON input -->\n"
"<!-- Updated 20200924 - Replace FontAwesome with SVG -->\n"
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <title>Meshtastic - Chat</title>\n"
" <link rel=\"stylesheet\" href=\"static/style.css\">\n"
"\n"
"</head>\n"
"<body>\n"
"<center><h1>This area is under development. Please don't file bugs.</h1></center><!-- Add SVG for Symbols -->\n"
"<svg aria-hidden=\"true\" style=\"position: absolute; width: 0; height: 0; overflow: hidden;\" version=\"1.1\" "
"xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"
"<defs>\n"
"<symbol id=\"icon-map-marker\" viewBox=\"0 0 16 28\">\n"
"<path d=\"M12 10c0-2.203-1.797-4-4-4s-4 1.797-4 4 1.797 4 4 4 4-1.797 4-4zM16 10c0 0.953-0.109 1.937-0.516 2.797l-5.688 "
"12.094c-0.328 0.688-1.047 1.109-1.797 1.109s-1.469-0.422-1.781-1.109l-5.703-12.094c-0.406-0.859-0.516-1.844-0.516-2.797 "
"0-4.422 3.578-8 8-8s8 3.578 8 8z\"></path>\n"
"</symbol>\n"
"<symbol id=\"icon-circle\" viewBox=\"0 0 24 28\">\n"
"<path d=\"M24 14c0 6.625-5.375 12-12 12s-12-5.375-12-12 5.375-12 12-12 12 5.375 12 12z\"></path>\n"
"</symbol>\n"
"</defs>\n"
"</svg>\n"
"<div class=\"grid\">\n"
"\t<div class=\"top\">\n"
"\t\t<div class=\"top-text\">Meshtastic - Chat</div>\n"
"\t</div>\n"
"\n"
"\t<div class=\"side clearfix\">\n"
" <div class=\"channel-list\" id=\"channel-list\">\n"
"\t <div class=\"side-header\">\n"
"\t\t<div class=\"side-text\">Users</div>\n"
"\t </div>\n"
" <ul class=\"list\" id='userlist-id'>\n"
" </ul>\n"
" </div>\n"
" </div>\n"
" <div class=\"content\">\n"
" <div class=\"content-header clearfix\">\n"
"<!-- <div class=\"content-about\"> -->\n"
" <div class=\"content-from\">\n"
"\t\t <span class=\"content-from-highlight\" id=\"content-from-id\">All Users</span>\n"
"\t\t </div>\n"
"<!-- </div> -->\n"
" </div> <!-- end content-header -->\n"
" \n"
" <div class=\"content-history\" id='chat-div-id'>\n"
" <ul id='chat-history-id'>\n"
"\t\t</ul>\n"
" \n"
" </div> <!-- end content-history -->\n"
" \n"
" <div class=\"content-message clearfix\">\n"
" <textarea name=\"message-to-send\" id=\"message-to-send\" placeholder =\"Type your message\" "
"rows=\"3\"></textarea>\n"
" \n"
" \n"
" <button>Send</button>\n"
"\n"
" </div> <!-- end content-message -->\n"
" \n"
" </div> <!-- end content -->\n"
" \n"
" </div> <!-- end container -->\n"
"\n"
"<script src=\"/scripts/script.js\"></script>\n"
"\n"
"</body>\n"
"</html>\n"
"";
// Status code is 200 OK by default.
// We want to deliver a simple HTML page, so we send a corresponding content type:
res->setHeader("Content-Type", "text/html");
// The response implements the Print interface, so you can use it just like
// you would write to Serial etc.
res->print(out);
}
randomSeed(millis());
void handleScriptsScriptJS(HTTPRequest *req, HTTPResponse *res)
{
String out = "";
out += "String.prototype.toHHMMSS = function () {\n"
" var sec_num = parseInt(this, 10); // don't forget the second param\n"
" var hours = Math.floor(sec_num / 3600);\n"
" var minutes = Math.floor((sec_num - (hours * 3600)) / 60);\n"
" var seconds = sec_num - (hours * 3600) - (minutes * 60);\n"
"\n"
" if (hours < 10) {hours = \"0\"+hours;}\n"
" if (minutes < 10) {minutes = \"0\"+minutes;}\n"
" if (seconds < 10) {seconds = \"0\"+seconds;}\n"
"// return hours+':'+minutes+':'+seconds;\n"
"\treturn hours+'h'+minutes+'m';\n"
"}\n"
"String.prototype.padLeft = function (length, character) { \n"
" return new Array(length - this.length + 1).join(character || ' ') + this; \n"
"};\n"
"\n"
"Date.prototype.toFormattedString = function () {\n"
" return [String(this.getFullYear()).substr(2, 2),\n"
"\t\t\tString(this.getMonth()+1).padLeft(2, '0'),\n"
" String(this.getDate()).padLeft(2, '0')].join(\"/\") + \" \" +\n"
" [String(this.getHours()).padLeft(2, '0'),\n"
" String(this.getMinutes()).padLeft(2, '0')].join(\":\");\n"
"};\n"
"\n"
"function getData(file) {\n"
"\tfetch(file)\n"
"\t.then(function (response) {\n"
"\t\treturn response.json();\n"
"\t})\n"
"\t.then(function (datafile) {\n"
"\t\tupdateData(datafile);\n"
"\t})\n"
"\t.catch(function (err) {\n"
"\t\tconsole.log('error: ' + err);\n"
"\t});\n"
"}\n"
"\t\n"
"function updateData(datafile) {\n"
"// Update System Details\n"
"\tupdateSystem(datafile);\n"
"//\tUpdate Userlist and message count\n"
"\tupdateUsers(datafile);\n"
"// Update Chat\n"
"\tupdateChat(datafile);\n"
"}\n"
"\n"
"function updateSystem(datafile) {\n"
"// Update System Info \n"
"\tvar sysContainer = document.getElementById(\"content-from-id\");\n"
"\tvar newHTML = datafile.data.system.channel;\n"
"\tvar myDate = new Date( datafile.data.system.timeGPS *1000);\n"
"\tnewHTML += ' @' + myDate.toFormattedString();\n"
"\tvar newSec = datafile.data.system.timeSinceStart;\n"
"\tvar strsecondUp = newSec.toString();\n"
"\tnewHTML += ' Up:' + strsecondUp.toHHMMSS();\n"
"\tsysContainer.innerHTML = newHTML;\n"
"}\n"
"\n"
"function updateUsers(datafile) {\n"
"\tvar mainContainer = document.getElementById(\"userlist-id\");\n"
"\tvar htmlUsers = '';\n"
"\tvar timeBase = datafile.data.system.timeSinceStart;\n"
"//\tvar lookup = {};\n"
" for (var i = 0; i < datafile.data.users.length; i++) {\n"
" htmlUsers += formatUsers(datafile.data.users[i],timeBase);\n"
"\t}\n"
"\tmainContainer.innerHTML = htmlUsers;\n"
"}\n"
"\n"
"function formatUsers(user,timeBase) {\n"
"\tnewHTML = '<li class=\"clearfix\">';\n"
" newHTML += '<div class=\"channel-name clearfix\">' + user.NameLong + '(' + user.NameShort + ')</div>';\n"
" newHTML += '<div class=\"message-count clearfix\">';\n"
"\tvar secondsLS = timeBase - user.lastSeen;\n"
"\tvar strsecondsLS = secondsLS.toString();\n"
"\tnewHTML += '<svg class=\"icon icon-circle '+onlineStatus(secondsLS)+'\"><use "
"xlink:href=\"#icon-circle\"></use></svg></i>Seen: '+strsecondsLS.toHHMMSS()+' ago&nbsp;';\n"
"\tif (user.lat == 0 || user.lon == 0) {\n"
"\t\tnewHTML += '';\n"
"\t} else {\n"
"\t\tnewHTML += '<div class=\"tooltip\"><svg class=\"icon icon-map-marker\"><use "
"xlink:href=\"#icon-map-marker\"></use></svg><span class=\"tooltiptext\">lat:' + user.lat + ' lon:'+ user.lon+ "
"'</span>';\n"
"\t}\n"
" newHTML += '</div></div>';\n"
" newHTML += '</li>';\n"
"\treturn(newHTML);\n"
"}\n"
"\n"
"function onlineStatus(time) {\n"
"\tif (time < 3600) {\n"
"\t\treturn \"online\"\n"
"\t} else {\n"
"\t\treturn \"offline\"\n"
"\t}\n"
"}\n"
"\n"
"function updateChat(datafile) {\n"
"// Update Chat\n"
"\tvar chatContainer = document.getElementById(\"chat-history-id\");\n"
"\tvar htmlChat = '';\n"
"\tvar timeBase = datafile.data.system.timeSinceStart;\n"
"\tfor (var i = 0; i < datafile.data.chat.length; i++) {\n"
"\t\thtmlChat += formatChat(datafile.data.chat[i],timeBase);\n"
"\t}\n"
"\tchatContainer.innerHTML = htmlChat;\n"
"\tscrollHistory();\n"
"}\n"
"\n"
"function formatChat(data,timeBase) {\n"
"\tvar secondsTS = timeBase - data.timestamp;\n"
"\tvar strsecondsTS = secondsTS.toString();\n"
"\tnewHTML = '<li class=\"clearfix\">';\n"
"\tif (data.local == 1) {\n"
"\t\tnewHTML += '<div class=\"message-data\">';\n"
"\t\tnewHTML += '<span class=\"message-data-name\" >' + data.NameLong + '(' + data.NameShort + ')</span>';\n"
"\t\tnewHTML += '<span class=\"message-data-time\" >' + strsecondsTS.toHHMMSS() + ' ago</span>';\n"
"\t\tnewHTML += '</div>';\n"
"\t\tnewHTML += '<div class=\"message my-message\">' + data.chatLine + '</div>';\n"
"\t} else {\n"
"\t\tnewHTML += '<div class=\"message-data align-right\">';\n"
"\t\tnewHTML += '<span class=\"message-data-time\" >' + strsecondsTS.toHHMMSS() + ' ago</span> &nbsp; &nbsp;';\n"
"\t\tnewHTML += '<span class=\"message-data-name\" >' + data.NameLong + '(' + data.NameShort + ')</span>';\n"
"//\t\tnewHTML += '<i class=\"fa fa-circle online\"></i>';\n"
"\t\tnewHTML += '</div>';\n"
"\t\tnewHTML += '<div class=\"message other-message float-right\">' + data.chatLine + '</div>';\n"
"\t}\n"
"\n"
" newHTML += '</li>';\n"
"\treturn(newHTML);\t\n"
"}\n"
"\n"
"function scrollHistory() {\n"
"\tvar chatContainer = document.getElementById(\"chat-div-id\");\n"
"\tchatContainer.scrollTop = chatContainer.scrollHeight;\n"
"}\n"
"\n"
"\n"
"getData('/json/chat/history/dummy');\n"
"\n"
"\n"
"//window.onload=function(){\n"
"//\talert('onload');\n"
"// Async - Run scroll 0.5sec after onload event\n"
"//\tsetTimeout(scrollHistory(),500);\n"
"// }";
res->setHeader("Set-Cookie",
"mt_session=" + httpsserver::intToString(random(1, 9999999)) + "; Expires=Wed, 20 Apr 2049 4:20:00 PST");
// Status code is 200 OK by default.
// We want to deliver a simple HTML page, so we send a corresponding content type:
res->setHeader("Content-Type", "text/html");
std::string cookie = req->getHeader("Cookie");
//String cookieString = cookie.c_str();
//uint8_t nameIndex = cookieString.indexOf("mt_session");
//DEBUG_MSG(cookie.c_str());
// The response implements the Print interface, so you can use it just like
// you would write to Serial etc.
res->print(out);
std::string filename = "/static/index.html";
std::string filenameGzip = "/static/index.html.gz";
if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) {
// Send "404 Not Found" as response, as the file doesn't seem to exist
res->setStatusCode(404);
res->setStatusText("Not found");
res->println("404 Not Found");
res->printf("<p>File not found: %s</p>\n", filename.c_str());
return;
}
// Try to open the file from SPIFFS
File file;
if (SPIFFS.exists(filename.c_str())) {
file = SPIFFS.open(filename.c_str());
if (!file.available()) {
DEBUG_MSG("File not available - %s\n", filename.c_str());
}
} else if (SPIFFS.exists(filenameGzip.c_str())) {
file = SPIFFS.open(filenameGzip.c_str());
res->setHeader("Content-Encoding", "gzip");
if (!file.available()) {
DEBUG_MSG("File not available\n");
}
}
// Read the file from SPIFFS and write it to the HTTP response body
size_t length = 0;
do {
char buffer[256];
length = file.read((uint8_t *)buffer, 256);
std::string bufferString(buffer, length);
res->write((uint8_t *)bufferString.c_str(), bufferString.size());
} while (length > 0);
}
void handleFavicon(HTTPRequest *req, HTTPResponse *res)
@@ -710,98 +897,15 @@ void handleFavicon(HTTPRequest *req, HTTPResponse *res)
res->write(FAVICON_DATA, FAVICON_LENGTH);
}
/*
To convert text to c strings:
https://tomeko.net/online_tools/cpp_text_escape.php?lang=en
*/
void handleBasicJS(HTTPRequest *req, HTTPResponse *res)
void replaceAll(std::string &str, const std::string &from, const std::string &to)
{
String out = "";
out += "var meshtasticClient;\n"
"var connectionOne;\n"
"\n"
"\n"
"// Important: the connect action must be called from a user interaction (e.g. button press), otherwise the browsers "
"won't allow the connect\n"
"function connect() {\n"
"\n"
" // Create new connection\n"
" var httpconn = new meshtasticjs.IHTTPConnection();\n"
"\n"
" // Set connection params\n"
" let sslActive;\n"
" if (window.location.protocol === 'https:') {\n"
" sslActive = true;\n"
" } else {\n"
" sslActive = false;\n"
" }\n"
" let deviceIp = window.location.hostname; // Your devices IP here\n"
" \n"
"\n"
" // Add event listeners that get called when a new packet is received / state of device changes\n"
" httpconn.addEventListener('fromRadio', function(packet) { console.log(packet)});\n"
"\n"
" // Connect to the device async, then send a text message\n"
" httpconn.connect(deviceIp, sslActive)\n"
" .then(result => { \n"
"\n"
" alert('device has been configured')\n"
" // This gets called when the connection has been established\n"
" // -> send a message over the mesh network. If no recipient node is provided, it gets sent as a broadcast\n"
" return httpconn.sendText('meshtastic is awesome');\n"
"\n"
" })\n"
" .then(result => { \n"
"\n"
" // This gets called when the message has been sucessfully sent\n"
" console.log('Message sent!');})\n"
"\n"
" .catch(error => { console.log(error); });\n"
"\n"
"}";
// Status code is 200 OK by default.
// We want to deliver a simple HTML page, so we send a corresponding content type:
res->setHeader("Content-Type", "text/javascript");
// The response implements the Print interface, so you can use it just like
// you would write to Serial etc.
res->print(out);
if (from.empty())
return;
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
}
}
/*
To convert text to c strings:
https://tomeko.net/online_tools/cpp_text_escape.php?lang=en
*/
void handleBasicHTML(HTTPRequest *req, HTTPResponse *res)
{
String out = "";
out += "<!doctype html>\n"
"<html class=\"no-js\" lang=\"\">\n"
"\n"
"<head>\n"
" <meta charset=\"utf-8\">\n"
" <title></title>\n"
"\n"
" <script src=\"/static/meshtastic.js\"></script>\n"
" <script src=\"basic.js\"></script>\n"
"</head>\n"
"\n"
"<body>\n"
"\n"
" <button id=\"connect_button\" onclick=\"connect()\">Connect to Meshtastic device</button>\n"
" \n"
"</body>\n"
"\n"
"</html>";
// Status code is 200 OK by default.
// We want to deliver a simple HTML page, so we send a corresponding content type:
res->setHeader("Content-Type", "text/html");
// The response implements the Print interface, so you can use it just like
// you would write to Serial etc.
res->print(out);
}

View File

@@ -22,6 +22,7 @@ void handleRoot();
void handleScriptsScriptJS();
void handleJSONChatHistoryDummy();
void replaceAll(std::string& str, const std::string& from, const std::string& to);
class HttpAPI : public PhoneAPI
{

File diff suppressed because it is too large Load Diff

View File

@@ -50,9 +50,11 @@ void deinitWifi()
saving on the 2.4g transceiver.
*/
WiFi.mode(WIFI_MODE_NULL);
DEBUG_MSG("WiFi Turned Off\n");
// WiFi.printDiag(Serial);
if (isWifiAvailable()) {
WiFi.mode(WIFI_MODE_NULL);
DEBUG_MSG("WiFi Turned Off\n");
// WiFi.printDiag(Serial);
}
}
// Startup WiFi
@@ -118,7 +120,7 @@ void initWifi()
}
}
if (!MDNS.begin( "Meshtastic" )) {
if (!MDNS.begin("Meshtastic")) {
DEBUG_MSG("Error setting up MDNS responder!\n");
while (1) {

View File

@@ -392,6 +392,7 @@ void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
}
}
/**
* A helper function that implements simple read and write handling for a uint32_t
*
@@ -415,6 +416,7 @@ int chr_readwrite32le(uint32_t *v, struct ble_gatt_access_ctxt *ctxt)
if (len < sizeof(le)) {
DEBUG_MSG("Error: wrongsized write32\n");
*v = 0;
return BLE_ATT_ERR_UNLIKELY;
} else {
*v = get_le32(le);
DEBUG_MSG("BLE writing a uint32\n");
@@ -441,8 +443,10 @@ int chr_readwrite8(uint8_t *v, size_t vlen, struct ble_gatt_access_ctxt *ctxt)
auto rc = ble_hs_mbuf_to_flat(ctxt->om, v, vlen, &len);
assert(rc == 0);
if (len < vlen)
if (len < vlen) {
DEBUG_MSG("Error: wrongsized write\n");
return BLE_ATT_ERR_UNLIKELY;
}
else {
DEBUG_MSG("BLE writing bytes\n");
}

View File

@@ -202,6 +202,13 @@ void setupMeshService(void)
// FIXME, turn off soft device access for debugging
static bool isSoftDeviceAllowed = true;
void NRF52Bluetooth::shutdown()
{
// Shutdown bluetooth for minimum power draw
DEBUG_MSG("Disable NRF52 bluetooth\n");
Bluefruit.Advertising.stop();
}
void NRF52Bluetooth::setup()
{
// Initialise the Bluefruit module

View File

@@ -4,5 +4,6 @@ class NRF52Bluetooth
{
public:
void setup();
void shutdown();
};

View File

@@ -1,6 +1,8 @@
#include "NRF52Bluetooth.h"
#include "configuration.h"
#include "graphics/TFTDisplay.h"
#include <SPI.h>
#include <Wire.h>
#include <assert.h>
#include <ble_gap.h>
#include <memory.h>
@@ -49,7 +51,7 @@ void getMacAddr(uint8_t *dmac)
NRF52Bluetooth *nrf52Bluetooth;
static bool bleOn = false;
static const bool enableBle = true; // Set to false for easier debugging
static const bool enableBle = false; // Set to false for easier debugging
void setBluetoothEnable(bool on)
{
@@ -64,7 +66,8 @@ void setBluetoothEnable(bool on)
}
}
} else {
DEBUG_MSG("FIXME: implement BLE disable\n");
if (nrf52Bluetooth)
nrf52Bluetooth->shutdown();
}
bleOn = on;
}
@@ -97,7 +100,7 @@ void nrf52Setup()
#ifdef BQ25703A_ADDR
auto *bq = new BQ25713();
if(!bq->setup())
if (!bq->setup())
DEBUG_MSG("ERROR! Charge controller init failed\n");
#endif
@@ -109,4 +112,30 @@ void nrf52Setup()
// randomSeed(r);
DEBUG_MSG("FIXME, call randomSeed\n");
// ::printf("TESTING PRINTF\n");
}
void cpuDeepSleep(uint64_t msecToWake)
{
// FIXME, configure RTC or button press to wake us
// FIXME, power down SPI, I2C, RAMs
Wire.end();
SPI.end();
Serial.end();
Serial1.end();
// FIXME, use system off mode with ram retention for key state?
// FIXME, use non-init RAM per
// https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled
auto ok = sd_power_system_off();
if(ok != NRF_SUCCESS) {
DEBUG_MSG("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!\n");
NRF_POWER->SYSTEMOFF = 1;
}
// The following code should not be run, because we are off
while (1) {
delay(5000);
DEBUG_MSG(".");
}
}

View File

@@ -0,0 +1,44 @@
#include "NodeInfoPlugin.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
#include "main.h"
NodeInfoPlugin nodeInfoPlugin;
bool NodeInfoPlugin::handleReceivedProtobuf(const MeshPacket &mp, const User &p)
{
// FIXME - we currently update NodeInfo data in the DB only if the message was a broadcast or destined to us
// it would be better to update even if the message was destined to others.
nodeDB.updateUser(mp.from, p);
bool wasBroadcast = mp.to == NODENUM_BROADCAST;
// Show new nodes on LCD screen
if (wasBroadcast) {
String lcd = String("Joined: ") + p.long_name + "\n";
screen->print(lcd.c_str());
}
return false; // Let others look at this message also if they want
}
void NodeInfoPlugin::sendOurNodeInfo(NodeNum dest, bool wantReplies)
{
MeshPacket *p = allocReply();
p->to = dest;
p->decoded.want_response = wantReplies;
service.sendToMesh(p);
}
MeshPacket *NodeInfoPlugin::allocReply()
{
User &u = owner;
DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name);
return allocDataProtobuf(u);
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include "ProtobufPlugin.h"
/**
* NodeInfo plugin for sending/receiving NodeInfos into the mesh
*/
class NodeInfoPlugin : public ProtobufPlugin<User>
{
public:
/** Constructor
* name is for debugging output
*/
NodeInfoPlugin() : ProtobufPlugin("nodeinfo", PortNum_NODEINFO_APP, User_fields) {}
/**
* Send our NodeInfo into the mesh
*/
void sendOurNodeInfo(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
protected:
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual bool handleReceivedProtobuf(const MeshPacket &mp, const User &p);
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual MeshPacket *allocReply();
};
extern NodeInfoPlugin nodeInfoPlugin;

View File

@@ -0,0 +1,51 @@
#include "PositionPlugin.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
PositionPlugin positionPlugin;
bool PositionPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Position &p)
{
// FIXME - we currently update position data in the DB only if the message was a broadcast or destined to us
// it would be better to update even if the message was destined to others.
if (p.time) {
struct timeval tv;
uint32_t secs = p.time;
tv.tv_sec = secs;
tv.tv_usec = 0;
perhapsSetRTC(RTCQualityFromNet, &tv);
}
nodeDB.updatePosition(mp.from, p);
return false; // Let others look at this message also if they want
}
MeshPacket *PositionPlugin::allocReply()
{
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
assert(node);
assert(node->has_position);
// Update our local node info with our position (even if we don't decide to update anyone else)
auto position = node->position;
position.time = getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid.
return allocDataProtobuf(position);
}
void PositionPlugin::sendOurPosition(NodeNum dest, bool wantReplies)
{
MeshPacket *p = allocReply();
p->to = dest;
p->decoded.want_response = wantReplies;
service.sendToMesh(p);
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include "ProtobufPlugin.h"
/**
* Position plugin for sending/receiving positions into the mesh
*/
class PositionPlugin : public ProtobufPlugin<Position>
{
public:
/** Constructor
* name is for debugging output
*/
PositionPlugin() : ProtobufPlugin("position", PortNum_POSITION_APP, Position_fields) {}
/**
* Send our position into the mesh
*/
void sendOurPosition(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
protected:
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual bool handleReceivedProtobuf(const MeshPacket &mp, const Position &p);
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual MeshPacket *allocReply();
};
extern PositionPlugin positionPlugin;

View File

@@ -0,0 +1,63 @@
#include "RemoteHardwarePlugin.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
#include "main.h"
RemoteHardwarePlugin remoteHardwarePlugin;
#define NUM_GPIOS 64
bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const HardwareMessage &p)
{
switch (p.typ) {
case HardwareMessage_Type_WRITE_GPIOS:
// Print notification to LCD screen
screen->print("Write GPIOs\n");
for (uint8_t i = 0; i < NUM_GPIOS; i++) {
uint64_t mask = 1 << i;
if (p.gpio_mask & mask) {
digitalWrite(i, (p.gpio_value & mask) ? 1 : 0);
pinMode(i, OUTPUT);
}
}
break;
case HardwareMessage_Type_READ_GPIOS: {
// Print notification to LCD screen
screen->print("Read GPIOs\n");
uint64_t res = 0;
for (uint8_t i = 0; i < NUM_GPIOS; i++) {
uint64_t mask = 1 << i;
if (p.gpio_mask & mask) {
pinMode(i, INPUT_PULLUP);
if (digitalRead(i))
res |= (1 << i);
}
}
// Send the reply
HardwareMessage reply = HardwareMessage_init_default;
reply.typ = HardwareMessage_Type_READ_GPIOS_REPLY;
reply.gpio_value = res;
MeshPacket *p = allocDataProtobuf(reply);
setReplyTo(p, req);
service.sendToMesh(p);
break;
}
case HardwareMessage_Type_READ_GPIOS_REPLY:
case HardwareMessage_Type_GPIOS_CHANGED:
break; // Ignore - we might see our own replies
default:
DEBUG_MSG("Hardware operation %d not yet implemented! FIXME\n", p.typ);
break;
}
return true; // handled
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "ProtobufPlugin.h"
#include "remote_hardware.pb.h"
/**
* A plugin that provides easy low-level remote access to device hardware.
*/
class RemoteHardwarePlugin : public ProtobufPlugin<HardwareMessage>
{
public:
/** Constructor
* name is for debugging output
*/
RemoteHardwarePlugin() : ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields) {}
protected:
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual bool handleReceivedProtobuf(const MeshPacket &mp, const HardwareMessage &p);
};
extern RemoteHardwarePlugin remoteHardwarePlugin;

View File

@@ -0,0 +1,28 @@
#include "configuration.h"
#include "TextMessagePlugin.h"
#include "NodeDB.h"
#include "PowerFSM.h"
TextMessagePlugin textMessagePlugin;
bool TextMessagePlugin::handleReceived(const MeshPacket &mp)
{
auto &p = mp.decoded.data;
DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes);
// We only store/display messages destined for us.
// Keep a copy of the most recent text message.
devicestate.rx_text_message = mp;
devicestate.has_rx_text_message = true;
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
notifyObservers(&mp);
// This is going into the wifidev feature branch
// Only update the WebUI if WiFi is enabled
//#if WiFi_MODE != 0
// notifyWebUI();
//#endif
return false; // Let others look at this message also if they want
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "SinglePortPlugin.h"
#include "Observer.h"
/**
* Text message handling for meshtastic - draws on the OLED display the most recent received message
*/
class TextMessagePlugin : public SinglePortPlugin, public Observable<const MeshPacket *>
{
public:
/** Constructor
* name is for debugging output
*/
TextMessagePlugin() : SinglePortPlugin("text", PortNum_TEXT_MESSAGE_APP) {}
protected:
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual bool handleReceived(const MeshPacket &mp);
};
extern TextMessagePlugin textMessagePlugin;

View File

@@ -1,6 +1,7 @@
#include "CryptoEngine.h"
#include "target_specific.h"
#include <Utility.h>
#include "sleep.h"
// FIXME - move getMacAddr/setBluetoothEnable into a HALPlatform class
@@ -26,6 +27,10 @@ void setBluetoothEnable(bool on)
notImplemented("setBluetoothEnable");
}
void cpuDeepSleep(uint64_t msecs) {
notImplemented("cpuDeepSleep");
}
// FIXME - implement real crypto for linux
CryptoEngine *crypto = new CryptoEngine();

View File

@@ -23,6 +23,7 @@ class Power : private concurrency::OSThread
Power();
void shutdown();
void readPowerStatus();
virtual bool setup();
virtual int32_t runOnce();

View File

@@ -142,19 +142,22 @@ static void waitEnterSleep()
void doDeepSleep(uint64_t msecToWake)
{
DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000);
DEBUG_MSG("Entering deep sleep for %lu seconds\n", msecToWake / 1000);
#ifndef NO_ESP32
// not using wifi yet, but once we are this is needed to shutoff the radio hw
// esp_wifi_stop();
waitEnterSleep();
notifySleep.notifyObservers(NULL); // also tell the regular sleep handlers
notifyDeepSleep.notifyObservers(NULL);
screen->setOn(false); // datasheet says this will draw only 10ua
screen->doDeepSleep(); // datasheet says this will draw only 10ua
nodeDB.saveToDisk();
// Kill GPS power completely (even if previously we just had it in sleep mode)
setGPSPower(false);
setLed(false);
#ifdef RESET_OLED
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
#endif
@@ -163,11 +166,6 @@ void doDeepSleep(uint64_t msecToWake)
digitalWrite(VEXT_ENABLE, 1); // turn off the display power
#endif
// Kill GPS power completely (even if previously we just had it in sleep mode)
setGPSPower(false);
setLed(false);
#ifdef TBEAM_V10
if (axp192_found) {
// Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep.
@@ -185,57 +183,7 @@ void doDeepSleep(uint64_t msecToWake)
}
#endif
/*
Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default.
If an external circuit drives this pin in deep sleep mode, current consumption may
increase due to current flowing through these pullups and pulldowns.
To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function.
For example, on ESP32-WROVER module, GPIO12 is pulled up externally.
GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep,
some current will flow through these external and internal resistors, increasing deep
sleep current above the minimal possible value.
Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button
*/
static const uint8_t rtcGpios[] = {/* 0, */ 2,
/* 4, */
#ifndef USE_JTAG
13,
/* 14, */ /* 15, */
#endif
/* 25, */ 26, /* 27, */
32, 33, 34, 35,
36, 37
/* 38, 39 */};
for (int i = 0; i < sizeof(rtcGpios); i++)
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
// to detect wake and in normal operation the external part drives them hard.
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#ifdef BUTTON_PIN
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
uint64_t gpioMask = (1ULL << BUTTON_PIN);
#ifdef BUTTON_NEED_PULLUP
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
#endif
// Not needed because both of the current boards have external pullups
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of
// just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
#endif
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
#endif
cpuDeepSleep(msecToWake);
}
#ifndef NO_ESP32

View File

@@ -4,7 +4,8 @@
#include "Observer.h"
#include "configuration.h"
void doDeepSleep(uint64_t msecToWake);
void doDeepSleep(uint64_t msecToWake), cpuDeepSleep(uint64_t msecToWake);
#ifndef NO_ESP32
#include "esp_sleep.h"
esp_sleep_wakeup_cause_t doLightSleep(uint64_t msecToWake);

View File

@@ -205,6 +205,7 @@ External serial flash WP25R1635FZUIL0
#define PIN_EINK_MOSI (0 + 29) // also called SDI
// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON
// FIXME - I think this is actually just the board power enable - it enables power to the CPU also
#define PIN_EINK_PWR_ON (0 + 12)
#define HAS_EINK

View File

@@ -147,7 +147,7 @@ static const uint8_t SCK = PIN_SPI_SCK;
#define SX1262_ANT_SW (32 + 10) // P1.10
// To debug via the segger JLINK console rather than the CDC-ACM serial device
#define USE_SEGGER
// #define USE_SEGGER
#ifdef __cplusplus
}

View File

@@ -168,7 +168,7 @@ static const uint8_t SCK = PIN_SPI_SCK;
#define LORA_DISABLE_SENDING // Define this to disable transmission for testing (power testing etc...)
// To debug via the segger JLINK console rather than the CDC-ACM serial device
#define USE_SEGGER
// #define USE_SEGGER
#ifdef __cplusplus
}

4
version.properties Normal file
View File

@@ -0,0 +1,4 @@
[VERSION]
major = 1
minor = 1
build = 20